Copyright (C) Kevin Larke 2009-2020

This file is part of libcm.

libcm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

libcm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

See the GNU General Public License distributed with the libcm package or look here: .


cmDspFx : 'snap' audio effects processor units.

#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmFloatTypes.h"
#include "cmComplexTypes.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmText.h"
#include "cmMath.h"
#include "cmFile.h"
#include "cmFileSys.h"
#include "cmSymTbl.h"
#include "cmJson.h"
#include "cmPrefs.h"
#include "cmDspValue.h"
#include "cmMsgProtocol.h"
#include "cmThread.h" 
#include "cmUdpPort.h"
#include "cmUdpNet.h"
#include "cmSerialPort.h"
#include "cmTime.h"
#include "cmAudioSys.h"
#include "cmProcObj.h"
#include "cmDspCtx.h"
#include "cmDspClass.h"
#include "cmDspUi.h"
#include "cmAudioFile.h"

#include "cmProcObj.h"
#include "cmProcTemplateMain.h"
#include "cmProc.h"
#include "cmMidi.h"
#include "cmProc2.h"
#include "cmProc3.h"
#include "cmVectOpsTemplateMain.h"

#include "app/cmPickup.h"

#include "cmDspSys.h"


cmDspDelay : Simple audio delay with feedback.
enum
{
  kBypassDyId,
  kTimeDyId,
  kFbDyId,
  kInDyId,
  kOutDyId
  
};

cmDspClass_t _cmDelayDC;

typedef struct
{
  cmDspInst_t  inst;
  cmMDelay*    delay;
} cmDspDelay_t;

cmDspInst_t*  _cmDspDelayAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  unsigned chs = 1;
  
  cmDspVarArg_t args[] =
  {
    { "bypass",kBypassDyId,0,0,   kInDsvFl  | kBoolDsvFl   | kOptArgDsvFl,  "Bypass enable flag." },
    { "time",  kTimeDyId, 0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Max delay time in milliseconds" },
    { "fb",    kFbDyId,   0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Feedback"  },
    { "in",    kInDyId,   0, 0,   kInDsvFl  | kAudioBufDsvFl,               "Audio input" },
    { "out",   kOutDyId,  0, chs, kOutDsvFl | kAudioBufDsvFl,               "Audio output." },
    { NULL, 0, 0, 0, 0 }
  };
  
  cmDspDelay_t* p  = cmDspInstAlloc(cmDspDelay_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl);
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultBool(   ctx, &p->inst, kBypassDyId,0,   0 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kTimeDyId,  0,   1000 );
  cmDspSetDefaultDouble( ctx, &p->inst ,kFbDyId,    0.0, 0.0 );
  
  cmReal_t dtimeMs =  cmDspDefaultUInt(&p->inst,kTimeDyId);
  cmReal_t fbCoeff = cmDspDefaultDouble(&p->inst,kFbDyId);
  
  p->delay = cmMDelayAlloc(ctx->cmProcCtx,NULL, cmDspSamplesPerCycle(ctx), cmDspSampleRate(ctx), fbCoeff,  1, &dtimeMs, NULL  );
  
  return &p->inst;
}

cmDspRC_t _cmDspDelayFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  cmDspDelay_t* p  = (cmDspDelay_t*)inst;
  
  cmMDelayFree(&p->delay);
  
  //cmCtxFree(&ampp-&gtctx);
  
  return rc;
}

cmDspRC_t _cmDspDelayReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  
  rc = cmDspApplyAllDefaults(ctx,inst);
  
  return rc;  
}

cmDspRC_t _cmDspDelayExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspDelay_t* p  = (cmDspDelay_t*)inst;
  cmDspRC_t     rc = kOkDspRC;
  
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInDyId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInDyId);
  
  unsigned          oChIdx  = 0;
  cmSample_t*       op      = cmDspAudioBuf(ctx,inst,kOutDyId,oChIdx);
  unsigned          oSmpCnt = cmDspVarRows(inst,kOutDyId);
  
  bool              bypassFl= cmDspBool(inst,kBypassDyId);
  
  cmMDelayExec(p->delay,ip,op,cmMin(iSmpCnt,oSmpCnt),bypassFl);
  
  return rc;
}

cmDspRC_t _cmDspDelayRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspDelay_t* p = (cmDspDelay_t*)inst;
  cmDspRC_t     rc= kOkDspRC;
  
  cmDspSetEvent(ctx,inst,evt);
  
  switch( evt->dstVarId )
  {
  case kBypassDyId:
    break;
    
  case kTimeDyId:
    p->delay->delayArray[0].delayMs = cmDspDouble(inst,kTimeDyId);
    break;
    
  case kFbDyId:
    p->delay->fbCoeff  = cmDspDouble(inst,kFbDyId);
    break;
    
  default:
    { assert(0); }
  }
  return rc;
}

cmDspClass_t* cmDelayClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmDelayDC,ctx,"Delay",
  NULL,
  _cmDspDelayAlloc,
  _cmDspDelayFree,
  _cmDspDelayReset,
  _cmDspDelayExec,
  _cmDspDelayRecv,
  NULL,NULL,
  "Simple delay.");
  
  return &_cmDelayDC;
}


cmDspMtDelay : Multi-tap audio delay with feedback.
enum
{
  kBypassMtId,
  kScaleMtId,
  kFbMtId,
  kInMtId,
  kOutMtId,
  kBaseMsMtId
  
};

cmDspClass_t _cmMtDelayDC;

typedef struct
{
  cmDspInst_t  inst;
  cmMDelay*    p;
  unsigned     baseGainMtId;
  unsigned     tapCnt;
  cmReal_t*    msV;
  cmReal_t*    gainV;
  unsigned     printSymId;
} cmDspMtDelay_t;

// args: bypassFl, time_scale, feedback, tap_ms0, tap_gain0, tapms1, tap_gain1, ....

cmDspInst_t*  _cmDspMtDelayAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  va_list vl1;
  
  cmDspVarArg_t args[] =
  {
    { "bypass",kBypassMtId,0, 0,   kInDsvFl  | kBoolDsvFl   | kReqArgDsvFl,  "Bypass enable flag." },
    { "scale", kScaleMtId, 0, 0,   kInDsvFl  | kDoubleDsvFl | kReqArgDsvFl,  "Scale tap times. (0.0 - 1.0)" },
    { "fb",    kFbMtId,    0, 0,   kInDsvFl  | kDoubleDsvFl | kReqArgDsvFl,  "Feedback"  },
    { "in",    kInMtId,    0, 0,   kInDsvFl  | kAudioBufDsvFl,               "Audio input" },
    { "out",   kOutMtId,   0, 1,   kOutDsvFl | kAudioBufDsvFl,               "Audio output." },
  };
  
  // verify that at least one var arg exists
  if( va_cnt < 5 || cmIsEvenU(va_cnt) )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The 'multi-tap delay requires at least 5 arguments. Three fixed arguments and groups of two tap specification arguments.");
    return NULL;
  }    
  
  va_copy(vl1,vl);
  
  unsigned      reqArgCnt    = 3;
  unsigned      fixArgCnt    = sizeof(args)/sizeof(args[0]);
  unsigned      tapCnt       = (va_cnt - reqArgCnt)/2;
  cmReal_t*     msV          = cmMemAllocZ(cmReal_t,tapCnt);
  cmReal_t*     gainV        = cmMemAllocZ(cmReal_t,tapCnt);
  
  unsigned      argCnt       = fixArgCnt + 2 * tapCnt;
  unsigned      baseGainMtId = kBaseMsMtId + tapCnt;
  cmDspVarArg_t a[ argCnt+1 ];
  unsigned      i;
  
  // Get the taps and gains
  va_arg(vl1,int);     // enable
  va_arg(vl1,double);   // time scale
  va_arg(vl1,double);   // feedback
  for(i=0; i<tapCnt; ++i)
  {
    msV[i]   = va_arg(vl1,double);
    gainV[i] = va_arg(vl1,double);
  }
  
  
  // setup the output gain args 
  cmDspArgCopy(       a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kBaseMsMtId,  tapCnt, "ms",   kBaseMsMtId,  0, 0, kInDsvFl | kDoubleDsvFl, "Tap delay times in milliseconds.");
  cmDspArgSetupN(ctx, a, argCnt, baseGainMtId, tapCnt, "gain", baseGainMtId, 0, 0, kInDsvFl | kDoubleDsvFl, "Tap delay linear gain.");
  cmDspArgSetupNull(  a+argCnt);  // set terminating arg. flag
  
  cmDspMtDelay_t* p  = cmDspInstAlloc(cmDspMtDelay_t,ctx,classPtr,a,instSymId,id,storeSymId,reqArgCnt,vl1);
  
  
  p->p            = cmMDelayAlloc(ctx->cmProcCtx,NULL,0, 0, 0, 0, NULL, NULL  );
  p->baseGainMtId = baseGainMtId;
  p->tapCnt       = tapCnt;
  p->msV          = msV;
  p->gainV        = gainV;
  p->printSymId   = cmSymTblRegisterStaticSymbol(ctx->stH,"_print");
  
  for(i=0; i<tapCnt; ++i)
  {
    cmDspSetDefaultDouble(ctx,&p->inst,kBaseMsMtId+i,   0.0, msV[i]);
    cmDspSetDefaultDouble(ctx,&p->inst,baseGainMtId+i,  0.0, gainV[i]);
  }
  
  va_end(vl1);
  return &p->inst;
}

cmDspRC_t _cmDspMtDelayFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  cmDspMtDelay_t* p  = (cmDspMtDelay_t*)inst;
  
  cmMemFree(p->msV);
  cmMemFree(p->gainV);
  
  cmMDelayFree(&p->p);
  
  return rc;
}

cmDspRC_t _cmDspMtDelayReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  cmDspMtDelay_t* p  = (cmDspMtDelay_t*)inst;
  
  if((rc = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    cmMDelayInit(p->p,cmDspSamplesPerCycle(ctx), cmDspSampleRate(ctx), cmDspDouble(&p->inst,kFbMtId), p->tapCnt, p->msV, p->gainV  );    
  }
  
  return rc;  
}

cmDspRC_t _cmDspMtDelayExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspMtDelay_t* p  = (cmDspMtDelay_t*)inst;
  cmDspRC_t     rc = kOkDspRC;
  
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInMtId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInMtId);
  
  unsigned          oChIdx  = 0;
  cmSample_t*       op      = cmDspAudioBuf(ctx,inst,kOutMtId,oChIdx);
  unsigned          oSmpCnt = cmDspVarRows(inst,kOutMtId);
  
  bool              bypassFl= cmDspBool(inst,kBypassMtId);
  
  if( ip != NULL )
    cmMDelayExec(p->p,ip,op,cmMin(iSmpCnt,oSmpCnt),bypassFl);
  
  return rc;
}

cmDspRC_t _cmDspMtDelayRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspMtDelay_t* p = (cmDspMtDelay_t*)inst;
  cmDspRC_t     rc= kOkDspRC;
  
  cmDspSetEvent(ctx,inst,evt);
  
  // set tap times
  if( kBaseMsMtId <= evt->dstVarId && evt->dstVarId < kBaseMsMtId + p->p->delayCnt )
    cmMDelaySetTapMs(p->p,evt->dstVarId - kBaseMsMtId, cmDspDouble(inst,evt->dstVarId));
  else
    // set tap gains
  if( p->baseGainMtId <= evt->dstVarId && evt->dstVarId < p->baseGainMtId + p->p->delayCnt )
    cmMDelaySetTapGain(p->p,evt->dstVarId - p->baseGainMtId, cmDspDouble(inst,evt->dstVarId));
  else
  {
    switch( evt->dstVarId )
    {
    case kScaleMtId:
      //cmDspDouble(inst,kScaleMtId);
      break;
      
    case kFbMtId:
      p->p->fbCoeff  = cmDspDouble(inst,kFbMtId);
      break;
      
    }
  }
  return rc;
}

cmDspRC_t  _cmDspMtDelayRecvFunc(   cmDspCtx_t* ctx, cmDspInst_t*  inst,  unsigned attrSymId, const cmDspValue_t* value )
{
  cmDspRC_t       rc = kOkDspRC;
  cmDspMtDelay_t* p  = (cmDspMtDelay_t*)inst;
  
  if( cmDsvIsSymbol(value) && (cmDsvSymbol(value)==p->printSymId) )
  {
    int i;
    cmRptPrintf(ctx->rpt,"taps:%i\n",p->tapCnt);
    for(i=0; i<p->tapCnt; ++i)
      cmRptPrintf(ctx->rpt,"%f %f\n",p->msV[i],p->gainV[i]);
    
    cmMDelayReport(p->p, ctx->rpt );
  }
  
  return rc;
}


cmDspClass_t* cmMtDelayClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmMtDelayDC,ctx,"MtDelay",
  NULL,
  _cmDspMtDelayAlloc,
  _cmDspMtDelayFree,
  _cmDspMtDelayReset,
  _cmDspMtDelayExec,
  _cmDspMtDelayRecv,
  NULL,
  _cmDspMtDelayRecvFunc,
  "Multi-tap delay.");
  
  return &_cmMtDelayDC;
}


cmDspPShift : Time-domain pitch shifter.
enum
{
  kBypassPsId,
  kRatioPsId,
  kInPsId,
  kOutPsId
};

cmDspClass_t _cmPShiftDC;

typedef struct
{
  cmDspInst_t   inst;
  //cmCtx*        ctx;
  cmPitchShift* pshift;
} cmDspPShift_t;

cmDspInst_t*  _cmDspPShiftAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  unsigned chs = 1;
  
  cmDspVarArg_t args[] =
  {
    { "bypass",kBypassPsId, 0, 0,   kInDsvFl  | kBoolDsvFl   | kOptArgDsvFl,  "Bypass enable flag." },
    { "ratio", kRatioPsId,  0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Ratio"  },
    { "in",    kInPsId,     0, 0,   kInDsvFl  | kAudioBufDsvFl,               "Audio input" },
    { "out",   kOutPsId,    0, chs, kOutDsvFl | kAudioBufDsvFl,               "Audio output." },
    { NULL, 0, 0, 0, 0 }
  };
  
  cmDspPShift_t* p  = cmDspInstAlloc(cmDspPShift_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl);
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultBool(   ctx, &p->inst, kBypassPsId,  0,     0 );
  cmDspSetDefaultDouble( ctx, &p->inst ,kRatioPsId,   0.0, 1.0 );
  
  p->pshift = cmPitchShiftAlloc(ctx->cmProcCtx,NULL,cmDspSamplesPerCycle(ctx), cmDspSampleRate(ctx)  );
  
  return &p->inst;
}

cmDspRC_t _cmDspPShiftFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  cmDspPShift_t* p  = (cmDspPShift_t*)inst;
  
  cmPitchShiftFree(&p->pshift);
  
  //cmCtxFree(&ampp-&gtctx);
  
  return rc;
}

cmDspRC_t _cmDspPShiftReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  
  rc = cmDspApplyAllDefaults(ctx,inst);
  
  return rc;  
}

cmDspRC_t _cmDspPShiftExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspPShift_t* p  = (cmDspPShift_t*)inst;
  cmDspRC_t      rc = kOkDspRC;
  
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInPsId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInPsId);
  
  unsigned          oChIdx  = 0;
  cmSample_t*       op      = cmDspAudioBuf(ctx,inst,kOutPsId,oChIdx);
  unsigned          oSmpCnt = cmDspVarRows(inst,kOutPsId);
  
  bool              bypassFl= cmDspBool(inst,kBypassPsId);
  
  cmPitchShiftExec(p->pshift,ip,op,cmMin(iSmpCnt,oSmpCnt),cmDspDouble(inst,kRatioPsId),bypassFl);
  
  return rc;
}

cmDspRC_t _cmDspPShiftRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return cmDspSetEvent(ctx,inst,evt);
}

cmDspClass_t* cmPShiftClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmPShiftDC,ctx,"PShift",
  NULL,
  _cmDspPShiftAlloc,
  _cmDspPShiftFree,
  _cmDspPShiftReset,
  _cmDspPShiftExec,
  _cmDspPShiftRecv,
  NULL,NULL,
  "Pitch Shifter.");
  
  return &_cmPShiftDC;
}


cmDspLoopRecd : Loop recorder.
enum
{
  kTimeLrId,
  kPGainLrId,
  kRGainLrId,
  kBypassLrId,
  kPlayLrId,
  kRecdLrId,
  kRatioLrId,
  kInLrId,
  kOutLrId
  
};

cmDspClass_t _cmLoopRecdDC;

typedef struct
{
  cmDspInst_t     inst;
  //cmCtx*          ctx;
  bool            playFl;
  cmLoopRecord*   lrp;
} cmDspLoopRecd_t;

cmDspInst_t*  _cmDspLoopRecdAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  unsigned chs = 1;
  
  cmDspVarArg_t args[] =
  {
    { "time",  kTimeLrId,  0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Max record time in seconds" },
    { "pgain", kPGainLrId, 0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Pass-through gain."},
    { "rgain", kRGainLrId, 0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Recorder out gain."},
    { "bypass",kBypassLrId,0, 0,   kInDsvFl  | kBoolDsvFl,                   "Bypass flag"},
    { "play",  kPlayLrId,  0, 0,   kInDsvFl  | kBoolDsvFl,                   "Play gate flag"  },
    { "recd",  kRecdLrId,  0, 0,   kInDsvFl  | kBoolDsvFl,                   "Recd gate flag" },
    { "ratio", kRatioLrId, 0, 0,   kInDsvFl  | kDoubleDsvFl,                 "Playback speed ratio"},
    { "in",    kInLrId,    0, 0,   kInDsvFl  | kAudioBufDsvFl,               "Audio input" },
    { "out",   kOutLrId,   0, chs, kOutDsvFl | kAudioBufDsvFl,               "Audio output." },
    { NULL, 0, 0, 0, 0 }
  };
  
  cmDspLoopRecd_t* p  = cmDspInstAlloc(cmDspLoopRecd_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl);
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultDouble(   ctx, &p->inst, kTimeLrId,   0,   10 );
  cmDspSetDefaultDouble(   ctx, &p->inst, kPGainLrId,   0,  1.0 );
  cmDspSetDefaultDouble(   ctx, &p->inst, kRGainLrId,   0,  1.0 );
  cmDspSetDefaultBool(     ctx, &p->inst, kBypassLrId, 0,    0 ); 
  cmDspSetDefaultBool(     ctx, &p->inst, kPlayLrId,   0,    0 );
  cmDspSetDefaultBool(     ctx, &p->inst, kRecdLrId,   0,    0 );
  cmDspSetDefaultDouble(   ctx, &p->inst, kRatioLrId,  0,   1.0);
  
  p->lrp   = cmLoopRecordAlloc(ctx->cmProcCtx,NULL,0,0,0   );
  
  return &p->inst;
}

cmDspRC_t _cmDspLoopRecdFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  cmDspLoopRecd_t* p  = (cmDspLoopRecd_t*)inst;
  
  cmLoopRecordFree(&p->lrp);
  
  //cmCtxFree(&ampp-&gtctx);
  
  return rc;
}

cmDspRC_t _cmDspLoopRecdReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  cmDspLoopRecd_t* p  = (cmDspLoopRecd_t*)inst;
  
  rc = cmDspApplyAllDefaults(ctx,inst);
  
  cmReal_t   maxRecdTimeSecs  =  cmDspDefaultDouble(&p->inst,kTimeLrId);
  unsigned   maxRecdTimeSmps  = floor(cmDspSampleRate(ctx) * maxRecdTimeSecs);
  unsigned   xfadeTimeSmps    = floor(cmDspSampleRate(ctx) * 50.0/1000.0);
  
  if( maxRecdTimeSmps != p->lrp->maxRecdSmpCnt || xfadeTimeSmps != p->lrp->xfadeSmpCnt )
    cmLoopRecordInit(p->lrp,cmDspSamplesPerCycle(ctx),maxRecdTimeSmps,xfadeTimeSmps);
  
  return rc;  
}

cmDspRC_t _cmDspLoopRecdExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspLoopRecd_t* p  = (cmDspLoopRecd_t*)inst;
  cmDspRC_t        rc = kOkDspRC;
  
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInLrId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInLrId);
  
  unsigned          oChIdx  = 0;
  cmSample_t*       op      = cmDspAudioBuf(ctx,inst,kOutLrId,oChIdx);
  unsigned          oSmpCnt = cmDspVarRows(inst,kOutLrId);
  
  bool               recdFl   = cmDspBool(inst,kRecdLrId);
  bool               bypassFl = cmDspBool(inst,kBypassLrId);
  double             ratio    = cmDspDouble(inst,kRatioLrId);
  
  double           rgain    = cmDspDouble(inst,kRGainLrId);  // recorder output gain
  double           pgain    = cmDspDouble(inst,kPGainLrId);  // pass through gain
  
  if( ip != NULL && op != NULL )
    cmLoopRecordExec(p->lrp,ip,op,cmMin(iSmpCnt,oSmpCnt), bypassFl, recdFl, p->playFl, ratio, pgain, rgain );
  
  
  p->playFl = false;
  
  return rc;
}

cmDspRC_t _cmDspLoopRecdRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspLoopRecd_t* p = (cmDspLoopRecd_t*)inst;
  cmDspRC_t       rc = cmDspSetEvent(ctx,inst,evt);
  
  
  switch(evt->dstVarId)
  {
  case kPlayLrId:
    p->playFl = cmDspBool(inst,kPlayLrId);
    break;
  }
  
  return rc;
}

cmDspClass_t* cmLoopRecdClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmLoopRecdDC,ctx,"LoopRecd",
  NULL,
  _cmDspLoopRecdAlloc,
  _cmDspLoopRecdFree,
  _cmDspLoopRecdReset,
  _cmDspLoopRecdExec,
  _cmDspLoopRecdRecv,
  NULL,NULL,
  "Loop recorder.");
  
  return &_cmLoopRecdDC;
}


cmDspRectify : Full-wave rectifier.
enum
{
  kBypassRcId,
  kCoeffRcId,
  kInRcId,
  kOutRcId
};

cmDspClass_t _cmRectifyDC;

typedef struct
{
  cmDspInst_t  inst;
} cmDspRectify_t;

cmDspInst_t*  _cmDspRectifyAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  unsigned chs = 1;
  
  cmDspVarArg_t args[] =
  {
    { "bypass",kBypassRcId,0, 0,   kInDsvFl  | kBoolDsvFl   | kOptArgDsvFl,  "Bypass enable flag." },
    { "coeff", kCoeffRcId, 0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl,  "Coefficient" },
    { "in",    kInRcId,    0, 0,   kInDsvFl  | kAudioBufDsvFl,               "Audio input" },
    { "out",   kOutRcId,   0, chs, kOutDsvFl | kAudioBufDsvFl,               "Audio output." },
    { NULL, 0, 0, 0, 0 }
  };
  
  cmDspRectify_t* p  = cmDspInstAlloc(cmDspRectify_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl);
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultBool(   ctx, &p->inst, kBypassRcId,0,    0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kCoeffRcId, 0,  0.0 );
  
  
  return &p->inst;
}

cmDspRC_t _cmDspRectifyFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return kOkDspRC;
}

cmDspRC_t _cmDspRectifyReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  
  rc = cmDspApplyAllDefaults(ctx,inst);
  
  return rc;  
}

cmDspRC_t _cmDspRectifyExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInRcId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInRcId);
  
  unsigned          oChIdx  = 0;
  cmSample_t*       op      = cmDspAudioBuf(ctx,inst,kOutRcId,oChIdx);
  unsigned          oSmpCnt = cmDspVarRows(inst,kOutRcId);
  
  bool              bypassFl= cmDspBool(inst,kBypassRcId);
  unsigned          n       = cmMin(iSmpCnt,oSmpCnt);
  
  unsigned          i;
  
  if( bypassFl )
    memcpy(op,ip,n*sizeof(cmSample_t));
  else
  {    
    for(i=0; i<n; ++i)
      op[i] = ip[i] > 0 ? ip[i] : 0;
  }
  
  return kOkDspRC;
}

cmDspRC_t _cmDspRectifyRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return cmDspSetEvent(ctx,inst,evt);
}

cmDspClass_t* cmRectifyClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmRectifyDC,ctx,"Rectify",
  NULL,
  _cmDspRectifyAlloc,
  _cmDspRectifyFree,
  _cmDspRectifyReset,
  _cmDspRectifyExec,
  _cmDspRectifyRecv,
  NULL,NULL,
  "Half-wave rectifier.");
  
  return &_cmRectifyDC;
}



cmDspGateDetect : Track the onset and offset of an incoming signal.
enum
{
  kWndMsGdId,
  kOnThreshPctGdId,
  kOnThreshDbGdId,
  kOffThreshDbGdId,
  kInGdId,
  kGateGdId,
  kRmsGdId,
  kMeanGdId
};

cmDspClass_t _cmGateDetectDC;

typedef struct
{
  cmDspInst_t  inst;
  //cmCtx*        ctx;
  cmShiftBuf*   sbp;
  cmGateDetect* gdp;
  
  
} cmDspGateDetect_t;

cmDspInst_t*  _cmDspGateDetectAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "wnd",   kWndMsGdId,      0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Window length in milliseconds." },
    { "onpct", kOnThreshPctGdId,0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Onset slope threshold [0.0 - 1.0]." },
    { "ondb",  kOnThreshDbGdId, 0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Onset threshold dB [-Inf to 0]" },
    { "offdb", kOffThreshDbGdId,0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Offset threshold dB [-Inf to 0]" },
    { "in",    kInGdId,         0, 0,   kInDsvFl  | kAudioBufDsvFl,                 "Audio input" },
    { "gate",  kGateGdId,       0, 0,   kOutDsvFl | kBoolDsvFl,                     "Gate state output." },
    { "rms",   kRmsGdId,        0, 0,   kOutDsvFl | kDoubleDsvFl,                   "Signal level RMS"},
    { "mean",  kMeanGdId,       0, 0,   kOutDsvFl | kDoubleDsvFl,                   "Derr mean."},
    { NULL, 0, 0, 0, 0 }
  };
  
  cmDspGateDetect_t* p  = cmDspInstAlloc(cmDspGateDetect_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl);
  
  p->sbp   = cmShiftBufAlloc(ctx->cmProcCtx,NULL,0,0,0);
  p->gdp   = cmGateDetectAlloc(ctx->cmProcCtx,NULL,0,0,0,0);
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultDouble( ctx, &p->inst, kWndMsGdId,       0,    42 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOnThreshPctGdId, 0,    0.8 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOnThreshDbGdId,  0,  -30.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOffThreshDbGdId, 0,  -60.0 );
  
  return &p->inst;
}

cmDspRC_t _cmDspGateDetectFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspGateDetect_t* p = (cmDspGateDetect_t*)inst;
  cmGateDetectFree(&p->gdp);
  cmShiftBufFree(&p->sbp);
  //cmCtxFree(&ampp-&gtctx);
  return kOkDspRC;
}

cmDspRC_t _cmDspGateDetectReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspGateDetect_t* p = (cmDspGateDetect_t*)inst;
  cmDspRC_t     rc = kOkDspRC;
  
  if((rc = cmDspApplyAllDefaults(ctx,inst)) != kOkDspRC )
    return rc;
  
  double   wndMs     = cmDspDouble(inst,kWndMsGdId);
  double   sr        = cmDspSampleRate(ctx);
  unsigned wndSmpCnt = floor(wndMs * sr / 1000.0);
  
  if( cmShiftBufInit(p->sbp, cmDspSamplesPerCycle(ctx), wndSmpCnt, wndSmpCnt/4 ) != cmOkRC )
    return cmErrMsg(&ctx->cmCtx->err,kInstResetFailDspRC,"The gate detector shift buffer initialization failed.");
  
  if( cmGateDetectInit(p->gdp, cmDspSamplesPerCycle(ctx), cmDspDouble(inst,kOnThreshPctGdId), cmDspDouble(inst,kOnThreshDbGdId), cmDspDouble(inst,kOffThreshDbGdId) ) != cmOkRC )
    return cmErrMsg(&ctx->cmCtx->err,kInstResetFailDspRC,"The gate detector shift buffer initialization failed.");
  
  return rc;  
}

cmDspRC_t _cmDspGateDetectExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspGateDetect_t* p = (cmDspGateDetect_t*)inst;
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInGdId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInGdId);
  
  
  while( cmShiftBufExec(p->sbp, ip, iSmpCnt ) )
  {
    cmGateDetectExec(p->gdp,p->sbp->outV, p->sbp->outN );
    
    cmDspSetDouble(ctx,inst,kRmsGdId,p->gdp->rms);
    cmDspSetDouble(ctx,inst,kMeanGdId,p->gdp->mean);
    
    if( p->gdp->deltaFl )
      cmDspSetBool(  ctx,inst,kGateGdId,p->gdp->gateFl);
    
  }
  
  return kOkDspRC;
}

cmDspRC_t _cmDspGateDetectRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t rc;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  switch(evt->dstVarId)
  {
  case kWndMsGdId:
    break;
  case kOnThreshPctGdId:
    break;
  case kOnThreshDbGdId:
    break;
  case kOffThreshDbGdId:
    break;
  case kInGdId:
    break;
  }
  
  return rc;
}

cmDspClass_t* cmGateDetectClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmGateDetectDC,ctx,"GateDetect",
  NULL,
  _cmDspGateDetectAlloc,
  _cmDspGateDetectFree,
  _cmDspGateDetectReset,
  _cmDspGateDetectExec,
  _cmDspGateDetectRecv,
  NULL,NULL,
  "Gate detector.");
  
  return &_cmGateDetectDC;
}



cmDspAutoGain : Normalize a set of audio input signals to acheive a consistent level.
// The purpose of this object is to calculate, store and retrieve gain coefficents
// for a set of audio channels.  The gain coefficients are designed to balance the
// volume of each channel relative to the others.  During gain calibration
// a sample of each channel is taken and it's average volume is determined.  
// After an example of all channels has been received a new set of gain coefficients
// is calculated which decreases the volume of loud channels and increases the
// volume of quiet channels.
//
//  The gain coefficents are made available via a set of 'gain-###' output ports.
//
//  This object acts as an interface to the cmAutoGain processor.  
//
//  As input it takes a channel configuration JSON file of the form:
//  {
//    ch_array : 
//    [ [&quotch&quot,&quotssi&quot,&quotpitch&quot,&quotmidi&quot,&quotgain&quot]
//      [ 0,    0,    &quotC4&quot,   60,    1.0 ]
//               ....
//      [ n     0,    &quotC5&quot,   72,    1.0 ]
//    ] 
//  }      
//
//  Each array in 'ch_array' gives the configuration of a channel.
//  
//
//  It also requires a JSON resource object of the form
//    gdParms: 
//    {
//      medCnt: 5                   
//      avgCnt: 9       
//      suprCnt: 6       
//      offCnt: 3       
//      suprCoeff: 1.400000       
//      onThreshDb: -53.000000       
//      offThreshDb: -80.000000       
//    }    
//
//    These arguments are used to configure the cmAutoGain Proessor gate detector function.
//
//  During runtime the object accepts the following action selector symbol id's:
//    a. start  - begin a new calibration  session 
//    b. proc   - end a calibration session and calculate new gain coeff's
//    c. cancel - cancel a calibration session   
//    d. write  - write the channel configuration file 
//    e. print  - print the current auto gain calibration state
//
//  After a 'start' msg the object accepts channel id's throught its 'id' input port.
//  Each 'id' identifies the channel which it will process next.
//  Upon reception of a channel id the object routes subsequent audio to its
//  internal cmAutoGain processor until it receives the next channel id 
//  or a 'proc' or 'cancel' symbol.



enum { kChCntAgId, kHopAgId, kMedNAgId, kAvgNAgId, kSupNAgId, kOffNAgId, kSupCoefAgId, kOnThrAgId, kOffThrAgId, kSelAgId, kIdAgId, kInBaseAgId }; typedef struct { cmDspInst_t inst; cmAutoGain* agp; unsigned gainBaseAgId; unsigned chCnt; unsigned chIdx; unsigned startSymId; unsigned procSymId; unsigned cancelSymId; unsigned writeSymId; unsigned printSymId; } cmDspAutoGain_t; cmDspClass_t _cmAutoGainDC; cmDspInst_t* _cmDspAutoGainAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl ) { cmDspVarArg_t args[] = { { "chCnt",kChCntAgId, 0, 0, kUIntDsvFl | kReqArgDsvFl, "Audio channel count."}, { "hop", kHopAgId, 0, 0, kDoubleDsvFl | kReqArgDsvFl, "RMS hop in milliseconds."}, { "med", kMedNAgId, 0, 0, kUIntDsvFl | kReqArgDsvFl, "Median filter hop count."}, { "avg", kAvgNAgId, 0, 0, kUIntDsvFl | kReqArgDsvFl, "Average filter hop count."}, { "sup", kSupNAgId, 0, 0, kUIntDsvFl | kReqArgDsvFl, "Supression filter hop count."}, { "off", kOffNAgId, 0, 0, kUIntDsvFl | kReqArgDsvFl, "Offset filter hop count."}, { "supC", kSupCoefAgId,0, 0, kInDsvFl | kDoubleDsvFl | kReqArgDsvFl, "Suppression coefficent."}, { "onThr",kOnThrAgId, 0, 0, kInDsvFl | kDoubleDsvFl | kReqArgDsvFl, "Onset threshold in dB."}, { "offThr",kOffThrAgId,0, 0, kInDsvFl | kDoubleDsvFl | kReqArgDsvFl, "Offset threshold in dB."}, { "sel", kSelAgId, 0, 0, kInDsvFl | kSymDsvFl | kNoArgDsvFl, "Action Selector: start | proc | cancel." }, { "id", kIdAgId, 0, 0, kInDsvFl | kIntDsvFl | kNoArgDsvFl, "Channel id input."}, }; va_list vl1; unsigned i; // verify that at least one var arg exists if( va_cnt < 1 ) { cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The AutoGain constructor must be given the audio channel count as its first argument."); return NULL; } // copy the va_list so that it can be used again in cmDspInstAlloc() va_copy(vl1,vl); // get the first var arg which should be a filename unsigned chCnt = va_arg(vl,unsigned); if( chCnt == 0 ) { cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The AutoGain constructor requires at least 1 audio channel."); va_end(vl1); return NULL; } unsigned fixArgCnt = sizeof(args)/sizeof(args[0]); unsigned argCnt = fixArgCnt + 2 * chCnt; unsigned gainBaseAgId = kInBaseAgId + chCnt; cmDspVarArg_t a[ argCnt+1 ]; assert( fixArgCnt == kInBaseAgId ); // setup the output gain args cmDspArgCopy( a, argCnt, 0, args, fixArgCnt ); cmDspArgSetupN(ctx, a, argCnt, kInBaseAgId, chCnt, "in", kInBaseAgId, 0, 0, kInDsvFl | kAudioBufDsvFl, "audio in"); cmDspArgSetupN(ctx, a, argCnt, gainBaseAgId, chCnt, "gain", gainBaseAgId, 0, 0, kOutDsvFl | kDoubleDsvFl, "calibrated channel gain"); cmDspArgSetupNull( a+argCnt); // set terminating arg. flag // instantiate the object cmDspAutoGain_t* p = cmDspInstAlloc(cmDspAutoGain_t,ctx,classPtr,a,instSymId,id,storeSymId,va_cnt,vl1); // assign the current gain coefficients for(i=0; i<chCnt; ++i) cmDspSetDefaultDouble( ctx, &p->inst, gainBaseAgId + i, 0.0, 1.0); // allocate the auto gain calculation proc p->agp = cmAutoGainAlloc(ctx->cmProcCtx,NULL,0,0,0,0,NULL); p->chCnt = chCnt; p->chIdx = cmInvalidIdx; p->gainBaseAgId= gainBaseAgId; p->startSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"start"); p->procSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"proc"); p->cancelSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"cancel"); p->printSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"print"); cmDspSetDefaultSymbol( ctx, &p->inst, kSelAgId, p->cancelSymId ); cmDspSetDefaultInt( ctx, &p->inst, kIdAgId, 0, cmInvalidId ); va_end(vl1); return &p->inst; } cmDspRC_t _cmDspAutoGainFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt ) { cmDspAutoGain_t* p = (cmDspAutoGain_t*)inst; cmAutoGainFree(&p->agp); return kOkDspRC; } cmDspRC_t _cmDspAutoGainReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt ) { cmDspRC_t rc = kOkDspRC; rc = cmDspApplyAllDefaults(ctx,inst); return rc; } cmDspRC_t _cmDspAutoGainExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt ) { cmDspAutoGain_t* p = (cmDspAutoGain_t*)inst; unsigned curInChIdx = cmDspInt( inst, kIdAgId); if( cmDspSymbol( inst, kSelAgId ) == p->startSymId && curInChIdx != cmInvalidId ) { unsigned inChVarId = kInBaseAgId+curInChIdx; const cmSample_t* ip = cmDspAudioBuf(ctx,inst,inChVarId,0); unsigned iSmpCnt = cmDspVarRows(inst,inChVarId); cmAutoGainProcCh( p->agp, ip, iSmpCnt ); } return kOkDspRC; } cmDspRC_t _cmDspAutoGainInit( cmDspCtx_t* ctx, cmDspInst_t* inst ) { cmDspAutoGain_t* p = (cmDspAutoGain_t*)inst; cmGateDetectParams gd; unsigned i; // collect the params into a cmGateDetectParams recd gd.medCnt = cmDspUInt( inst, kMedNAgId ); gd.avgCnt = cmDspUInt( inst, kAvgNAgId ); gd.suprCnt = cmDspUInt( inst, kSupNAgId ); gd.offCnt = cmDspUInt( inst, kOffNAgId ); gd.suprCoeff = cmDspDouble( inst, kSupCoefAgId ); gd.onThreshDb = cmDspDouble( inst, kOnThrAgId ); gd.offThreshDb = cmDspDouble( inst, kOffThrAgId ); // setup the internal auto-gain object if( cmAutoGainInit(p->agp, cmDspSamplesPerCycle(ctx), cmDspSampleRate(ctx), cmDspDouble(inst,kHopAgId), p->chCnt, &gd ) != cmOkRC ) return cmDspInstErr(ctx,inst,kSubSysFailDspRC,"The internal auto-gain instance could not be initialized."); // send out gain's of 1.0 so that the input audio is not // biased by any existing scaling. for(i=0; i<p->chCnt; ++i) cmDspSetDouble( ctx, inst, p->gainBaseAgId+i, 1.0); return kOkDspRC; } cmDspRC_t _cmDspAutoGainRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt ) { cmDspAutoGain_t* p = (cmDspAutoGain_t*)inst; cmDspRC_t rc = kOkDspRC; switch( evt->dstVarId ) { case kSelAgId: { // store the current 'sel' state unsigned prvSymId = cmDspSymbol(inst,kSelAgId); // update it 'sel' to the new state cmDspSetEvent(ctx,inst,evt); // get the new state unsigned newSymId = cmDspSymbol(inst,kSelAgId); // // if PRINTing was requested // if( newSymId == p->printSymId ) { cmRptPrintf(&ctx->cmCtx->rpt,"Auto-Gain Report\n"); cmAutoGainPrint( p->agp, &ctx->cmCtx->rpt ); goto doneLabel; } // // if calibration was CANCELLED // if( newSymId == p->cancelSymId ) { cmDspSetInt( ctx, inst, kIdAgId, cmInvalidId ); cmRptPrintf(&ctx->cmCtx->rpt,"cancelled\n"); goto doneLabel; } // // if calibration STARTup was requested - initialize the autogain proc // if( newSymId == p->startSymId ) { _cmDspAutoGainInit(ctx,inst); cmRptPrintf(&ctx->cmCtx->rpt,"started\n"); goto doneLabel; } // // if calibration PROCessing was requested // if( newSymId == p->procSymId && prvSymId == p->startSymId ) { cmRptPrintf(&ctx->cmCtx->rpt,"proc\n"); // set the current channel id to 'cmInvalidId' to stop calls to // cmAutoGainProcCh() in _cmDspAutoGainExec() cmDspSetInt( ctx, inst, kIdAgId, cmInvalidId ); // update the auto gain coefficients if( cmAutoGainCalcGains(p->agp) == cmOkRC ) { // send the new auto gain coefficients to the output ports unsigned i; for(i=0; i<p->chCnt; ++i) cmDspSetDouble( ctx, inst, p->gainBaseAgId+i, p->agp->chArray[i].gain); } goto doneLabel; } } break; case kIdAgId: cmDspSetEvent(ctx,inst,evt); if( cmDspSymbol(inst,kSelAgId) == p->startSymId ) { cmRptPrintf(&ctx->cmCtx->rpt,"id:%i\n", cmDspInt(inst,kIdAgId)); cmAutoGainStartCh(p->agp, p->chIdx = cmDspInt(inst,kIdAgId)); } break; default: { assert(0); } } doneLabel: return rc; } cmDspClass_t* cmAutoGainClassCons( cmDspCtx_t* ctx ) { cmDspClassSetup(&_cmAutoGainDC,ctx,"AutoGain", NULL, _cmDspAutoGainAlloc, _cmDspAutoGainFree, _cmDspAutoGainReset, _cmDspAutoGainExec, _cmDspAutoGainRecv, NULL,NULL, "Auto-gain calibrator."); return &_cmAutoGainDC; }
cmDspEnvFollow : Generate a control signal from by analyzing the power envelope of an incoming audio signal.
enum
{
  kHopMsEfId,      // RMS window length in milliseconds
  kMedCntEfId,     
  kAvgCntEfId,     
  kSuprCntEfId,    
  kOffCntEfId,     
  kSuprCoefEfId,  
  kOnThrDbEfId,    
  kOffThrDbEfId,   
  kMaxDbEfId,
  kInEfId,     
  kGateEfId,
  kRmsEfId,
  kLevelEfId,
  kOnEfId,
  kOffEfId
};

cmDspClass_t _cmEnvFollowDC;

typedef struct
{
  cmDspInst_t    inst;
  cmShiftBuf*    sbp;
  cmGateDetect2* gdp;
} cmDspEnvFollow_t;

cmDspInst_t*  _cmDspEnvFollowAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "wnd",   kHopMsEfId,      0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "RMS Window length"},
    { "med",   kMedCntEfId,     0, 0,   kInDsvFl  | kUIntDsvFl     | kOptArgDsvFl,  "Median filter length." },
    { "avg",   kAvgCntEfId,     0, 0,   kInDsvFl  | kUIntDsvFl     | kOptArgDsvFl,  "Averaging filter length." },
    { "sup",   kSuprCntEfId,    0, 0,   kInDsvFl  | kUIntDsvFl     | kOptArgDsvFl,  "Supression filter length." },
    { "off",   kOffCntEfId,     0, 0,   kInDsvFl  | kUIntDsvFl     | kOptArgDsvFl,  "Offset detection window length" },
    { "supc",  kSuprCoefEfId,   0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Suppression shape coefficient" },
    { "ondb",  kOnThrDbEfId,    0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Onset threshold dB." },
    { "offdb", kOffThrDbEfId,   0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Offset threshold dB" },
    { "maxdb", kMaxDbEfId,      0, 0,   kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl,  "Max reference dB" },
    { "in",    kInEfId,         0, 0,   kInDsvFl  | kAudioBufDsvFl,                 "Audio input" },
    { "gate",  kGateEfId,       0, 0,   kOutDsvFl | kBoolDsvFl,                     "Gate state output." },
    { "rms",   kRmsEfId,        0, 0,   kOutDsvFl | kDoubleDsvFl,                   "Signal level RMS"},
    { "level", kLevelEfId,      0, 0,   kOutDsvFl | kDoubleDsvFl,                   "Signal level 0.0-1.0 as scale between offset thresh dB and max dB"},
    { "ons",   kOnEfId,         0, 0,   kOutDsvFl | kUIntDsvFl,                     "Onset counter"},
    { "offs",  kOffEfId,        0, 0,   kOutDsvFl | kUIntDsvFl,                     "Offset counter"},
    { NULL, 0, 0, 0, 0 }
  };
  
  cmDspEnvFollow_t* p  = cmDspInstAlloc(cmDspEnvFollow_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl);
  
  p->sbp   = cmShiftBufAlloc(ctx->cmProcCtx,NULL,0,0,0);
  p->gdp   = cmGateDetectAlloc2(ctx->cmProcCtx,NULL,0,NULL);
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultDouble( ctx, &p->inst, kHopMsEfId,       0,     12 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kMedCntEfId,      0,      5 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kAvgCntEfId,      0,      9 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kSuprCntEfId,     0,      6 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kOffCntEfId,      0,      3 );
  cmDspSetDefaultDouble( ctx, &p->inst, kSuprCoefEfId,    0,    1.4 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOnThrDbEfId,     0,    -45 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOffThrDbEfId,    0,    -80 );
  cmDspSetDefaultDouble( ctx, &p->inst, kMaxDbEfId,       0,    -10.0 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kOnEfId,          0,      0 );
  cmDspSetDefaultUInt(   ctx, &p->inst, kOffEfId,         0,      0 );
  
  return &p->inst;
}

cmDspRC_t _cmDspEnvFollowFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspEnvFollow_t* p = (cmDspEnvFollow_t*)inst;
  cmGateDetectFree2(&p->gdp);
  cmShiftBufFree(&p->sbp);
  return kOkDspRC;
}

cmDspRC_t _cmDspEnvFollowReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspEnvFollow_t* p = (cmDspEnvFollow_t*)inst;
  cmDspRC_t     rc = kOkDspRC;
  
  if((rc = cmDspApplyAllDefaults(ctx,inst)) != kOkDspRC )
    return rc;
  
  cmGateDetectParams r;
  r.medCnt      = cmDspUInt(   inst, kMedCntEfId );
  r.avgCnt      = cmDspUInt(   inst, kAvgCntEfId );
  r.suprCnt     = cmDspUInt(   inst, kSuprCntEfId );
  r.offCnt      = cmDspUInt(   inst, kOffCntEfId );
  r.suprCoeff   = cmDspDouble( inst, kSuprCoefEfId );
  r.onThreshDb  = cmDspDouble( inst, kOnThrDbEfId );
  r.offThreshDb = cmDspDouble( inst, kOffThrDbEfId );
  
  double   sr        = cmDspSampleRate(ctx);
  double   hopSmpCnt = floor(sr * cmDspDouble(inst,kHopMsEfId) / 1000 );
  unsigned wndSmpCnt = floor(r.medCnt * hopSmpCnt);
  
  if( cmShiftBufInit(p->sbp, cmDspSamplesPerCycle(ctx), wndSmpCnt, hopSmpCnt ) != cmOkRC )
    return cmDspInstErr(ctx,inst,kInstResetFailDspRC,"The gate detector shift buffer initialization failed.");
  
  if( cmGateDetectInit2(p->gdp, cmDspSamplesPerCycle(ctx), &r ) != cmOkRC )
    return cmDspInstErr(ctx,inst,kInstResetFailDspRC,"The gate detector shift buffer initialization failed.");
  
  return rc;  
}

cmDspRC_t _cmDspEnvFollowExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspEnvFollow_t* p       = (cmDspEnvFollow_t*)inst;
  unsigned          iChIdx  = 0;
  const cmSample_t* ip      = cmDspAudioBuf(ctx,inst,kInEfId,iChIdx);
  unsigned          iSmpCnt = cmDspVarRows(inst,kInEfId);
  double            maxDb   = cmDspDouble(inst,kMaxDbEfId);
  double            offDb   = cmDspDouble(inst,kOffThrDbEfId);
  
  while( cmShiftBufExec(p->sbp, ip, iSmpCnt ) )
  {
    cmGateDetectExec2(p->gdp,p->sbp->outV, p->sbp->outN );
    
    // RMS is going out at the audio rate - maybe there should be an option
    // to send it only when the gate changes - this could significantly 
    // cut down on unnecessary transmission if the RMS is only used 
    // when the gate changes    
    cmDspSetDouble(ctx,inst,kRmsEfId, p->gdp->rms );
    
    double rmsDb = p->gdp->rms <  0.00001 ? -100.0 : 20.0 * log10(p->gdp->rms);
    double level = maxDb       <= offDb   ?      0 : fabs((offDb - cmMax( offDb, cmMin( maxDb, rmsDb ))) / (maxDb - offDb));
    
    cmDspSetDouble(ctx,inst,kLevelEfId, level );
    
    if( p->gdp->onFl || p->gdp->offFl )
    {
      cmDspSetBool(ctx, inst, kGateEfId, p->gdp->gateFl);    
      
      if( p->gdp->onFl )
        cmDspSetUInt( ctx, inst, kOnEfId, cmDspUInt(inst,kOnEfId) + 1 );
      
      if( p->gdp->offFl )
        cmDspSetUInt( ctx, inst, kOffEfId, cmDspUInt(inst,kOffEfId) + 1 );
    }
  }
  
  return kOkDspRC;
}

cmDspRC_t _cmDspEnvFollowRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t rc;
  cmDspEnvFollow_t* p = (cmDspEnvFollow_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    switch( evt->dstVarId )
    {
    case kOnThrDbEfId:
      cmGateDetectSetOnThreshDb2(p->gdp, cmDspDouble(inst,kOnThrDbEfId) );
      break;
      
    case kOffThrDbEfId:
      cmGateDetectSetOffThreshDb2(p->gdp, cmDspDouble(inst,kOffThrDbEfId) );
      break;
    }
    
  }
  return rc;
}

cmDspClass_t* cmEnvFollowClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmEnvFollowDC,ctx,"EnvFollow",
  NULL,
  _cmDspEnvFollowAlloc,
  _cmDspEnvFollowFree,
  _cmDspEnvFollowReset,
  _cmDspEnvFollowExec,
  _cmDspEnvFollowRecv,
  NULL,NULL,
  "Envelope follower and gate detector.");
  
  return &_cmEnvFollowDC;
}


cmDspXfader : Gate controlled fader bank.
// Fade in and out an arbitrary number of audio signals based on gate signals.
// When the gate is high the signal fades in and when the gate is low the signal fades out.
// Constructor Args:
//    Required: Count of input and output channels.
//    Optional: Fade time in milliseconds.
//
// Inputs:
//    bool   Control gates
//    audio  Input audio.
// Outputs:
//    audio  Output audio
//    double Channel gains.

enum
{
  kChCntXfId,
  kFadeTimeMsXfId,
  kMstrGateXfId,
  kFadeInTimeMsXfId,
  kFadeOutTimeMsXfId,
  kResetXfId,
  kOnXfId,
  kOffXfId,
  kGateBaseXfId,
};

cmDspClass_t _cmXfaderDC;

typedef struct
{
  cmDspInst_t  inst;
  cmXfader*    xfdp;
  unsigned     inBaseXfId;
  unsigned     outBaseXfId;
  unsigned     stateBaseXfId;
  unsigned     gainBaseXfId;
  unsigned     chCnt;
  bool*        chGateV;
  unsigned     onSymId;
  unsigned     offSymId;
} cmDspXfader_t;

cmDspInst_t*  _cmDspXfaderAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "chs",   kChCntXfId,         0, 0,               kUIntDsvFl   | kReqArgDsvFl, "Input and Output channel count"},
    { "ms",    kFadeTimeMsXfId,    0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl, "Fade time in milliseonds."},
    { "mgate", kMstrGateXfId,      0, 0,   kInDsvFl  | kBoolDsvFl   | kOptArgDsvFl, "Master gate - can be used to set all gates."},
    { "ims",   kFadeInTimeMsXfId,  0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl, "Fade in time in milliseonds."},
    { "oms",   kFadeOutTimeMsXfId, 0, 0,   kInDsvFl  | kDoubleDsvFl | kOptArgDsvFl, "Fade out time in milliseonds."},
    { "reset", kResetXfId,         0, 0,   kInDsvFl  | kBoolDsvFl,                  "Jump to gate states rather than fade."},
    { "on",    kOnXfId,            0, 0,   kOutDsvFl | kSymDsvFl,                   "Send 'on' when all ch's transition from off to on."},
    { "off",   kOffXfId,           0, 0,   kOutDsvFl | kSymDsvFl,                   "Send 'off' when all ch's transition from on to off."},
  };
  
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The Xfader object must be given a channel count argument.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  unsigned chCnt         = va_arg(vl,int);
  unsigned fixArgCnt     = sizeof(args)/sizeof(args[0]);
  unsigned argCnt        = fixArgCnt + 5*chCnt;
  unsigned inBaseXfId    = kGateBaseXfId + chCnt;
  unsigned outBaseXfId   = inBaseXfId + chCnt;
  unsigned stateBaseXfId = outBaseXfId + chCnt;
  unsigned gainBaseXfId  = stateBaseXfId + chCnt;
  cmDspVarArg_t a[ argCnt+1 ];
  
  
  // setup the input gate detectors and the output gain args 
  cmDspArgCopy(  a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kGateBaseXfId, chCnt, "gate", kGateBaseXfId, 0, 0, kInDsvFl  | kBoolDsvFl,     "gate flags");
  cmDspArgSetupN(ctx, a, argCnt, inBaseXfId,    chCnt, "in",   inBaseXfId,    0, 0, kInDsvFl  | kAudioBufDsvFl, "audio input");
  cmDspArgSetupN(ctx, a, argCnt, outBaseXfId,   chCnt, "out",  outBaseXfId,   0, 1, kOutDsvFl | kAudioBufDsvFl, "audio output"); 
  cmDspArgSetupN(ctx, a, argCnt, stateBaseXfId, chCnt, "state",stateBaseXfId, 0, 0, kOutDsvFl | kBoolDsvFl,     "current fader state"); 
  cmDspArgSetupN(ctx, a, argCnt, gainBaseXfId,  chCnt, "gain", gainBaseXfId,  0, 0, kOutDsvFl | kDoubleDsvFl,   "gain output"); 
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  cmDspXfader_t* p  = cmDspInstAlloc(cmDspXfader_t,ctx,classPtr,a,instSymId,id,storeSymId,va_cnt,vl1);
  
  double fadeTimeMs = cmDspDouble(&p->inst, kFadeTimeMsXfId );
  p->xfdp           = cmXfaderAlloc(ctx->cmProcCtx,NULL,cmDspSampleRate(ctx), chCnt, fadeTimeMs);
  p->inBaseXfId     = inBaseXfId;
  p->outBaseXfId    = outBaseXfId;
  p->stateBaseXfId  = stateBaseXfId;
  p->gainBaseXfId   = gainBaseXfId;
  p->chCnt          = chCnt;
  p->chGateV        = cmMemAllocZ(bool,p->chCnt);
  p->onSymId        = cmSymTblRegisterStaticSymbol(ctx->stH,"on");
  p->offSymId       = cmSymTblRegisterStaticSymbol(ctx->stH,"off");
  
  // set default values for the parameters that were not explicitely set in the va_arg list
  cmDspSetDefaultDouble( ctx, &p->inst, kFadeTimeMsXfId,  0,     100 );
  cmDspSetDefaultBool(   ctx, &p->inst, kMstrGateXfId,    false, false);
  cmDspSetDefaultSymbol( ctx, &p->inst, kOnXfId,    p->onSymId );
  cmDspSetDefaultSymbol( ctx, &p->inst, kOffXfId,   p->offSymId );  
  
  int i;
  for(i=0; i<chCnt; ++i)
    cmDspSetDefaultBool( ctx, &p->inst, stateBaseXfId+i, false, false );
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspXfaderFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspXfader_t* p = (cmDspXfader_t*)inst;
  cmMemFree(p->chGateV);
  cmXfaderFree(&p->xfdp);
  return kOkDspRC;
}

cmDspRC_t _cmDspXfaderReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  rc = cmDspApplyAllDefaults(ctx,inst);
  
  cmDspXfader_t* p  = (cmDspXfader_t*)inst;
  
  // TODO: zeroing of output audio buffers should be built into cmDspApplyAllDefaults().
  unsigned i;
  for(i=0; i<p->chCnt; ++i)
    cmDspZeroAudioBuf(ctx,inst,p->outBaseXfId + i);
  
  return rc;  
}

cmDspRC_t _cmDspXfaderExec(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = cmOkRC;
  cmDspXfader_t* p  = (cmDspXfader_t*)inst;
  unsigned       i;
  
  // update the internal cross fader by providing it with new gate settings and generate new gain values
  cmXfaderExec( p->xfdp, cmDspSamplesPerCycle(ctx), p->chGateV, p->chCnt );
  
  for(i=0; i<p->chCnt; ++i)
  {
    unsigned          n  = cmDspAudioBufSmpCount(ctx,inst,p->outBaseXfId+i,0);
    cmSample_t*       op = cmDspAudioBuf(ctx,inst,p->outBaseXfId+i,0);
    const cmSample_t* ip = cmDspAudioBuf(ctx,inst,p->inBaseXfId+i,0);
    cmSample_t        gain = (cmSample_t)p->xfdp->chArray[i].ep_gain;
    
    if( op != NULL )
    {
      if( ip == NULL )
        cmVOS_Zero(op,n);
      else
        cmVOS_MultVVS(op,n,ip,gain);
    }
    
    if( p->xfdp->chArray[i].onFl )
    {
      cmDspSetBool(ctx,inst,p->stateBaseXfId+i,true);
    }
    
    if( p->xfdp->chArray[i].offFl )
    {
      cmDspSetBool(ctx,inst,p->stateBaseXfId+i,false);
    }
    
    // send the gain output
    cmDspSetDouble(ctx,inst,p->gainBaseXfId+i,gain);
    
  }
  
  if( p->xfdp->onFl )
    cmDspSetSymbol(ctx,inst,kOnXfId,p->onSymId);
  
  if( p->xfdp->offFl )
    cmDspSetSymbol(ctx,inst,kOffXfId,p->offSymId);
  
  return rc;
}

cmDspRC_t _cmDspXfaderRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t rc;
  cmDspXfader_t* p = (cmDspXfader_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  switch( evt->dstVarId )
  {
  case kFadeTimeMsXfId:
    // if this is an xfade time event then transfer the new xfade time to the xfade proc
    cmXfaderSetXfadeTime(p->xfdp,cmDspDouble(inst,kFadeTimeMsXfId));
    break;
    
  case kMstrGateXfId:
    {
      bool fl = cmDspBool(inst,kMstrGateXfId);
      unsigned i;
      for(i=0; i<p->chCnt; ++i)
        p->chGateV[i] = fl;
    }
    break;
    
  case kFadeInTimeMsXfId:
    cmXfaderSetXfadeInTime(p->xfdp,cmDspDouble(inst,kFadeInTimeMsXfId));
    break;
    
  case kFadeOutTimeMsXfId:
    cmXfaderSetXfadeOutTime(p->xfdp,cmDspDouble(inst,kFadeOutTimeMsXfId));
    break;
    
  case kResetXfId:
    {
      cmXfaderExec( p->xfdp, cmDspSamplesPerCycle(ctx), p->chGateV, p->chCnt );
      cmXfaderJumpToDestinationGain(p->xfdp);
      // force the chGateV[] to match the xfaders state
      int i;
      for(i=0; i<p->chCnt; ++i)
      {
        bool gateFl = p->xfdp->chArray[i].gateFl;
        p->chGateV[i] = gateFl;
        cmDspSetBool(  ctx,inst,p->stateBaseXfId + i, gateFl);
        cmDspSetDouble(ctx,inst,p->gainBaseXfId  + i, gateFl ? 1.0 : 0.0 );          
      }
    }
    break;
    
  }
  
  // record gate changes into p-&gtchGateV[] for later use in _cmDspXfaderExec().
  if( kGateBaseXfId <= evt->dstVarId && evt->dstVarId < kGateBaseXfId + p->chCnt )
  {
    p->chGateV[ evt->dstVarId - kGateBaseXfId ] = cmDspBool( inst, evt->dstVarId );
  }
  
  return rc;
}

cmDspClass_t* cmXfaderClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmXfaderDC,ctx,"Xfader",
  NULL,
  _cmDspXfaderAlloc,
  _cmDspXfaderFree,
  _cmDspXfaderReset,
  _cmDspXfaderExec,
  _cmDspXfaderRecv,
  NULL,NULL,
  "Cross fade gain generator.");
  
  return &_cmXfaderDC;
}



cmDspChCfg : Configure a 'fluxo' channel.

enum
{
  kFnCcId,
  kSelCcId,
  kDoneCcId,
  kGainBaseCcId
};

cmDspClass_t _cmChCfgDC;

typedef struct
{
  cmDspInst_t  inst;
  cmChCfg*     ccp;
  unsigned     midiBaseCcId;
  unsigned     hzBaseCcId;
  unsigned     chBaseCcId;
  unsigned     nsflBaseCcId;
  unsigned     nshzBaseCcId;
  unsigned     printSymId;
  unsigned     writeSymId;
  unsigned     nsCmdSymId;
  unsigned     hzCmdSymId;
  unsigned     resetSymId;
} cmDspChCfg_t;

cmDspInst_t*  _cmDspChCfgAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "fn",   kFnCcId,  0, 0,               kStrzDsvFl| kReqArgDsvFl, "Channel configuration JSON file name."},
    { "sel",  kSelCcId, 0, 0,   kInDsvFl  | kSymDsvFl,                "Action selector: print | write | ns | reset"},
    { "done", kDoneCcId,0, 0,   kOutDsvFl | kSymDsvFl,                "Trigger following action."}
  };
  
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The channel configuration object must be given a file name argument.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  const cmChar_t* chCfgFn = va_arg(vl,cmChar_t*);
  
  cmChCfg* ccp = cmChCfgAlloc( ctx->cmProcCtx, NULL, ctx->cmCtx, chCfgFn );
  
  if( ccp == NULL || ccp->chCnt==0 )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The channel configuration object could not be initialized with the file name '%s'.",cmStringNullGuard(chCfgFn));
    return NULL;
  }
  
  unsigned       chCnt         = ccp->chCnt;
  unsigned       nsChCnt       = ccp->nsChCnt;
  unsigned       fixArgCnt     = sizeof(args)/sizeof(args[0]);
  unsigned       argCnt        = fixArgCnt + 5*chCnt + nsChCnt;
  unsigned       midiBaseCcId  = kGainBaseCcId + chCnt;
  unsigned       hzBaseCcId    = midiBaseCcId  + chCnt;
  unsigned       chBaseCcId    = hzBaseCcId    + chCnt;
  unsigned       nsflBaseCcId  = chBaseCcId    + chCnt;
  unsigned       nshzBaseCcId  = nsflBaseCcId  + chCnt;
  cmDspChCfg_t*  p             = NULL;
  cmDspVarArg_t  a[ argCnt+1 ];
  unsigned       i,j;
  
  // setup the input gate detectors and the output gain args 
  cmDspArgCopy(  a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kGainBaseCcId, chCnt,   "gain",  kGainBaseCcId, 0, 0, kSendDfltDsvFl | kInDsvFl   | kOutDsvFl | kDoubleDsvFl,  "Gain input and output.");
  cmDspArgSetupN(ctx, a, argCnt, midiBaseCcId,  chCnt,   "midi",  midiBaseCcId,  0, 0, kSendDfltDsvFl | kOutDsvFl  | kUIntDsvFl,                "MIDI pitch output");
  cmDspArgSetupN(ctx, a, argCnt, hzBaseCcId,    chCnt,   "hz",    hzBaseCcId,    0, 0, kSendDfltDsvFl | kOutDsvFl  | kDoubleDsvFl,              "pitch output in Hz");
  cmDspArgSetupN(ctx, a, argCnt, chBaseCcId,    chCnt,   "ch",    chBaseCcId,    0, 0, kSendDfltDsvFl | kOutDsvFl  | kUIntDsvFl  ,              "Audio channel index");
  cmDspArgSetupN(ctx, a, argCnt, nsflBaseCcId,  chCnt,   "nsfl",  nsflBaseCcId,  0, 0,                  kOutDsvFl  | kBoolDsvFl,                "noise shaper enables");
  cmDspArgSetupN(ctx, a, argCnt, nshzBaseCcId,  nsChCnt, "nshz",  nshzBaseCcId,  0, 0,                  kOutDsvFl  | kDoubleDsvFl,              "noise-shaper pitch output in Hz");
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  if((p = cmDspInstAlloc(cmDspChCfg_t,ctx,classPtr,a,instSymId,id,storeSymId,va_cnt,vl1)) == NULL )
    return NULL;
  
  p->ccp           = ccp;
  p->midiBaseCcId  = midiBaseCcId;
  p->hzBaseCcId    = hzBaseCcId;
  p->chBaseCcId    = chBaseCcId;
  p->nsflBaseCcId  = nsflBaseCcId;
  p->nshzBaseCcId  = nshzBaseCcId;
  p->writeSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"write");
  p->printSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"print");
  p->nsCmdSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"ns");
  p->hzCmdSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"hz");
  p->resetSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"reset");
  
  for(i=0,j=0; i<chCnt; ++i)
  {
    double hz = cmMidiToHz(ccp->chArray[i].midi);
    cmDspSetDefaultDouble(ctx, &p->inst, kGainBaseCcId   + i,   0.0, ccp->chArray[i].gain);
    cmDspSetDefaultUInt(  ctx, &p->inst, p->midiBaseCcId + i,   0,   ccp->chArray[i].midi );
    cmDspSetDefaultDouble(ctx, &p->inst, p->hzBaseCcId   + i,   0.0, hz );
    cmDspSetDefaultUInt(  ctx, &p->inst, p->chBaseCcId   + i,   0,   ccp->chArray[i].ch );
    cmDspSetDefaultBool(  ctx, &p->inst, p->nsflBaseCcId+i, false, false );
    if( ccp->chArray[i].nsFl )
    {
      cmDspSetDefaultDouble(ctx,&p->inst, p->nshzBaseCcId+j, 0.0, hz);
      ++j;
    }
    
  }
  
  
  cmDspSetDefaultSymbol(ctx, &p->inst, kDoneCcId, cmInvalidId );
  cmDspSetDefaultSymbol(ctx, &p->inst, kSelCcId,  cmInvalidId );
  
  return &p->inst;
}

cmDspRC_t _cmDspChCfgFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspChCfg_t* p = (cmDspChCfg_t*)inst;
  cmChCfgFree(&p->ccp);
  return kOkDspRC;
}

cmDspRC_t _cmDspChCfgReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  rc = cmDspApplyAllDefaults(ctx,inst);
  return rc;  
}


cmDspRC_t _cmDspChCfgRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc;
  cmDspChCfg_t* p = (cmDspChCfg_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    if( evt->dstVarId == kSelCcId )
    {
      unsigned selId = cmDspSymbol(inst,kSelCcId);
      
      if( selId == p->resetSymId )
      {
        _cmDspChCfgReset(ctx,inst,evt);
      }
      else
        if( selId == p->hzCmdSymId )
      {
        unsigned i;
        // snd the  hz
        for(i=0; i<p->ccp->chCnt; ++i)
          cmDspSetDouble(ctx,inst,p->hzBaseCcId+i,cmDspDouble(inst,p->hzBaseCcId+i));
        
      }
      else
        if( selId == p->nsCmdSymId )
      {
        cmRptPrintf(ctx->rpt,"ChCfg:NS\n");
        
        unsigned i;
        // send the ns flags
        for(i=0; i<p->ccp->chCnt; ++i)
          cmDspSetBool(ctx,inst,p->nsflBaseCcId+i,p->ccp->chArray[i].nsFl);
        
        // snd the ns hz
        for(i=0; i<p->ccp->nsChCnt; ++i)
          cmDspSetDouble(ctx,inst,p->nshzBaseCcId+i,cmDspDouble(inst,p->nshzBaseCcId+i));
        
        cmDspSetSymbol(ctx,inst,kDoneCcId,p->nsCmdSymId);
        
      }
      else
        
      if( selId == p->printSymId )
      {
        cmRptPrintf(&ctx->cmCtx->rpt,"Channel Cfg Report\n");
        cmChCfgPrint(p->ccp, ctx->rpt );
      }
      else
      {
        if( selId == p->writeSymId )
        {
          unsigned i;
          
          cmRptPrintf(&ctx->cmCtx->rpt,"writing\n");
          
          // copy the gain values into the internal chCfg object ...
          for(i=0; i<p->ccp->chCnt; ++i)
            p->ccp->chArray[i].gain = cmDspDouble(inst,kGainBaseCcId+i);
          
          // ... and write the object
          cmChCfgWrite(p->ccp);
        }
      }
    }
  }
  
  return rc;
}

cmDspClass_t* cmChCfgClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmChCfgDC,ctx,"ChCfg",
  NULL,
  _cmDspChCfgAlloc,
  _cmDspChCfgFree,
  _cmDspChCfgReset,
  NULL,
  _cmDspChCfgRecv,
  NULL,NULL,
  "PP Channel Configuration Object.");
  
  return &_cmChCfgDC;
}


cmDspChordDetect : Detect a predefined chord based on signal gates.
enum
{
  kRsrcCdId,
  kMaxTimeSpanCdId,
  kMinNoteCntCdId,
  kDetectCdId,
  kCountCdId,
  kGateBaseCdId
};

cmDspClass_t _cmChordDetectDC;

typedef struct
{
  cmDspInst_t    inst;
  cmChordDetect* cdp;
  unsigned       rmsBaseCdId;
  unsigned       chCnt; 
  bool*          chGateV;     // chGateV[ chCnt ]
  cmReal_t*      chRmsV;      // chRmsV[ chCnt ]
  unsigned*      chEnaV;      // chEnaV[ chCnt ]
  unsigned       count;
} cmDspChordDetect_t;

cmDspInst_t*  _cmDspChordDetectAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "rsrc",    kRsrcCdId,        0, 0,              kStrzDsvFl   | kReqArgDsvFl, "Channel enable flag array."},
    { "span",    kMaxTimeSpanCdId, 0, 0, kInDsvFl   | kDoubleDsvFl | kOptArgDsvFl, "Max. onset time span."},
    { "notes",   kMinNoteCntCdId,  0, 0, kInDsvFl   | kUIntDsvFl   | kOptArgDsvFl, "Min. note count per chord."},
    { "detect",  kDetectCdId,      0, 0, kOutDsvFl  | kBoolDsvFl,                  "Chord detect flag."},
    { "count",   kCountCdId,       0, 0, kOutDsvFl  | kUIntDsvFl,                  "Count of chords detected since last reset."}
  };
  
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The chord detector must be given a channel enable flags array resource argument .");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  const cmChar_t* rsrc  = va_arg(vl,const cmChar_t*);
  unsigned*       enaV  = NULL;
  unsigned        chCnt   = 0;
  
  if( cmDspRsrcUIntArray( ctx->dspH, &chCnt, &enaV, rsrc, NULL ) != kOkDspRC )
  {
    va_end(vl1);
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The chord detector channel index resource '%s' could not be read.",cmStringNullGuard(rsrc));
    return NULL;
  }
  
  //cmRptPrintf(ctx-&gtrpt,&quotcd %s chs:%i\n&quot,rsrc,chCnt);
  
  unsigned            fixArgCnt   = sizeof(args)/sizeof(args[0]);
  unsigned            argCnt      = fixArgCnt + 2*chCnt;
  unsigned            rmsBaseCdId = kGateBaseCdId + chCnt;
  cmDspVarArg_t       a[ argCnt+1 ];
  unsigned            i;
  cmDspChordDetect_t* p;
  
  // setup the input gate detectors and the output gain args 
  cmDspArgCopy(       a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kGateBaseCdId, chCnt, "gate", kGateBaseCdId, 0, 0, kInDsvFl | kOutDsvFl | kBoolDsvFl,   "Channel gate input and output.");
  cmDspArgSetupN(ctx, a, argCnt, rmsBaseCdId,   chCnt, "rms",  rmsBaseCdId,   0, 0, kInDsvFl | kOutDsvFl | kDoubleDsvFl, "Channel RMS input and output");
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  if((p = cmDspInstAlloc(cmDspChordDetect_t,ctx,classPtr,a,instSymId,id,storeSymId,va_cnt,vl1)) == NULL )
    return NULL;
  
  double   dfltMaxTimeSpanMs = 50.0;
  unsigned dfltMinNoteCnt    = 2;
  cmDspSetDefaultDouble( ctx, &p->inst, kMaxTimeSpanCdId, 0.0,   dfltMaxTimeSpanMs );
  cmDspSetDefaultUInt(   ctx, &p->inst, kMinNoteCntCdId,  0,     dfltMinNoteCnt );
  cmDspSetDefaultBool(   ctx, &p->inst, kDetectCdId,      false, false );
  cmDspSetDefaultUInt(   ctx, &p->inst, kCountCdId,       0,     0 );
  
  for(i=0; i<chCnt; ++i)
  {
    cmDspSetDefaultBool(  ctx, &p->inst, kGateBaseCdId  + i, false, false );
    cmDspSetDefaultDouble(ctx, &p->inst, rmsBaseCdId    + i, 0.0, 0.0 );
  }
  
  p->cdp           = cmChordDetectAlloc( ctx->cmProcCtx, NULL, cmDspSampleRate(ctx), chCnt, cmDspDouble(&p->inst,kMaxTimeSpanCdId), cmDspUInt(&p->inst,kMinNoteCntCdId) );
  p->rmsBaseCdId   = rmsBaseCdId;
  p->chCnt         = chCnt;
  p->chGateV       = cmMemAllocZ(bool,     chCnt);
  p->chRmsV        = cmMemAllocZ(cmReal_t, chCnt);
  p->chEnaV        = enaV;
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspChordDetectFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspChordDetect_t* p = (cmDspChordDetect_t*)inst;
  cmChordDetectFree(&p->cdp);
  cmMemFree(p->chGateV);
  cmMemFree(p->chRmsV);
  return kOkDspRC;
}

cmDspRC_t _cmDspChordDetectReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  rc = cmDspApplyAllDefaults(ctx,inst);
  return rc;  
}

cmDspRC_t _cmDspChordDetectExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t           rc = kOkDspRC;
  cmDspChordDetect_t* p = (cmDspChordDetect_t*)inst;
  
  cmChordDetectExec(p->cdp, cmDspSamplesPerCycle(ctx), p->chGateV, p->chRmsV, p->chCnt );
  
  if( p->cdp->detectFl )
  {
    unsigned i;
    for(i=0; i<p->chCnt; ++i)
    {
      bool fl = p->cdp->chArray[i].chordFl;
      
      cmDspSetBool(   ctx, inst, kGateBaseCdId  + i, fl );
      cmDspSetDouble( ctx, inst, p->rmsBaseCdId + i, fl ? p->cdp->chArray[i].candRMS : 0 );
    }
    
    cmDspSetBool(ctx, inst, kDetectCdId, true);
    cmDspSetUInt(ctx, inst, kCountCdId, cmDspUInt(inst,kCountCdId) + 1 );
  }
  
  cmVOB_Zero(p->chGateV,p->chCnt);
  cmVOR_Zero(p->chRmsV,p->chCnt);
  return rc;
}

cmDspRC_t _cmDspChordDetectRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t           rc = kOkDspRC;
  cmDspChordDetect_t* p = (cmDspChordDetect_t*)inst;
  
  if( kGateBaseCdId <= evt->dstVarId && evt->dstVarId < kGateBaseCdId + p->chCnt )
  {
    unsigned idx = evt->dstVarId - kGateBaseCdId;
    if( p->chEnaV[idx] )
      p->chGateV[ idx ] = cmDsvGetBool(evt->valuePtr);
    
    //cmRptPrintf(ctx-&gtrpt,&quotcd gate:%i e:%i v:%i\n&quot,idx,p-&gtchEnaV[idx],p-&gtchGateV[idx]);
    
  }
  else
    if( p->rmsBaseCdId <= evt->dstVarId && evt->dstVarId < p->rmsBaseCdId + p->chCnt )
  {
    unsigned idx = evt->dstVarId - p->rmsBaseCdId;
    if( p->chEnaV[idx] )
      p->chRmsV[ idx ] = cmDsvGetReal( evt->valuePtr );
  }
  else
  {
    if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
    {
      switch( evt->dstVarId )
      {
      case kMaxTimeSpanCdId:
        cmChordDetectSetSpanMs(p->cdp,cmDspDouble(inst,kMaxTimeSpanCdId));
        break;
        
      case kMinNoteCntCdId:
        p->cdp->minNotesPerChord = cmDspUInt(inst,kMinNoteCntCdId);
        break;
      }
    }
  }
  
  return rc;
}

cmDspClass_t* cmChordDetectClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmChordDetectDC,ctx,"ChordDetect",
  NULL,
  _cmDspChordDetectAlloc,
  _cmDspChordDetectFree,
  _cmDspChordDetectReset,
  _cmDspChordDetectExec,
  _cmDspChordDetectRecv,
  NULL,NULL,
  "Chord detector.");
  
  return &_cmChordDetectDC;
}


cmDspFader : Single channel gate controlled fader.

enum
{
  kTimeFaId,
  kGateFaId,
  kInFaId,
  kGainFaId,
  kOutFaId
};

cmDspClass_t _cmFaderDC;

typedef struct
{
  cmDspInst_t    inst;
  cmFader*       fdp;
} cmDspFader_t;

cmDspInst_t*  _cmDspFaderAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "time", kTimeFaId, 0, 0, kDoubleDsvFl | kOptArgDsvFl,   "Fade time in milliseconds."},
    { "gate", kGateFaId, 0, 0, kInDsvFl     | kBoolDsvFl,     "Gate control signal."},
    { "in",   kInFaId,   0, 0, kInDsvFl     | kAudioBufDsvFl, "Audio input."},
    { "gain", kGainFaId, 0, 0, kOutDsvFl    | kDoubleDsvFl,   "gain output."},
    { "out",  kOutFaId,  0, 0, kOutDsvFl    | kAudioBufDsvFl, "Audio out."},
    { NULL,   0,         0, 0, 0, NULL }
  };
  
  cmDspFader_t* p;
  
  if((p = cmDspInstAlloc(cmDspFader_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl)) == NULL )
    return NULL;
  
  double dfltFadeTimeMs = 100.0;
  cmDspSetDefaultDouble( ctx, &p->inst, kTimeFaId, 0.0,   dfltFadeTimeMs );
  
  p->fdp = cmFaderAlloc(ctx->cmProcCtx, NULL, cmDspSampleRate(ctx), dfltFadeTimeMs );
  
  return &p->inst;
}

cmDspRC_t _cmDspFaderFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspFader_t* p = (cmDspFader_t*)inst;
  cmFaderFree(&p->fdp);
  return kOkDspRC;
}

cmDspRC_t _cmDspFaderReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t     rc = kOkDspRC;
  cmDspFader_t* p  = (cmDspFader_t*)inst;
  rc               = cmDspApplyAllDefaults(ctx,inst);
  cmDspZeroAudioBuf(ctx,inst,kOutFaId);
  cmFaderSetFadeTime(p->fdp,cmDspDouble(inst,kTimeFaId));
  return rc;  
}

cmDspRC_t _cmDspFaderExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc = kOkDspRC;
  cmDspFader_t*     p  = (cmDspFader_t*)inst;
  
  unsigned          n  = cmDspAudioBufSmpCount(ctx,inst,kOutFaId,0);
  cmSample_t*       op = cmDspAudioBuf(ctx,inst,kOutFaId,0);
  const cmSample_t* ip = cmDspAudioBuf(ctx,inst,kInFaId,0);
  
  cmFaderExec(p->fdp,n,cmDspBool(inst,kGateFaId),false,ip,op);
  
  
  cmDspSetDouble(ctx,inst,kGainFaId,p->fdp->gain);
  
  return rc;
}

cmDspRC_t _cmDspFaderRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t     rc = kOkDspRC;
  cmDspFader_t* p  = (cmDspFader_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    if( evt->dstVarId == kTimeFaId )
      cmFaderSetFadeTime(p->fdp,cmDspDouble(inst,kTimeFaId));
  }
  
  return rc;
}

cmDspClass_t* cmFaderClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmFaderDC,ctx,"Fader",
  NULL,
  _cmDspFaderAlloc,
  _cmDspFaderFree,
  _cmDspFaderReset,
  _cmDspFaderExec,
  _cmDspFaderRecv,
  NULL,NULL,
  "Audio fade in/out controller.");
  
  return &_cmFaderDC;
}


cmDspNoteSelect : 'fluxo' gate based logic controller.
enum
{
  kChCntNsId,
  kTrigNsId,
  kDoneNsId,
  kGateBaseNsId
};

enum
{
  kGroupNonNsId,
  kGroup0NsId,
  kGroup1NsId
};

cmDspClass_t _cmNoteSelectDC;

typedef struct
{
  cmDspInst_t    inst;
  unsigned       chCnt; 
  unsigned       rmsBaseNsId;
  unsigned       gate0BaseNsId;
  unsigned       gate1BaseNsId;
  unsigned       gate2BaseNsId;
  unsigned       gate3BaseNsId;
  unsigned       gate4BaseNsId;
  bool*          chGateV;     // chGateV[chCnt]
  cmReal_t*      chRmsV;      // chRmsV[ chCnt ];
  unsigned*      chGroupV;    // chGroupV[ chCnt ] (0=non-chord 1=low/high 2=middle)
  unsigned       count;
  unsigned       doneSymId;
} cmDspNoteSelect_t;

cmDspInst_t*  _cmDspNoteSelectAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "ch_cnt",  kChCntNsId,       0, 0,             kUIntDsvFl   | kReqArgDsvFl, "Channel count."},
    { "trig",    kTrigNsId,        0, 0, kInDsvFl | kBoolDsvFl,                  "Trigger note selection."},
    { "done",    kDoneNsId,        0, 0, kOutDsvFl | kSymDsvFl,                  "Sends 'done' after new set of outputs have been sent."},
  };
  
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The note selector must be given a channel count argument .");
    return NULL;
  }
  
  va_list vl1;
  
  unsigned        CD0chanN = 0;
  unsigned        CD1chanN = 0;
  unsigned*       CD0chan  = NULL;
  unsigned*       CD1chan  = NULL;
  const cmChar_t* CD0rsrc  = "CD0chan";
  const cmChar_t* CD1rsrc  = "CD1chan";
  
  if( cmDspRsrcUIntArray( ctx->dspH, &CD0chanN, &CD0chan, CD0rsrc, NULL ) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The chord detector channel index resource '%s' could not be read.",cmStringNullGuard(CD0rsrc));
    return NULL;
  }
  
  if( cmDspRsrcUIntArray( ctx->dspH, &CD1chanN, &CD1chan, CD1rsrc, NULL ) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The chord detector channel index resource '%s' could not be read.",cmStringNullGuard(CD1rsrc));
    return NULL;
  }
  
  va_copy(vl1,vl);
  
  unsigned chCnt         = va_arg(vl,unsigned);
  unsigned fixArgCnt     = sizeof(args)/sizeof(args[0]);
  unsigned argCnt        = fixArgCnt + 7*chCnt;
  unsigned rmsBaseNsId   = kGateBaseNsId + 1 * chCnt;
  unsigned gate0BaseNsId = kGateBaseNsId + 2 * chCnt;
  unsigned gate1BaseNsId = kGateBaseNsId + 3 * chCnt;
  unsigned gate2BaseNsId = kGateBaseNsId + 4 * chCnt;
  unsigned gate3BaseNsId = kGateBaseNsId + 5 * chCnt;
  unsigned gate4BaseNsId = kGateBaseNsId + 6 * chCnt;
  
  cmDspVarArg_t       a[ argCnt+1 ];
  unsigned            i;
  cmDspNoteSelect_t* p;
  
  // setup the input gate detectors and the output gain args 
  cmDspArgCopy(  a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kGateBaseNsId, chCnt, "gate",   kGateBaseNsId, 0, 0, kInDsvFl  | kBoolDsvFl,   "Channel gate input.");
  cmDspArgSetupN(ctx, a, argCnt, rmsBaseNsId,   chCnt, "rms",    rmsBaseNsId,   0, 0, kInDsvFl  | kDoubleDsvFl, "Channel RMS input");
  cmDspArgSetupN(ctx, a, argCnt, gate0BaseNsId, chCnt, "gate-0", gate0BaseNsId, 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 0 output.");
  cmDspArgSetupN(ctx, a, argCnt, gate1BaseNsId, chCnt, "gate-1", gate1BaseNsId, 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 1 output.");
  cmDspArgSetupN(ctx, a, argCnt, gate2BaseNsId, chCnt, "gate-2", gate2BaseNsId, 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 2 output.");
  cmDspArgSetupN(ctx, a, argCnt, gate3BaseNsId, chCnt, "gate-3", gate3BaseNsId, 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 3 output.");
  cmDspArgSetupN(ctx, a, argCnt, gate4BaseNsId, chCnt, "gate-4", gate4BaseNsId, 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 4 output.");
  
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  if((p = cmDspInstAlloc(cmDspNoteSelect_t,ctx,classPtr,a,instSymId,id,storeSymId,va_cnt,vl1)) == NULL )
    return NULL;
  
  cmDspSetDefaultBool(   ctx, &p->inst, kTrigNsId,      false, false );
  cmDspSetDefaultSymbol( ctx, &p->inst, kDoneNsId,  cmInvalidId );
  
  
  p->rmsBaseNsId   = rmsBaseNsId;
  p->gate0BaseNsId = gate0BaseNsId;
  p->gate1BaseNsId = gate1BaseNsId;
  p->gate2BaseNsId = gate2BaseNsId;
  p->gate3BaseNsId = gate3BaseNsId;
  p->gate4BaseNsId = gate4BaseNsId;
  
  p->chCnt         = chCnt;
  p->chGateV       = cmMemAllocZ(bool,chCnt);
  p->chRmsV        = cmMemAllocZ(cmReal_t,chCnt);
  p->chGroupV      = cmMemAllocZ(unsigned,chCnt);
  p->doneSymId     = cmSymTblRegisterStaticSymbol(ctx->stH,"done");
  
  for(i=0; i<CD0chanN; ++i)
  {
    if( CD0chan[i] >= chCnt )
      cmDspInstErr(ctx,&p->inst,kInvalidArgDspRC,"The chord detector resource array '%s' value  %i is out of range %i.",cmStringNullGuard(CD0rsrc),CD0chan[i],chCnt);
    else
      p->chGroupV[ CD0chan[i] ] = kGroup0NsId;
  }
  
  for(i=0; i<CD1chanN; ++i)
  {
    if( CD1chan[i] >= chCnt )
      cmDspInstErr(ctx,&p->inst,kInvalidArgDspRC,"The chord detector resource array '%s' value  %i is out of range %i.",cmStringNullGuard(CD1rsrc),CD1chan[i],chCnt);
    else
      p->chGroupV[ CD1chan[i] ] = kGroup1NsId;
  }
  
  for(i=0; i<chCnt; ++i)
  {
    cmDspSetDefaultDouble(ctx, &p->inst, rmsBaseNsId+i,   0.0,   0.0 );
    cmDspSetDefaultBool(  ctx, &p->inst, gate0BaseNsId+i, false, false );
    cmDspSetDefaultBool(  ctx, &p->inst, gate1BaseNsId+i, false, false );
    cmDspSetDefaultBool(  ctx, &p->inst, gate2BaseNsId+i, false, false );   
    cmDspSetDefaultBool(  ctx, &p->inst, gate3BaseNsId+i, false, false );
    // the non-chord channel selections should always be on
    cmDspSetDefaultBool(  ctx, &p->inst, gate4BaseNsId+i, false, p->chGroupV[i] == kGroupNonNsId );   
  }
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspNoteSelectFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspNoteSelect_t* p = (cmDspNoteSelect_t*)inst;
  cmMemFree(p->chGateV);
  cmMemFree(p->chRmsV);
  cmMemFree(p->chGroupV);
  return kOkDspRC;
}

cmDspRC_t _cmDspNoteSelectReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  cmDspNoteSelect_t* p = (cmDspNoteSelect_t*)inst;
  rc = cmDspApplyAllDefaults(ctx,inst);
  cmVOR_Zero(p->chRmsV,p->chCnt);
  return rc;  
}


cmDspRC_t _cmDspNoteSelectRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t           rc = kOkDspRC;
  cmDspNoteSelect_t* p = (cmDspNoteSelect_t*)inst;
  
  // store incoming gate  values
  if( kGateBaseNsId <= evt->dstVarId && evt->dstVarId < kGateBaseNsId + p->chCnt )
    p->chGateV[ evt->dstVarId - kGateBaseNsId ] = cmDsvGetBool(evt->valuePtr);
  else
    // store incoming RMS values
  if( p->rmsBaseNsId <= evt->dstVarId && evt->dstVarId < p->rmsBaseNsId + p->chCnt )
    p->chRmsV[ evt->dstVarId - p->rmsBaseNsId ] = cmDsvGetReal( evt->valuePtr );
  else
  {
    // if a chord detection was triggered
    if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC && evt->dstVarId == kTrigNsId )
    {
      unsigned i;
      cmReal_t maxRms = 0;
      unsigned maxIdx = cmInvalidIdx;
      
      for(i=1; i<p->chCnt; ++i)
      {
        // if this channel had an onset and is a possible chord note and is the max RMS chord note
        if(  p->chGroupV[i] != kGroupNonNsId && p->chGateV[i] &&  (maxIdx==cmInvalidIdx || p->chRmsV[i] > maxRms) )
        {
          maxRms = p->chRmsV[i];
          maxIdx = i;
        }
      }
      
      for(i=0; i<p->chCnt; ++i)
      {
        bool fl       = p->chGroupV[i] != kGroupNonNsId;
        bool chosenFl = fl && i==maxIdx;
        bool otherFl  = fl && i!=maxIdx && p->chGateV[i];
        bool cd0Fl    = p->chGroupV[i]==kGroup0NsId && (!otherFl) && (!chosenFl);
        bool cd1Fl    = p->chGroupV[i]==kGroup1NsId && (!otherFl) &&  (!chosenFl);
        
        // gate set 0: set output gate for max chord note
        cmDspSetBool(ctx,inst,p->gate0BaseNsId+i, chosenFl );        
        
        // gate set 1: set output gate for non-max chord notes
        cmDspSetBool(ctx,inst,p->gate1BaseNsId+i, otherFl );                  
        
        // gate set 2: set output gate for non-chord notes
        cmDspSetBool(ctx,inst,p->gate2BaseNsId+i, cd0Fl );
        
        // gate set 3: set output gate for non-chord notes
        cmDspSetBool(ctx,inst,p->gate3BaseNsId+i, cd1Fl);
        
        // gate set 4: set output gate for non-chord notes
        cmDspSetBool(ctx,inst,p->gate4BaseNsId+i, !fl );
        
      }
      
      // send the 'done' symbol to notify the gate receivers that the
      // new set of gates is complete      
      cmDspSetSymbol(ctx,inst,kDoneNsId, p->doneSymId);
      
      // zero the RMS vector
      cmVOR_Zero(p->chRmsV,p->chCnt);
    }
  }
  
  return rc;
}

cmDspClass_t* cmNoteSelectClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmNoteSelectDC,ctx,"NoteSelect",
  NULL,
  _cmDspNoteSelectAlloc,
  _cmDspNoteSelectFree,
  _cmDspNoteSelectReset,
  NULL,
  _cmDspNoteSelectRecv,
  NULL,NULL,
  "Chord detector.");
  
  return &_cmNoteSelectDC;
}


cmDspNetNoteSelect : 'fluxo' transmit gate information over the UDP network.

enum
{
  kTrigNnId,
  kDoneNnId,
  kGateBaseNnId
};


cmDspClass_t _cmNetNoteSelectDC;

#define _cmNetNoteSelPortCnt (10)


typedef struct
{
  cmDspInst_t    inst;
  unsigned       chCnt; 
  unsigned       rmsBaseNnId;
  
  unsigned       gateBaseNNId[ _cmNetNoteSelPortCnt ];
  
  
  bool*     chGateV;             // chGateV[chCnt]
  cmReal_t* chRmsV;              // chRmsV[ chCnt ];
  unsigned* portChCntV;          // portChCntV[ 10 ]
  unsigned* portBaseIdV;         // portBaseIdV[ 10 ]
  unsigned* chPortV;             // chPortV[ chCnt ]
  unsigned* chPortIdxV;          // chPortIdxV[ chCnt ]
  unsigned* ncPortV;             // ncPortV[ chCnt ]
  unsigned* ncPortIdxV;          // ncPortIdxV[ chCnt ]
  
  unsigned       count;
  unsigned       doneSymId;
} cmDspNetNoteSelect_t;

cmDspInst_t*  _cmDspNetNoteSelectAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  
  unsigned        chCnt         = 0;
  const cmChar_t* label         = NULL;
  const cmChar_t* chPortRsrc    = "nsChSelChV";
  unsigned*       chPortV       = NULL;
  const cmChar_t* chPortIdxRsrc = "nsChSelChIdxV";
  unsigned*       chPortIdxV    = NULL;
  const cmChar_t* ncPortRsrc    = "nsNcSelChV";
  unsigned*       ncPortV       = NULL;
  const cmChar_t* ncPortIdxRsrc = "nsNcSelChIdxV";
  unsigned*       ncPortIdxV    = NULL;
  
  unsigned i,j,n;
  
  cmDspVarArg_t args[] =
  {
    { "trig",    kTrigNnId,        0, 0, kInDsvFl  | kBoolDsvFl,                  "Trigger note selection."},
    { "done",    kDoneNnId,        0, 0, kOutDsvFl | kSymDsvFl,                   "Sends 'done' after new set of outputs have been sent."},
  };
  
  
  if( cmDspRsrcUIntArray( ctx->dspH, &chCnt, &chPortV, label = chPortRsrc, NULL ) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kRsrcNotFoundDspRC,"The resource '%s' could not be read.",label);
    return NULL;
  }
  
  if( cmDspRsrcUIntArray( ctx->dspH, &n, &chPortIdxV, label = chPortIdxRsrc, NULL ) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kRsrcNotFoundDspRC,"The resource '%s' could not be read.",label);
    return NULL;
  }
  
  assert(n == chCnt );
  
  if( cmDspRsrcUIntArray( ctx->dspH, &n, &ncPortV, label = ncPortRsrc, NULL ) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kRsrcNotFoundDspRC,"The resource '%s' could not be read.",label);
    return NULL;
  }
  
  assert(n == chCnt );
  
  if( cmDspRsrcUIntArray( ctx->dspH, &n, &ncPortIdxV, label = ncPortIdxRsrc, NULL ) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kRsrcNotFoundDspRC,"The resource '%s' could not be read.",label);
    return NULL;
  }
  
  assert(n == chCnt );
  
  unsigned fixArgCnt     = sizeof(args)/sizeof(args[0]);
  unsigned rmsBaseNnId   = kGateBaseNnId + chCnt;
  
  // get the count of ch's on each port
  unsigned* portChCntV  = cmLhAllocZ( ctx->lhH, unsigned, _cmNetNoteSelPortCnt );
  unsigned* portBaseIdV = cmLhAllocZ( ctx->lhH, unsigned, _cmNetNoteSelPortCnt );
  for(i=0; i<_cmNetNoteSelPortCnt; ++i)
  {
    // get the count of ch's in the ith gate output port
    portChCntV[i] = cmVOU_Count( chPortV, chCnt, i ) + cmVOU_Count( ncPortV, chCnt, i );
    
    // ports 1 and 6 are duplicates of ports 0 and 5
    if( i == 1 || i == 6 )
      portChCntV[i] = portChCntV[i-1];
    
    // set the base port id for this port
    if( i > 0 )
      portBaseIdV[i] = portBaseIdV[i-1] + portChCntV[i-1];
    else
      portBaseIdV[ i ] = rmsBaseNnId + chCnt;
    
  }
  
  
  unsigned argCnt  = fixArgCnt + (2*chCnt) + cmVOU_Sum(portChCntV,_cmNetNoteSelPortCnt );
  cmDspVarArg_t       a[ argCnt+1 ];
  cmDspNetNoteSelect_t* p;
  
  // setup the input gate detectors and the output gain args 
  cmDspArgCopy(  a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kGateBaseNnId, chCnt,   "gate",   kGateBaseNnId, 0, 0, kInDsvFl  | kBoolDsvFl,   "Channel gate input.");
  cmDspArgSetupN(ctx, a, argCnt, rmsBaseNnId,   chCnt,   "rms",    rmsBaseNnId,   0, 0, kInDsvFl  | kDoubleDsvFl, "Channel RMS input");
  
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[0], portChCntV[0], "gate-0", portBaseIdV[0], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 0 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[1], portChCntV[1], "gate-1", portBaseIdV[1], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 1 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[2], portChCntV[2], "gate-2", portBaseIdV[2], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 2 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[3], portChCntV[3], "gate-3", portBaseIdV[3], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 3 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[4], portChCntV[4], "gate-4", portBaseIdV[4], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 4 output.");
  
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[5], portChCntV[5], "gate-5", portBaseIdV[5], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 5 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[6], portChCntV[6], "gate-6", portBaseIdV[6], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 6 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[7], portChCntV[7], "gate-7", portBaseIdV[7], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 7 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[8], portChCntV[8], "gate-8", portBaseIdV[8], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 8 output.");
  cmDspArgSetupN(ctx, a, argCnt, portBaseIdV[9], portChCntV[9], "gate-9", portBaseIdV[9], 0, 0, kOutDsvFl | kBoolDsvFl,   "Channel gate set 9 output.");
  
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  if((p = cmDspInstAlloc(cmDspNetNoteSelect_t,ctx,classPtr,a,instSymId,id,storeSymId,0,vl)) == NULL )
    return NULL;
  
  cmDspSetDefaultBool(   ctx, &p->inst, kTrigNnId,      false, false );
  cmDspSetDefaultSymbol( ctx, &p->inst, kDoneNnId,  cmInvalidId );
  
  
  p->rmsBaseNnId   = rmsBaseNnId;
  
  p->chCnt       = chCnt;
  p->chGateV     = cmMemAllocZ(bool,chCnt);
  p->chRmsV      = cmMemAllocZ(cmReal_t,chCnt);
  p->portChCntV  = portChCntV;
  p->portBaseIdV = portBaseIdV;
  p->doneSymId   = cmSymTblRegisterStaticSymbol(ctx->stH,"done");
  p->chPortV     = chPortV;
  p->chPortIdxV  = chPortIdxV;
  p->ncPortV     = ncPortV;
  p->ncPortIdxV  = ncPortIdxV;
  
  for(i=0; i<chCnt; ++i)
  {
    cmDspSetDefaultBool(  ctx, &p->inst, kGateBaseNnId+i, false, false );
    cmDspSetDefaultDouble(ctx, &p->inst, rmsBaseNnId+i,   0.0,   0.0 );    
  }
  
  for(i=0; i<_cmNetNoteSelPortCnt; ++i)
    for(j=0; j<p->portChCntV[i]; ++j)
      cmDspSetDefaultBool( ctx, &p->inst, p->portBaseIdV[i]+j, false, false );
  
  return &p->inst;
}

cmDspRC_t _cmDspNetNoteSelectFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspNetNoteSelect_t* p = (cmDspNetNoteSelect_t*)inst;
  cmMemFree(p->chGateV);
  cmMemFree(p->chRmsV);
  return kOkDspRC;
}

cmDspRC_t _cmDspNetNoteSelectReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  cmDspNetNoteSelect_t* p = (cmDspNetNoteSelect_t*)inst;
  rc = cmDspApplyAllDefaults(ctx,inst);
  cmVOR_Zero(p->chRmsV,p->chCnt);
  return rc;  
}


cmDspRC_t _cmDspNetNoteSelectRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t           rc = kOkDspRC;
  cmDspNetNoteSelect_t* p = (cmDspNetNoteSelect_t*)inst;
  
  // store incoming gate  values
  if( kGateBaseNnId <= evt->dstVarId && evt->dstVarId < kGateBaseNnId + p->chCnt )
  {
    p->chGateV[ evt->dstVarId - kGateBaseNnId ] = cmDsvGetBool(evt->valuePtr);
    
    //unsigned idx = evt-&gtdstVarId - kGateBaseNnId;
    //cmRptPrintf(ctx-&gtrpt,&quotns gate:%i %i\n&quot,idx, p-&gtchGateV[ idx ]);    
  }
  else
    // store incoming RMS values
  if( p->rmsBaseNnId <= evt->dstVarId && evt->dstVarId < p->rmsBaseNnId + p->chCnt )
  {
    p->chRmsV[ evt->dstVarId - p->rmsBaseNnId ] = cmDsvGetReal( evt->valuePtr );
  }
  else
  {
    // if a chord detection was triggered
    if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC && evt->dstVarId == kTrigNnId )
    {
      unsigned i;
      cmReal_t maxRms = 0;
      unsigned maxIdx = cmInvalidIdx;
      
      for(i=1; i<p->chCnt; ++i)
      {
        // if this channel had an onset and is a possible chord note and is the max RMS chord note
        if(  p->chGateV[i] &&  (maxIdx==cmInvalidIdx || p->chRmsV[i] > maxRms) )
        {
          maxRms = p->chRmsV[i];
          maxIdx = i;
        }
      }
      
      for(i=0; i<p->chCnt; ++i)
      {
        bool chosenFl = i==maxIdx;
        bool otherFl  = i!=maxIdx && p->chGateV[i];
        bool nonFl    = chosenFl==false && otherFl==false;
        unsigned k;
        
        // if this is a chord channel
        if( p->chPortV[i] != cmInvalidIdx )
        {
          // get the port associated with this chord note
          k = p->chPortV[i];
          
          assert( k+1 < _cmNetNoteSelPortCnt );
          assert( p->chPortIdxV[i] < p->portChCntV[k] && p->chPortIdxV[i] < p->portChCntV[k+1] );
          
          // set the chosen and other gate outputs based on the state of 
          // chosenFl and otherFl          
          cmDspSetBool(ctx,inst,p->portBaseIdV[ k   ] + p->chPortIdxV[i],chosenFl);
          cmDspSetBool(ctx,inst,p->portBaseIdV[ k+1 ] + p->chPortIdxV[i],otherFl);            
        }
        
        // all channels have a 'single' note channel
        assert( p->ncPortV[i] != cmInvalidIdx );
        
        k = p->ncPortV[i];
        
        assert( k < _cmNetNoteSelPortCnt );
        assert( p->ncPortIdxV[i] < p->portChCntV[k] );
        
        cmDspSetBool(ctx,inst,p->portBaseIdV[k] + p->ncPortIdxV[i],nonFl);
        
      }
      
      // send the 'done' symbol to notify the gate receivers that the
      // new set of gates is complete      
      cmDspSetSymbol(ctx,inst,kDoneNnId, p->doneSymId);
      
      // zero the RMS vector
      cmVOR_Zero(p->chRmsV,p->chCnt);
    }
  }
  
  return rc;
}

cmDspClass_t* cmNetNoteSelectClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmNetNoteSelectDC,ctx,"NetNoteSelect",
  NULL,
  _cmDspNetNoteSelectAlloc,
  _cmDspNetNoteSelectFree,
  _cmDspNetNoteSelectReset,
  NULL,
  _cmDspNetNoteSelectRecv,
  NULL,NULL,
  "Chord detector.");
  
  return &_cmNetNoteSelectDC;
}



cmDspCombFilt : Comb and Inverse comb filter.
enum
{
  kBypassCfId,
  kMinHzCfId,
  kFbFlCfId,
  kHzCfId,
  kAlphaCfId,
  kInCfId,
  kOutCfId
};

cmDspClass_t _cmCombFiltDC;

typedef struct
{
  cmDspInst_t    inst;
  cmCombFilt*    cfp;
} cmDspCombFilt_t;

cmDspInst_t*  _cmDspCombFiltAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "bypass",kBypassCfId, 0, 0, kInDsvFl | kBoolDsvFl   | kReqArgDsvFl,   "Bypass enable flag." },
    { "minhz", kMinHzCfId,  0, 0,            kDoubleDsvFl | kReqArgDsvFl,   "Minimum frequency limit."},
    { "fb",    kFbFlCfId,   0, 0, kInDsvFl | kBoolDsvFl   | kReqArgDsvFl,   "Configure the filter in feedback mode."},
    { "hz",    kHzCfId,     0, 0, kInDsvFl | kDoubleDsvFl | kReqArgDsvFl,   "Lowest comb frequency." },
    { "alpha", kAlphaCfId,  0, 0, kInDsvFl | kDoubleDsvFl | kReqArgDsvFl,   "Filter coefficent."},
    { "in",    kInCfId,     0, 0, kInDsvFl | kAudioBufDsvFl,                "Audio input."},
    { "out",   kOutCfId,    0, 1, kOutDsvFl| kAudioBufDsvFl,                "Audio out."},
    { NULL,    0,           0, 0, 0, NULL }
  };
  
  cmDspCombFilt_t* p;
  
  if((p = cmDspInstAlloc(cmDspCombFilt_t,ctx,classPtr,args,instSymId,id,storeSymId,va_cnt,vl)) == NULL )
    return NULL;
  
  p->cfp = cmCombFiltAlloc(ctx->cmProcCtx, NULL, 
  cmDspSampleRate(ctx), 
  cmDspBool(&p->inst,kFbFlCfId),
  cmDspDouble(&p->inst,kMinHzCfId), 
  cmDspDouble(&p->inst,kAlphaCfId), 
  cmDspDouble(&p->inst,kHzCfId),
  cmDspBool(&p->inst,kBypassCfId));
  
  return &p->inst;
}

cmDspRC_t _cmDspCombFiltFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspCombFilt_t* p = (cmDspCombFilt_t*)inst;
  cmCombFiltFree(&p->cfp);
  return kOkDspRC;
}

cmDspRC_t _cmDspCombFiltReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspCombFilt_t* p = (cmDspCombFilt_t*)inst;
  rc               = cmDspApplyAllDefaults(ctx,inst);
  cmDspZeroAudioBuf(ctx,inst,kOutCfId);
  
  cmCombFiltInit(p->cfp, 
  cmDspSampleRate(ctx), 
  cmDspBool(inst,kFbFlCfId),
  cmDspDouble(inst,kMinHzCfId), 
  cmDspDouble(inst,kAlphaCfId), 
  cmDspDouble(inst,kHzCfId),
  cmDspBool(inst,kBypassCfId));
  
  return rc;  
}

cmDspRC_t _cmDspCombFiltExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc = kOkDspRC;
  cmDspCombFilt_t*  p  = (cmDspCombFilt_t*)inst;
  
  unsigned          n  = cmDspAudioBufSmpCount(ctx,inst,kOutCfId,0);
  cmSample_t*       op = cmDspAudioBuf(ctx,inst,kOutCfId,0);
  const cmSample_t* ip = cmDspAudioBuf(ctx,inst,kInCfId,0);
  
  cmCombFiltExec(p->cfp,ip,op,n);
  
  return rc;
}

cmDspRC_t _cmDspCombFiltRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t     rc = kOkDspRC;
  cmDspCombFilt_t* p  = (cmDspCombFilt_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    switch( evt->dstVarId )
    {
    case kHzCfId:
      cmCombFiltSetHz(p->cfp,cmDspDouble(inst,evt->dstVarId));
      //printf(&quot%s hz:%f\n&quot,cmSymTblLabel(ctx-&gtstH,inst-&gtsymId),cmDspDouble(inst,evt-&gtdstVarId));
      break;
      
    case kAlphaCfId:
      cmCombFiltSetAlpha(p->cfp,cmDspDouble(inst,evt->dstVarId));
      break;
      
    case kBypassCfId:
      p->cfp->bypassFl = cmDspBool(inst,evt->dstVarId);
      break;
    }
  }
  
  return rc;
}

cmDspClass_t* cmCombFiltClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmCombFiltDC,ctx,"CombFilt",
  NULL,
  _cmDspCombFiltAlloc,
  _cmDspCombFiltFree,
  _cmDspCombFiltReset,
  _cmDspCombFiltExec,
  _cmDspCombFiltRecv,
  NULL,NULL,
  "Comb Filter");
  
  return &_cmCombFiltDC;
}


cmDspScalarOp : Perform arithmetic functions on scalar values.
enum
{
  kPortCntSoId,
  kOpSoId,
  kOutSoId,
  kBaseOpdSoId
};

cmDspClass_t _cmScalarOpDC;
struct cmDspScalarOp_str;

typedef cmDspRC_t (*_cmDspScalarOpFunc_t)(cmDspCtx_t* ctx, cmDspInst_t* inst );


typedef struct cmDspScalar_str
{
  cmDspInst_t          inst;
  _cmDspScalarOpFunc_t func;
  unsigned             inPortCnt;
  bool                 allActiveFl;                  
} cmDspScalarOp_t;

cmDspRC_t _cmDspScalarOpFuncMult(cmDspCtx_t* ctx, cmDspInst_t* inst )
{
  cmDspScalarOp_t* p = (cmDspScalarOp_t*)inst;
  double val = 1.0;
  unsigned i;
  for(i=0; i<p->inPortCnt; ++i)
    val *= cmDspDouble( inst, kBaseOpdSoId+i );
  
  cmDspSetDouble( ctx, inst, kOutSoId, val );
  
  return kOkDspRC;
}

cmDspRC_t _cmDspScalarOpFuncAdd(cmDspCtx_t* ctx, cmDspInst_t* inst )
{
  cmDspScalarOp_t* p = (cmDspScalarOp_t*)inst;
  double val = 0;
  unsigned i;
  for(i=0; i<p->inPortCnt; ++i)
    val += cmDspDouble( inst, kBaseOpdSoId+i );
  
  cmDspSetDouble( ctx, inst, kOutSoId, val );
  return kOkDspRC;
}

// var args syntax: &quot&ltin_port_cnt&gt &ltop_string&gt &ltopd_label_0&gt &ltopd_dflt_val_0&gt  &ltopd_label_1&gt &ltopd_dflt_val_1&gt ...  &ltopd_label_n&gt &ltopd_dflt_val_n&gt 

cmDspInst_t*  _cmDspScalarOpAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =  
  {
    { "cnt",   kPortCntSoId,  0, 0, kUIntDsvFl | kReqArgDsvFl, "Input port count" },
    {  "op",   kOpSoId,       0, 0, kStrzDsvFl | kReqArgDsvFl, "Operation symbol as a string."},
    {  "out",  kOutSoId,      0, 0, kDoubleDsvFl | kOutDsvFl,  "Operation output."},
  };
  
  cmDspScalarOp_t* p;
  
  if( va_cnt < 2 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'ScalarOp' constructor must have a count of input ports and operation identifier string.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  unsigned           inPortCnt = va_arg(vl,unsigned);
  const cmChar_t*    opIdStr   = va_arg(vl,const cmChar_t*);
  unsigned           fixArgCnt = sizeof(args)/sizeof(args[0]);
  unsigned           argCnt    = fixArgCnt + inPortCnt;
  cmDspVarArg_t      a[ argCnt+1 ];
  double             dfltVal[ inPortCnt ];
  unsigned           i;
  _cmDspScalarOpFunc_t fp = NULL;
  bool allActiveFl = false;
  
  // validate the count of input ports
  if( inPortCnt == 0 )
  {
    cmDspClassErr(ctx,classPtr,kVarNotValidDspRC,"The 'ScalarOp' constructor input port argument must be non-zero.");
    goto errLabel;
  }
  
  if( opIdStr != NULL )
  {
    switch( opIdStr[0] )
    {
    case '*':
      fp = _cmDspScalarOpFuncMult;
      break;
    case '+':
      fp = _cmDspScalarOpFuncAdd;
      break;      
    }
    
    // if the second character of the operator string is '$' then all input ports trigger an output
    if( strlen( opIdStr ) > 0 && opIdStr[1]=='$' )
      allActiveFl = true;
    
    
  }
  
  
  
  // validate the operation function
  if( fp == NULL )
  {
    cmDspClassErr(ctx,classPtr,kVarNotValidDspRC,"The 'ScalarOp' constructor operation string id '%s' did not match a known operation.",cmStringNullGuard(opIdStr));
    goto errLabel;
  }
  
  // setup the fixed args
  cmDspArgCopy(  a, argCnt, 0, args, fixArgCnt );
  
  for(i=0; i<inPortCnt; ++i)
  {
    // get the operand label
    const cmChar_t* label = va_arg(vl,const cmChar_t*); 
    
    // get the operand default value
    dfltVal[i]            = va_arg(vl,double);
    
    // setup the arg recd
    cmDspArgSetup(ctx,a + fixArgCnt + i, label, cmInvalidId, kBaseOpdSoId+i,0,0,kDoubleDsvFl|kInDsvFl,"Operand");
  }
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  if((p = cmDspInstAlloc(cmDspScalarOp_t,ctx,classPtr,a,instSymId,id,storeSymId,2,vl1)) == NULL )
    goto errLabel;
  
  for(i=0; i<inPortCnt; ++i)
    cmDspSetDefaultDouble(ctx,&p->inst,kBaseOpdSoId+i,0.0,dfltVal[i]);
  
  p->inPortCnt = inPortCnt;
  p->func      = fp;
  p->allActiveFl = allActiveFl;
  va_end(vl1);
  
  return &p->inst;
  
  errLabel:
  va_end(vl1);
  
  return NULL;
}


cmDspRC_t _cmDspScalarOpReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  rc               = cmDspApplyAllDefaults(ctx,inst);
  return rc;  
}


cmDspRC_t _cmDspScalarOpRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t     rc = kOkDspRC;
  cmDspScalarOp_t* p  = (cmDspScalarOp_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    if( evt->dstVarId == kBaseOpdSoId || p->allActiveFl )
      p->func(ctx,inst);
  }
  
  return rc;
}

cmDspClass_t* cmScalarOpClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmScalarOpDC,ctx,"ScalarOp",
  NULL,
  _cmDspScalarOpAlloc,
  NULL,
  _cmDspScalarOpReset,
  NULL,
  _cmDspScalarOpRecv,
  NULL,NULL,
  "Scalar Operations");
  
  return &_cmScalarOpDC;
}


cmDspGroupSel : Select one group of audio channels from a set of audio channel groups.

enum
{
  kChCntGsId,
  kGroupCntGsId,
  kChsPerGroupGsId,
  kBaseGateGsId,
};

cmDspClass_t _cmGroupSelDC;

typedef struct
{
  cmDspInst_t  inst;
  unsigned     chCnt;
  unsigned     groupCnt;
  cmGroupSel*  gsp;
  unsigned     baseRmsGsId;
  unsigned     baseOutGsId;
} cmDspGroupSel_t;

cmDspInst_t*  _cmDspGroupSelAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspVarArg_t args[] =
  {
    { "chCnt",       kChCntGsId,       0, 0, kUIntDsvFl | kReqArgDsvFl,   "Channel count." },
    { "groupCnt",    kGroupCntGsId,    0, 0, kUIntDsvFl | kReqArgDsvFl,   "Group count." },
    { "chsPerGroup", kChsPerGroupGsId, 0, 0, kUIntDsvFl   | kInDsvFl | kReqArgDsvFl, "Channels per group." }
  };
  
  if( va_cnt < 2 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'GroupSel' constructor must have a channel and group count.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  cmDspGroupSel_t* p;
  unsigned         i;
  unsigned         chCnt       = va_arg(vl,unsigned);
  unsigned         groupCnt    = va_arg(vl,unsigned);
  unsigned         outCnt      = chCnt * groupCnt;
  unsigned         fixArgCnt   = sizeof(args)/sizeof(args[0]);
  unsigned         baseRmsGsId = kBaseGateGsId + chCnt;
  unsigned         baseOutGsId = baseRmsGsId   + chCnt;
  unsigned         argCnt      = baseOutGsId   + outCnt;
  cmDspVarArg_t    a[ argCnt + 1 ];
  
  
  cmDspArgCopy(  a, argCnt, 0, args, fixArgCnt );
  cmDspArgSetupN(ctx, a, argCnt, kBaseGateGsId, chCnt, "gate",  kBaseGateGsId, 0, 0, kInDsvFl  | kBoolDsvFl,   "Channel gate input.");
  cmDspArgSetupN(ctx, a, argCnt, baseRmsGsId,   chCnt, "rms",   baseRmsGsId,   0, 0, kInDsvFl  | kDoubleDsvFl, "Channel RMS input");
  
  for(i=0; i<groupCnt; ++i)
  {
    int  labelCharCnt = 31;
    char label[ labelCharCnt + 1 ];
    snprintf(label,labelCharCnt,"gate-%i",i);
    cmDspArgSetupN(ctx, a, argCnt, baseOutGsId + (i*chCnt), chCnt, label, baseOutGsId + (i*chCnt), 0, 0, kOutDsvFl | kBoolDsvFl,   "Output gates");
  }
  cmDspArgSetupNull( a+argCnt);  // set terminating arg. flag
  
  if((p = cmDspInstAlloc(cmDspGroupSel_t,ctx,classPtr,a,instSymId,id,storeSymId,va_cnt,vl1)) == NULL )
  {
    va_end(vl1);
    return NULL;
  }
  
  p->chCnt       = chCnt;
  p->groupCnt    = groupCnt;
  p->gsp         = cmGroupSelAlloc(ctx->cmProcCtx, NULL, 0, 0, 0 );
  p->baseRmsGsId = baseRmsGsId;
  p->baseOutGsId = baseOutGsId;
  
  for(i=0; i<chCnt; ++i)
  {
    cmDspSetDefaultBool( ctx, &p->inst, kBaseGateGsId, false, false );
    cmDspSetDefaultDouble(ctx,&p->inst, baseRmsGsId,   0.0,   0.0 );
  }
  for(i=0; i<outCnt; ++i)
    cmDspSetDefaultBool( ctx, &p->inst, baseOutGsId, false, false );
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspGroupSelFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspGroupSel_t* p = (cmDspGroupSel_t*)inst;
  cmGroupSelFree(&p->gsp);
  return kOkDspRC;
}

cmDspRC_t _cmDspGroupSelReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspGroupSel_t* p  = (cmDspGroupSel_t*)inst;
  rc               = cmDspApplyAllDefaults(ctx,inst);
  cmGroupSelInit(p->gsp,p->chCnt,p->groupCnt,cmDspUInt(&p->inst,kChsPerGroupGsId));
  return rc;  
}

cmDspRC_t _cmDspGroupSelExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspGroupSel_t* p  = (cmDspGroupSel_t*)inst;
  
  if( cmGroupSelExec(p->gsp) == cmOkRC && p->gsp->updateFl )
  {
    unsigned i,j;
    for(i=0; i<p->groupCnt; ++i)
    {
      cmGroupSelGrp* gp = p->gsp->groupArray + i;
      
      if( gp->releaseFl )
      {
        for(j=0; j<gp->chIdxCnt; ++j)
          cmDspSetBool(ctx,inst,p->baseOutGsId + (i*p->chCnt) + gp->chIdxArray[j], false);
      }
      
      if( gp->createFl )
      {
        for(j=0; j<gp->chIdxCnt; ++j)
          cmDspSetBool(ctx,inst,p->baseOutGsId + (i*p->chCnt) + gp->chIdxArray[j], true);
      }
    }
  }
  
  return rc;
}

cmDspRC_t _cmDspGroupSelRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t        rc = kOkDspRC;
  cmDspGroupSel_t* p  = (cmDspGroupSel_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    if( evt->dstVarId == kChsPerGroupGsId )
      p->gsp->chsPerGroup = cmDspUInt(inst, kChsPerGroupGsId );
    else
      if( kBaseGateGsId <= evt->dstVarId && evt->dstVarId < (kBaseGateGsId + p->chCnt) )
        cmGroupSetChannelGate(p->gsp, evt->dstVarId - kBaseGateGsId, cmDspDouble(inst,evt->dstVarId));
    else
      if( p->baseRmsGsId <= evt->dstVarId && evt->dstVarId < (p->baseRmsGsId + p->chCnt) )
        cmGroupSetChannelRMS(p->gsp, evt->dstVarId - p->baseRmsGsId, cmDspDouble(inst,evt->dstVarId));
  }
  
  return rc;
}

cmDspClass_t* cmGroupSelClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmGroupSelDC,ctx,"GroupSel",
  NULL,
  _cmDspGroupSelAlloc,
  _cmDspGroupSelFree,
  _cmDspGroupSelReset,
  _cmDspGroupSelExec,
  _cmDspGroupSelRecv,
  NULL,NULL,
  "Group selector.");
  
  return &_cmGroupSelDC;
}



cmDspNofM : Select N channels from a set of M channels based on their current gate states.

enum
{
  kInChCntNmId,
  kOutChCntNmId,
  kFadeTimeNmId,
  kBaseGateNmId,
};

cmDspClass_t _cmAudioNofM_DC;

typedef struct
{
  cmDspInst_t   inst;
  unsigned      inChCnt;
  unsigned      outChCnt;
  cmAudioNofM*  nmp;
  unsigned      baseInNmId;
  unsigned      baseOutNmId;
  unsigned      baseGainNmId;
} cmDspAudioNofM_t;

cmDspInst_t*  _cmDspAudioNofM_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 2 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'AudioNofM' constructor must given input and output channel counts.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int           inChCnt     = va_arg(vl,int);
  int           outChCnt    = va_arg(vl,int);
  unsigned      baseInNmId  = kBaseGateNmId + inChCnt;
  unsigned      baseOutNmId = baseInNmId    + inChCnt;
  unsigned      baseGainNmId= baseOutNmId   + outChCnt;
  unsigned      i;
  
  cmDspAudioNofM_t* p = cmDspInstAllocV(cmDspAudioNofM_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "ichs",  kInChCntNmId,  0, 0, kUIntDsvFl   | kReqArgDsvFl,            "Input channel count.",
  1,         "ochs",  kOutChCntNmId, 0, 0, kUIntDsvFl   | kReqArgDsvFl,            "Output channel count.", 
  1,         "time",  kFadeTimeNmId, 0, 0, kDoubleDsvFl | kOptArgDsvFl | kInDsvFl, "Fade time in milliseconds.",
  inChCnt,   "gate",  kBaseGateNmId, 0, 0, kBoolDsvFl   | kInDsvFl,                "Gate inputs.",
  inChCnt,   "in",    baseInNmId,    0, 0, kInDsvFl     | kAudioBufDsvFl,          "Audio input",
  outChCnt,  "out",   baseOutNmId,   0, 1, kOutDsvFl    | kAudioBufDsvFl,          "Audio output",
  outChCnt,  "gain",  baseGainNmId,  0, 0, kOutDsvFl    | kDoubleDsvFl,            "Gain output",
  0 );
  
  cmDspSetDefaultDouble( ctx, &p->inst, kFadeTimeNmId, 0.0, 25.0 );
  
  p->inChCnt      = inChCnt;
  p->outChCnt     = outChCnt;
  p->nmp          = cmAudioNofMAlloc(ctx->cmProcCtx,NULL,0,0,0,0);
  p->baseInNmId   = baseInNmId;
  p->baseOutNmId  = baseOutNmId;
  p->baseGainNmId = baseGainNmId;
  
  for(i=0; i<outChCnt; ++i)
    cmDspSetDefaultDouble( ctx, &p->inst, baseGainNmId + i, 0.0, 0.0 );
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspAudioNofM_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspAudioNofM_t* p = (cmDspAudioNofM_t*)inst;
  cmAudioNofMFree(&p->nmp);
  return kOkDspRC;
}

cmDspRC_t _cmDspAudioNofM_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspAudioNofM_t* p = (cmDspAudioNofM_t*)inst;
  unsigned i;
  if((rc               = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    for(i=0; i<p->outChCnt; ++i)
      cmDspZeroAudioBuf(ctx,inst,p->baseOutNmId+i);
    
    cmAudioNofMInit(p->nmp, cmDspSampleRate(ctx), p->inChCnt, p->outChCnt, cmDspDouble(&p->inst, kFadeTimeNmId ));
  }
  
  return rc;  
}

cmDspRC_t _cmDspAudioNofM_Exec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc = kOkDspRC;
  cmDspAudioNofM_t*  p  = (cmDspAudioNofM_t*)inst;
  unsigned i;
  const cmSample_t* x[ p->inChCnt ];
  cmSample_t*       y[ p->outChCnt ];
  unsigned n = 0;
  
  for(i=0; i<p->inChCnt; ++i)
  {
    if( i==0 )
      n  = cmDspAudioBufSmpCount(ctx,inst,p->baseInNmId+i,0);
    else
    { assert( n == cmDspAudioBufSmpCount(ctx,inst,p->baseInNmId+i,0)); }
    
    x[i] = cmDspAudioBuf(ctx,inst,p->baseInNmId+i,0);
  }
  
  for(i=0; i<p->outChCnt; ++i)
  {
    y[i] = cmDspAudioBuf(ctx,inst,p->baseOutNmId+i,0);
    
    assert( n == cmDspAudioBufSmpCount(ctx,inst,p->baseOutNmId+i,0));
    
    cmVOS_Zero(y[i],n);
  }
  
  cmAudioNofMExec(p->nmp,x,p->inChCnt,y,p->outChCnt,n);
  
  
  for(i=0; i<p->outChCnt; ++i)
  {
    cmAudioNofM_In* ip = p->nmp->outArray[i].list;
    double         v  = 0;
    
    for(; ip != NULL; ip=ip->link)
      v += ip->fader->gain;
    
    cmDspSetDouble(ctx, inst,p->baseGainNmId + i,v );
  }
  
  return rc;
}

cmDspRC_t _cmDspAudioNofM_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t        rc = kOkDspRC;
  cmDspAudioNofM_t* p  = (cmDspAudioNofM_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    if( kBaseGateNmId <= evt->dstVarId && evt->dstVarId <= kBaseGateNmId + p->inChCnt )
      cmAudioNofMSetChannelGate( p->nmp, evt->dstVarId - kBaseGateNmId, cmDspBool(inst,evt->dstVarId) );
  }
  
  return rc;
}

cmDspClass_t* cmAudioNofMClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmAudioNofM_DC,ctx,"AudioNofM",
  NULL,
  _cmDspAudioNofM_Alloc,
  _cmDspAudioNofM_Free,
  _cmDspAudioNofM_Reset,
  _cmDspAudioNofM_Exec,
  _cmDspAudioNofM_Recv,
  NULL,NULL,
  "Audio N of M Switch");
  
  return &_cmAudioNofM_DC;
}




cmDspRingMod : Ring modulator.
enum
{
  kInChCntRmId,
  kBypassRmId,
  kGainRmId,
  kOutRmId,
  kBaseInRmId
};

cmDspClass_t _cmRingModDC;

typedef struct
{
  cmDspInst_t   inst;
  unsigned      inChCnt;
} cmDspRingMod_t;

cmDspInst_t*  _cmDspRingModAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'RingMod' constructor must given an input channel counts.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int           inChCnt     = va_arg(vl,int);
  
  cmDspRingMod_t* p = cmDspInstAllocV(cmDspRingMod_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "ichs",  kInChCntRmId,  0, 0,                kUIntDsvFl   | kReqArgDsvFl, "Input channel count.",
  1,         "bypass",kBypassRmId,   0, 0, kInDsvFl     | kBoolDsvFl   | kOptArgDsvFl, "Bypass enable",
  1,         "gain",  kGainRmId,     0, 0, kInDsvFl     | kDoubleDsvFl | kOptArgDsvFl, "Output gain (default:1.0)",
  1,         "out",   kOutRmId,      0, 1, kOutDsvFl    | kAudioBufDsvFl,              "Audio output",
  inChCnt,   "in",    kBaseInRmId,   0, 0, kInDsvFl     | kAudioBufDsvFl,              "Audio input",
  0 );
  
  cmDspSetDefaultBool(   ctx, &p->inst, kBypassRmId, false, false );
  cmDspSetDefaultDouble( ctx, &p->inst, kGainRmId, 0.0, 1.0 );
  
  p->inChCnt = inChCnt;
  
  va_end(vl1);
  
  return &p->inst;
}


cmDspRC_t _cmDspRingModReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  
  if((rc  = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    cmDspZeroAudioBuf(ctx,inst,kOutRmId);
  }
  
  return rc;  
}

cmDspRC_t _cmDspRingModExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc       = kOkDspRC;
  cmDspRingMod_t*   p        = (cmDspRingMod_t*)inst;
  unsigned          i,j;
  cmSample_t*       y        = cmDspAudioBuf(ctx,inst,kOutRmId, 0);
  const cmSample_t* x0       = cmDspAudioBuf(ctx,inst,kBaseInRmId,0);
  unsigned          n        = cmDspAudioBufSmpCount(ctx,inst,kOutRmId,0);
  double            gain     = cmDspDouble(inst,kGainRmId);
  bool              bypassFl = cmDspBool(inst,kBypassRmId);
  
  for(i=1; i<p->inChCnt; ++i)
  {
    assert( n == cmDspAudioBufSmpCount(ctx,inst,kBaseInRmId+i,0)); 
    
    const cmSample_t* x1 = cmDspAudioBuf(ctx,inst,kBaseInRmId+i,0);
    
    if( bypassFl )
    {
      for(j=0; j<n; ++j)
        y[j] = x0[j] + x1[j];
    }
    else
    {
      for(j=0; j<n; ++j)
        y[j] = x0[j] * x1[j] * gain;
    }
    
    x0 = y;
  }
  
  
  return rc;
}

cmDspRC_t _cmDspRingModRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  return cmDspSetEvent(ctx,inst,evt);
}

cmDspClass_t* cmRingModClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmRingModDC,ctx,"RingMod",
  NULL,
  _cmDspRingModAlloc,
  NULL,
  _cmDspRingModReset,
  _cmDspRingModExec,
  _cmDspRingModRecv,
  NULL,NULL,
  "Ring modulator");
  
  return &_cmRingModDC;
}


cmDspMsgDelay : Delay an arbitrary value.
enum
{
  kMaxCntMdId,
  kDelayMdId,
  kClearMdId,
  kInMdId,
  kOutMdId,
};

cmDspClass_t _cmMsgDelayDC;

typedef struct cmDspMsgDelayEle_str
{
  unsigned              outTimeSmp;
  cmDspValue_t          value;
  struct cmDspMsgDelayEle_str* link;
} cmDspMsgDelayEle_t;

typedef struct
{
  cmDspInst_t         inst;
  unsigned            maxCnt;
  cmDspMsgDelayEle_t* array;   // array[maxCnt];
  cmDspMsgDelayEle_t* avail;
  cmDspMsgDelayEle_t* active;
} cmDspMsgDelay_t;

cmDspInst_t*  _cmDspMsgDelayAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspMsgDelay_t* p = cmDspInstAllocV(cmDspMsgDelay_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "maxcnt",  kMaxCntMdId,   0, 0,              kUIntDsvFl   | kReqArgDsvFl, "Maximum count of elements in the delay",
  1,   "delay",   kDelayMdId,    0, 0, kInDsvFl   | kDoubleDsvFl | kOptArgDsvFl, "Delay time in millisecond.",
  1,   "clear",   kClearMdId,    0, 0, kInDsvFl   | kTypeDsvMask,                "Clear delay",
  1,   "in",      kInMdId,       0, 0, kInDsvFl   | kUIntDsvFl,                "Msg input",
  1,   "out",     kOutMdId,      0, 0, kOutDsvFl  | kUIntDsvFl,                "Msg output",
  0 );
  
  if( p == NULL )
    return NULL;
  
  cmDspSetDefaultDouble( ctx, &p->inst, kDelayMdId, 0.0, 0.0 );
  cmDspSetDefaultBool(   ctx, &p->inst, kOutMdId,   false, false ); 
  
  p->maxCnt = cmDspUInt(&p->inst,kMaxCntMdId);
  p->array  = cmMemAllocZ(cmDspMsgDelayEle_t, p->maxCnt );
  return &p->inst;
}

cmDspRC_t _cmDspMsgDelayFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspMsgDelay_t* p = (cmDspMsgDelay_t*)inst;
  cmMemFree(p->array);
  return kOkDspRC;
}

// insert a value in the delay list
cmDspRC_t  _cmDspMsgDelayInsert( cmDspCtx_t* ctx, cmDspMsgDelay_t* p, unsigned delayTimeSmp, const cmDspValue_t* valPtr )
{
  cmDspRC_t rc = kOkDspRC;
  
  // protect against pre-reset calls
  if( p->avail == NULL && p->active == NULL )
    return kOkDspRC;
  
  // if there are no available delay elements
  if( p->avail == NULL )
    return cmDspInstErr(ctx,&p->inst,kInvalidStateDspRC,"The message delay has exhausted it's internal message store.");
  
  // we only do the simplest kind of copying to avoid allocating memory
  // TODO: fix this  
  if( cmDsvIsMtx(valPtr) ||  cmIsFlag(valPtr->flags,kProxyDsvFl))
    return cmDspInstErr(ctx,&p->inst,kInvalidArgDspRC,"The message delay cannot yet store matrix or proxy types.");
  
  // get a pointer to the next available element
  cmDspMsgDelayEle_t* np = p->avail;
  
  // remove the new ele from the avail list
  p->avail = np->link;
  
  // calc the new ele's exec time
  np->outTimeSmp = ctx->cycleCnt * cmDspSamplesPerCycle(ctx) + delayTimeSmp;
  
  // copy the msg value into the delay line element
  // TODO: this should be a real copy that supports all types  
  np->value      = *valPtr;
  
  cmDspMsgDelayEle_t* ep = p->active;
  cmDspMsgDelayEle_t* pp = NULL;
  
  // if the active list is empty ...
  if( ep == NULL )
  {
    // ... make the avail element the first on the list
    p->active  = np;
    np->link   = NULL;
  }
  else
  {
    // iterate through the list and find the active links which 
    // the new ele falls between based on its execution time    
    while(ep != NULL )
    {
      // ep's exec time is greater than the new ele's exec time
      if( ep->outTimeSmp > np->outTimeSmp )
      {
        
        // insert the new ele in the active list before 'ep'
        if( pp == NULL )
        {
          np->link   = p->active;
          p->active  = np;
        }
        else
        {
          np->link = pp->link;
          pp->link = np;
        }
        break;
      }
      
      pp = ep;
      ep = ep->link;
    }
    
    // if the new element is last on the list
    if( ep == NULL )
    {
      assert(pp != NULL && pp->link == NULL);
      pp->link = np;
      np->link = NULL;
    }
  }
  
  
  return rc;
}

void _cmDspMsgDelayClear(cmDspInst_t* inst )
{
  unsigned i;
  cmDspMsgDelay_t* p  = (cmDspMsgDelay_t*)inst;
  unsigned maxCnt = cmDspUInt(inst,kMaxCntMdId);
  p->active = NULL;
  p->avail  = NULL;
  
  // put all ele's on the available list
  for(i=0; i<maxCnt; ++i)
  {
    p->array[i].link = p->avail;
    p->avail         = p->array + i;      
  }
}

cmDspRC_t _cmDspMsgDelayReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  
  if((rc  = cmDspApplyDefault(ctx,inst,kDelayMdId)) == kOkDspRC )
  {
    _cmDspMsgDelayClear(inst);
  }
  
  return rc;  
}

cmDspRC_t _cmDspMsgDelayExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* e )
{
  cmDspRC_t           rc             = kOkDspRC;
  cmDspMsgDelay_t*    p              = (cmDspMsgDelay_t*)inst;
  unsigned            framesPerCycle = cmDspSamplesPerCycle(ctx);
  unsigned            begTimeSmp     = ctx->cycleCnt * framesPerCycle;
  unsigned            endTimeSmp     = begTimeSmp + framesPerCycle;
  
  while( p->active != NULL )
  {
    
    if( p->active->outTimeSmp >= endTimeSmp )
      break;
    
    cmDspMsgDelayEle_t* ep  = p->active;
    
    // remove the element from the active list and place it on the available list.
    p->active = p->active->link;  // advance the active list
    ep->link  = p->avail;  // put the cur. element on the avail list
    p->avail  = ep;        
    
    // output the element value
    if((rc = cmDspValueSet(ctx,inst,kOutMdId,&ep->value,0)) != kOkDspRC )
      return cmDspInstErr(ctx,inst,rc,"Message delay output failed.");      
    
  }
  
  return rc;
}

cmDspRC_t _cmDspMsgDelayRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t rc = kOkDspRC;
  cmDspMsgDelay_t* p = (cmDspMsgDelay_t*)inst;
  switch( evt->dstVarId )
  {
  case kDelayMdId:
    rc =  cmDspSetEvent(ctx,inst,evt);
    break;
    
  case kClearMdId:
    _cmDspMsgDelayClear(inst);
    break;
    
  case  kInMdId:
    {
      unsigned delayTimeSmp = floor(cmDspDouble(&p->inst,kDelayMdId) * cmDspSampleRate(ctx) / 1000.0);
      rc = _cmDspMsgDelayInsert(ctx,p,delayTimeSmp,evt->valuePtr);
    }
    break;
  }
  
  return rc;
}

cmDspClass_t* cmMsgDelayClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmMsgDelayDC,ctx,"MsgDelay",
  NULL,
  _cmDspMsgDelayAlloc,
  _cmDspMsgDelayFree,
  _cmDspMsgDelayReset,
  _cmDspMsgDelayExec,
  _cmDspMsgDelayRecv,
  NULL,NULL,
  "Message Delay");
  
  return &_cmMsgDelayDC;
}


cmDspLine : Programmable line generator.
enum
{
  kBegLnId,
  kEndLnId,
  kDurLnId,
  kCmdLnId,
  kRateLnId,
  kOutLnId,
};

cmDspClass_t _cmLineDC;

typedef struct cmDspLineEle_str
{
  unsigned              outTimeSmp;
  cmDspValue_t          value;
  struct cmDspLineEle_str* link;
} cmDspLineEle_t;

typedef struct
{
  cmDspInst_t inst;
  unsigned    onSymId;
  unsigned    offSymId;
  unsigned    resetSymId;
  unsigned    curSmpIdx;
  double      phase;
  bool        onFl;
} cmDspLine_t;

cmDspInst_t*  _cmDspLineAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspLine_t* p = cmDspInstAllocV(cmDspLine_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "beg",   kBegLnId,   0, 0, kInDsvFl   | kDoubleDsvFl | kReqArgDsvFl, "Begin value.",
  1,   "end",   kEndLnId,   0, 0, kInDsvFl   | kDoubleDsvFl | kReqArgDsvFl, "End value.",
  1,   "dur",   kDurLnId,   0, 0, kInDsvFl   | kDoubleDsvFl | kReqArgDsvFl, "Duration (ms)",
  1,   "cmd",   kCmdLnId,   0, 0, kInDsvFl   | kSymDsvFl    | kOptArgDsvFl, "Command: on | off | reset",
  1,   "rate",  kRateLnId,  0, 0, kInDsvFl   | kDoubleDsvFl | kOptArgDsvFl, "Output messages per second",
  1,   "out",   kOutLnId,   0, 0, kOutDsvFl  | kDoubleDsvFl,                "Output",
  0 );
  
  if( p == NULL )
    return NULL;
  
  cmDspSetDefaultDouble( ctx, &p->inst, kOutLnId, 0.0, cmDspDefaultDouble(&p->inst,kBegLnId) );
  cmDspSetDefaultDouble(   ctx, &p->inst, kRateLnId, 0.0, 0.0 );
  
  p->onSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"on");
  p->offSymId   = cmSymTblRegisterStaticSymbol(ctx->stH,"off");
  p->resetSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"reset");
  
  
  return &p->inst;
}

cmDspRC_t _cmDspLineFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return kOkDspRC;
}


cmDspRC_t _cmDspLineReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspLine_t* p   = (cmDspLine_t*)inst;
  p->curSmpIdx = 0;
  p->onFl      = false;
  p->phase     = 0;
  return cmDspApplyAllDefaults(ctx,inst);
}

cmDspRC_t _cmDspLineExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* e )
{
  cmDspRC_t    rc  = kOkDspRC;
  cmDspLine_t* p   = (cmDspLine_t*)inst;
  
  if( p->onFl == false )
    return kOkDspRC;
  
  unsigned   sPc       = cmDspSamplesPerCycle(ctx);
  double     beg       = cmDspDouble(inst,kBegLnId);
  double     end       = cmDspDouble(inst,kEndLnId);
  double     rate      = cmDspDouble(inst,kRateLnId);
  double     ms        = cmDspDouble(inst,kDurLnId);
  double     durSmpCnt = floor(ms * cmDspSampleRate(ctx) / 1000);
  double     out       = beg + (end - beg) * p->curSmpIdx / durSmpCnt;
  double     phsMax    = rate==0 ? sPc : cmDspSampleRate(ctx) / rate; 
  
  // we can never output with a period shorter than 
  // the length of one audio buffer  
  if( phsMax < sPc )
    phsMax = sPc;
  
  
  if( beg < end )
  {
    if( out >= end )
    {
      out     = end;
      p->onFl = false;
    }
  }
  else
  {
    if( out <= end )
    {
      out     = end;
      p->onFl = false;
    }
  }
  
  p->phase     += sPc;
  
  if( p->phase >= sPc )
  {
    cmDspSetDouble(ctx,inst,kOutLnId,out);
    p->phase -= sPc;
  }
  
  p->curSmpIdx += sPc;
  
  return rc;
}

cmDspRC_t _cmDspLineRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t rc = kOkDspRC;
  cmDspLine_t* p = (cmDspLine_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    switch( evt->dstVarId )
    {
    case kCmdLnId:
      {
        unsigned symId = cmDspSymbol(inst,kCmdLnId);
        
        if( symId == p->onSymId )
          p->onFl = true;
        else
          if( symId == p->offSymId )
            p->onFl = false;
        else
          if( symId == p->resetSymId )
        {
          p->curSmpIdx = 0;
          p->onFl = true;
        }
        
      }
      break;
    }
  }
  return rc;
}

cmDspClass_t* cmLineClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmLineDC,ctx,"Line",
  NULL,
  _cmDspLineAlloc,
  _cmDspLineFree,
  _cmDspLineReset,
  _cmDspLineExec,
  _cmDspLineRecv,
  NULL,NULL,
  "Line");
  
  return &_cmLineDC;
}



cmDspAdsr : ADSR envelope generator.

enum
{
  kTrigModeAdId,
  kMinLvlAdId,
  kDlyMsAdId,
  kAtkMsAdId,
  kAtkLvlAdId,
  kDcyMsAdId,
  kSusLvlAdId,
  kSusMsAdId,
  kRlsMsAdId,
  kTScaleAdId,
  kAScaleAdId,
  kGateAdId,
  kRmsAdId,
  kOutAdId,
  kCmdAdId
};

cmDspClass_t _cmAdsrDC;

typedef struct
{
  cmDspInst_t  inst;
  cmAdsr*      p;
} cmDspAdsr_t;

cmDspInst_t*  _cmDspAdsrAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspAdsr_t* p = cmDspInstAllocV(cmDspAdsr_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,         "trig",  kTrigModeAdId, 0, 0, kBoolDsvFl   | kInDsvFl | kOptArgDsvFl, "Trigger mode (offset ignored).",
  1,         "min",   kMinLvlAdId,   0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Minimum level (dflt:0.0).",
  1,         "dly",   kDlyMsAdId,    0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Delay milliseconds.",
  1,         "atk",   kAtkMsAdId,    0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Attack milliseconds.",
  1,         "alvl",  kAtkLvlAdId,   0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Attack Level.",
  1,         "dcy",   kDcyMsAdId,    0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Decay milliseconds.",
  1,         "sus",   kSusLvlAdId,   0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Sustain Level.",
  1,         "hold",  kSusMsAdId,    0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Sustain Ms (trig mode only).",
  1,         "rls",   kRlsMsAdId,    0, 0, kDoubleDsvFl | kInDsvFl | kOptArgDsvFl, "Release milliseconds.",
  1,         "tscale",kTScaleAdId,   0, 0, kDoubleDsvFl | kInDsvFl,                "Time scale.",
  1,         "ascale",kAScaleAdId,   0, 0, kDoubleDsvFl | kInDsvFl,                "Amplitude scale.",
  1,         "gate",  kGateAdId,     0, 0, kBoolDsvFl   | kInDsvFl,                "Gate input.",
  1,         "rms",   kRmsAdId,      0, 0, kDoubleDsvFl | kInDsvFl,                "RMS input.",
  1,         "out",   kOutAdId,      0, 0, kDoubleDsvFl | kOutDsvFl,               "Level output.",    
  1,         "cmd",   kCmdAdId,      0, 0, kSymDsvFl    | kInDsvFl,                "Command input.",
  0 );
  
  cmDspSetDefaultBool(   ctx, &p->inst, kTrigModeAdId, false, false );
  cmDspSetDefaultDouble( ctx, &p->inst, kMinLvlAdId,     0.0, 0.0 );  
  cmDspSetDefaultDouble( ctx, &p->inst, kDlyMsAdId,     0.0, 0.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kAtkMsAdId,     0.0, 5.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kAtkLvlAdId,    0.0, 1.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kDcyMsAdId,     0.0, 10.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kSusLvlAdId,    0.0, 0.8 );
  cmDspSetDefaultDouble( ctx, &p->inst, kSusMsAdId,    0.0, 50.0);
  cmDspSetDefaultDouble( ctx, &p->inst, kRlsMsAdId,     0.0, 20.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kTScaleAdId,     0.0, 1.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kAScaleAdId,     0.0, 1.0 );
  cmDspSetDefaultBool(   ctx, &p->inst, kGateAdId,     false,false);
  cmDspSetDefaultDouble( ctx, &p->inst, kRmsAdId,       0.0, 0.0);
  cmDspSetDefaultDouble( ctx, &p->inst, kOutAdId,       0.0, cmDspDouble(&p->inst,kMinLvlAdId));
  
  p->p = cmAdsrAlloc(ctx->cmProcCtx,NULL,false,0,0,0,0,0,0,0,0,0); 
  
  return &p->inst;
}

cmDspRC_t _cmDspAdsrFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspAdsr_t* p = (cmDspAdsr_t*)inst;
  cmAdsrFree(&p->p);
  return kOkDspRC;
}

cmDspRC_t _cmDspAdsrReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc = kOkDspRC;
  cmDspAdsr_t* p  = (cmDspAdsr_t*)inst;
  
  if((rc  = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    bool     trigFl= cmDspBool(   inst, kTrigModeAdId );
    cmReal_t minL  = cmDspDouble( inst, kMinLvlAdId );
    cmReal_t dlyMs = cmDspDouble( inst, kDlyMsAdId );
    cmReal_t atkMs = cmDspDouble( inst, kAtkMsAdId );
    cmReal_t atkL  = cmDspDouble( inst, kAtkLvlAdId );
    cmReal_t dcyMs = cmDspDouble( inst, kDcyMsAdId );
    cmReal_t susMs = cmDspDouble( inst, kSusMsAdId );
    cmReal_t susL  = cmDspDouble( inst, kSusLvlAdId );
    cmReal_t rlsMs = cmDspDouble( inst, kRlsMsAdId );
    
    cmAdsrInit( p->p, cmDspSampleRate(ctx), trigFl, minL, dlyMs, atkMs, atkL, dcyMs, susMs, susL, rlsMs );
    
    
  }
  
  return rc;  
}

cmDspRC_t _cmDspAdsrExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc     = kOkDspRC;
  cmDspAdsr_t* p      = (cmDspAdsr_t*)inst;
  bool         gateFl = cmDspBool( inst, kGateAdId );
  //double       rms    = cmDspDouble(inst,kRmsAdId);
  double       tscale = cmDspDouble(inst,kTScaleAdId);
  double       ascale = cmDspDouble(inst,kAScaleAdId);
  
  // 
  // HACK HACK HACK HACK
  // HACK HACK HACK HACK
  // HACK HACK HACK HACK  see the accompanying hack in cmProc3.c cmAdsrExec()
  // HACK HACK HACK HACK
  // HACK HACK HACK HACK
  //  
  
  //  double db  = rms&lt0.00001 ? -100.0 :  20.0*log10(rms);
  //  double dbMax = -15.0;
  //  double dbMin = -58.0;
  //
  //  db = cmMin(dbMax,cmMax(dbMin,db));
  //  double scale =  (db - dbMin) / (dbMax-dbMin);  
  
  cmReal_t     out    = cmAdsrExec( p->p, cmDspSamplesPerCycle(ctx), gateFl, tscale, ascale );
  
  rc = cmDspSetDouble( ctx, inst, kOutAdId, out );
  
  return rc;
}

cmDspRC_t _cmDspAdsrRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc     = kOkDspRC;
  cmDspAdsr_t* p      = (cmDspAdsr_t*)inst;
  
  if((rc =  cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  if( evt->dstVarId == kCmdAdId )
  {
    cmAdsrReport(p->p,ctx->rpt);
    return rc;
  }
  
  cmReal_t v = cmDspDouble(inst,evt->dstVarId);
  
  switch( evt->dstVarId )
  {
  case kTrigModeAdId:
    p->p->trigModeFl = cmDspBool(inst, kTrigModeAdId);
    break;
    
  case kMinLvlAdId:
    cmAdsrSetLevel(p->p, v, kDlyAdsrId );
    break;
    
  case kDlyMsAdId:
    cmAdsrSetTime(p->p, v, kDlyAdsrId );
    break;
    
  case kAtkMsAdId:
    cmAdsrSetTime(p->p, v, kAtkAdsrId );
    break;
    
  case kAtkLvlAdId:
    cmAdsrSetLevel(p->p, v, kAtkAdsrId );
    break;
    
  case kDcyMsAdId:
    cmAdsrSetTime(p->p, v, kDcyAdsrId );
    break;
    
  case kSusMsAdId:
    cmAdsrSetTime(p->p, v, kSusAdsrId );
    break;
    
  case kSusLvlAdId:
    cmAdsrSetLevel(p->p, v, kSusAdsrId );
    break;
    
  case kRlsMsAdId:
    cmAdsrSetTime(p->p, v, kRlsAdsrId );
    break;
    
    
  }
  
  return rc;
}

cmDspClass_t* cmAdsrClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmAdsrDC,ctx,"Adsr",
  NULL,
  _cmDspAdsrAlloc,
  _cmDspAdsrFree,
  _cmDspAdsrReset,
  _cmDspAdsrExec,
  _cmDspAdsrRecv,
  NULL,NULL,
  "ADSR Envelope Generator");
  
  return &_cmAdsrDC;
}


cmDspAdsr : Audio dynamics compressor.
enum
{
  kBypassCmId,
  kThreshDbCmId,
  kRatioCmId,
  kAtkMsCmId,
  kRlsMsCmId,
  kInGainCmId,
  kOutGainCmId,
  kWndMsCmId,
  kMaxWndMsCmId,
  kInCmId,
  kOutCmId,
  kEnvCmId
};

cmDspClass_t _cmCompressorDC;

typedef struct
{
  cmDspInst_t   inst;
  cmCompressor* p;
} cmDspCompressor_t;

cmDspInst_t*  _cmDspCompressorAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspCompressor_t* p = cmDspInstAllocV(cmDspCompressor_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "bypass",kBypassCmId,   0, 0, kInDsvFl  | kBoolDsvFl    | kReqArgDsvFl, "Bypass enable",
  1,   "thr",   kThreshDbCmId, 0, 0, kInDsvFl  | kDoubleDsvFl  | kReqArgDsvFl, "Threshold in dB.",
  1,   "ratio", kRatioCmId,    0, 0, kInDsvFl  | kDoubleDsvFl  | kReqArgDsvFl, "Ratio numerator.",
  1,   "atk",   kAtkMsCmId,    0, 0, kInDsvFl  | kDoubleDsvFl  | kOptArgDsvFl, "Attack milliseconds",
  1,   "rls",   kRlsMsCmId,    0, 0, kInDsvFl  | kDoubleDsvFl  | kOptArgDsvFl, "Release milliseconds",
  1,   "igain", kInGainCmId,   0, 0, kInDsvFl  | kDoubleDsvFl  | kOptArgDsvFl, "Input gain.",
  1,   "ogain", kOutGainCmId,  0, 0, kInDsvFl  | kDoubleDsvFl  | kOptArgDsvFl, "Makeup Gain",
  1,   "wnd",   kWndMsCmId,    0, 0, kInDsvFl  | kDoubleDsvFl  | kOptArgDsvFl, "RMS window milliseconds.",      
  1,   "maxwnd",kMaxWndMsCmId, 0, 0,             kDoubleDsvFl  | kOptArgDsvFl, "Max. RMS window milliseconds.",
  1,   "in",    kInCmId,       0, 0, kInDsvFl  | kAudioBufDsvFl,               "Audio input",
  1,   "out",   kOutCmId,      0, 1, kOutDsvFl | kAudioBufDsvFl,               "Audio output",
  1,   "env",   kEnvCmId,      0, 0, kOutDsvFl | kDoubleDsvFl,                 "Envelope out",
  0 );
  
  p->p = cmCompressorAlloc(ctx->cmProcCtx, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false );
  
  cmDspSetDefaultBool(   ctx, &p->inst, kBypassCmId, false, false );
  cmDspSetDefaultDouble( ctx, &p->inst, kAtkMsCmId,    0.0, 20.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kRlsMsCmId,    0.0, 20.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kInGainCmId,   0.0, 1.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOutGainCmId,  0.0, 1.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kWndMsCmId,    0.0, 200.0);
  cmDspSetDefaultDouble( ctx, &p->inst, kMaxWndMsCmId, 0.0, 1000.0);
  cmDspSetDefaultDouble( ctx, &p->inst, kEnvCmId,      0.0, 0.0 );
  
  return &p->inst;
}

cmDspRC_t _cmDspCompressorFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspCompressor_t* p = (cmDspCompressor_t*)inst;
  cmCompressorFree(&p->p);
  return kOkDspRC;
}

cmDspRC_t _cmDspCompressorReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspCompressor_t* p  = (cmDspCompressor_t*)inst;
  
  if((rc  = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    cmDspZeroAudioBuf(ctx,inst,kOutCmId);
    
    
    cmReal_t threshDb = cmDspDouble(inst, kThreshDbCmId );
    cmReal_t ratio    = cmDspDouble(inst, kRatioCmId );
    cmReal_t atkMs    = cmDspDouble(inst, kAtkMsCmId );
    cmReal_t rlsMs    = cmDspDouble(inst, kRlsMsCmId );
    cmReal_t inGain   = cmDspDouble(inst, kInGainCmId );
    cmReal_t outGain  = cmDspDouble(inst, kOutGainCmId );    
    cmReal_t wndMs    = cmDspDouble(inst, kWndMsCmId );
    cmReal_t maxWndMs = cmDspDouble(inst, kMaxWndMsCmId);
    bool     bypassFl = cmDspBool(  inst, kBypassCmId );
    cmCompressorInit(p->p,cmDspSampleRate(ctx),cmDspSamplesPerCycle(ctx), inGain, maxWndMs, wndMs, threshDb, ratio, atkMs, rlsMs, outGain, bypassFl );
  }
  
  return rc;  
}

cmDspRC_t _cmDspCompressorExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t          rc   = kOkDspRC;
  cmDspCompressor_t* p    = (cmDspCompressor_t*)inst;
  cmSample_t*        y    = cmDspAudioBuf(ctx,inst,kOutCmId, 0);
  const cmSample_t*  x    = cmDspAudioBuf(ctx,inst,kInCmId,0);
  unsigned           n    = cmDspAudioBufSmpCount(ctx,inst,kOutCmId,0);
  
  if( x != NULL )
  {
    cmCompressorExec(p->p,x,y,n);
    
    rc = cmDspSetDouble( ctx, inst, kEnvCmId, p->p->gain );
  }
  return rc;
}

cmDspRC_t _cmDspCompressorRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t rc;
  cmDspCompressor_t* p  = (cmDspCompressor_t*)inst;
  
  if((rc =  cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  cmReal_t v = cmDspDouble(inst,evt->dstVarId);
  
  switch( evt->dstVarId )
  {
  case kThreshDbCmId:
    cmCompressorSetThreshDb(p->p,v);
    break;
    
  case kRatioCmId:
    p->p->ratio_num = v;
    break;
    
  case kAtkMsCmId:
    cmCompressorSetAttackMs(p->p,v);
    break;
    
  case kRlsMsCmId:
    cmCompressorSetReleaseMs(p->p,v);
    break;
    
  case kInGainCmId:
    p->p->inGain = v;
    break;
    
  case kOutGainCmId:
    p->p->outGain = v;
    break;
    
  case kWndMsCmId:
    cmCompressorSetRmsWndMs(p->p,v);
    break;
    
  case kBypassCmId:
    p->p->bypassFl = cmDspBool(inst,kBypassCmId);
    break;
  }
  
  return rc;
}

cmDspClass_t* cmCompressorClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmCompressorDC,ctx,"Compressor",
  NULL,
  _cmDspCompressorAlloc,
  _cmDspCompressorFree,
  _cmDspCompressorReset,
  _cmDspCompressorExec,
  _cmDspCompressorRecv,
  NULL,NULL,
  "Compressor");
  
  return &_cmCompressorDC;
}


cmDspBiquad : Programmable Biquad EQ filter.
enum
{
  kBypassBqId,
  kModeBqId,
  kF0HzBqId,
  kQBqId,
  kGainDbBqId,
  kInBqId,
  kOutBqId
};

cmDspClass_t _cmBiQuadEqDC;

typedef struct
{
  const cmChar_t* label;
  unsigned        mode;
  unsigned        symbol;
} cmDspBiQuadMap_t;

cmDspBiQuadMap_t _cmDspBiQuadMap[] = 
{
  {"LP",    kLpfBqId, cmInvalidId },
  {"HP",    kHpFBqId, cmInvalidId },
  {"BP",    kBpfBqId, cmInvalidId },
  {"Notch", kNotchBqId, cmInvalidId },
  {"AP",    kAllpassBqId, cmInvalidId },
  {"Peak",  kPeakBqId, cmInvalidId },
  {"LSh",   kLowShelfBqId, cmInvalidId },
  {"HSh",   kHighShelfBqId, cmInvalidId },
  { NULL,   cmInvalidId, cmInvalidId }
};

typedef struct
{
  cmDspInst_t   inst;
  cmBiQuadEq*   p;
} cmDspBiQuadEq_t;

cmDspInst_t*  _cmDspBiQuadEqAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspBiQuadEq_t* p = cmDspInstAllocV(cmDspBiQuadEq_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "bypass",kBypassBqId,   0, 0, kInDsvFl  | kBoolDsvFl    | kReqArgDsvFl, "Bypass enable.",
  1,   "mode",  kModeBqId,     0, 0, kInDsvFl  | kSymDsvFl     | kReqArgDsvFl, "Mode Symbol: LP|HP|BP|AP|Notch|Pk|LSh|HSh.",
  1,   "f0",    kF0HzBqId,     0, 0, kInDsvFl  | kDoubleDsvFl  | kReqArgDsvFl, "Center or edge frequecy in Hz.",
  1,   "Q",     kQBqId,        0, 0, kInDsvFl  | kDoubleDsvFl  | kReqArgDsvFl, "Q",
  1,   "gain",  kGainDbBqId,   0, 0, kInDsvFl  | kDoubleDsvFl  | kOptArgDsvFl, "Gain Db (Pk,LSh,Hsh only)",
  1,   "in",    kInBqId,       0, 0, kInDsvFl  | kAudioBufDsvFl,               "Audio input",
  1,   "out",   kOutBqId,      0, 1, kOutDsvFl | kAudioBufDsvFl,               "Audio output",
  0 );
  
  cmDspSetDefaultDouble( ctx, &p->inst, kGainDbBqId, 0.0, 1.0 );
  
  p->p = cmBiQuadEqAlloc(ctx->cmProcCtx,NULL,0,0,0,0,0,false);
  
  // register the filter mode symbols
  unsigned i;
  for(i=0; _cmDspBiQuadMap[i].label != NULL; ++i)
    if( _cmDspBiQuadMap[i].symbol == cmInvalidId )
      _cmDspBiQuadMap[i].symbol = cmSymTblRegisterStaticSymbol(ctx->stH,_cmDspBiQuadMap[i].label);
  
  return &p->inst;
}

cmDspRC_t _cmDspBiQuadEqFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspBiQuadEq_t* p = (cmDspBiQuadEq_t*)inst;
  cmBiQuadEqFree(&p->p);
  return kOkDspRC;
}

unsigned _cmDspBiQuadEqModeId( cmDspCtx_t* ctx, cmDspInst_t* inst, unsigned modeSymId )
{
  unsigned i;
  
  for(i=0; _cmDspBiQuadMap[i].label!=NULL; ++i)
    if( _cmDspBiQuadMap[i].symbol == modeSymId )
      return _cmDspBiQuadMap[i].mode;
  
  cmDspInstErr(ctx,inst,kVarNotValidDspRC,"The mode string '%s' is not valid.",cmStringNullGuard(cmSymTblLabel(ctx->stH,modeSymId)));
  
  return cmInvalidId;
}

cmDspRC_t _cmDspBiQuadEqReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspBiQuadEq_t* p  = (cmDspBiQuadEq_t*)inst;
  
  if((rc  = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    cmDspZeroAudioBuf(ctx,inst,kOutBqId);
    
    unsigned modeSymId = cmDspSymbol(inst, kModeBqId );
    cmReal_t f0Hz      = cmDspDouble(inst, kF0HzBqId );
    cmReal_t Q         = cmDspDouble(inst, kQBqId );
    cmReal_t gainDb    = cmDspDouble(inst, kGainDbBqId );
    unsigned mode      = _cmDspBiQuadEqModeId(ctx,inst,modeSymId );
    bool     bypassFl  = cmDspBool(inst, kBypassBqId );
    
    if( mode != cmInvalidId )
      cmBiQuadEqInit(p->p, cmDspSampleRate(ctx), mode, f0Hz, Q, gainDb, bypassFl );
  }
  
  return rc;  
}

cmDspRC_t _cmDspBiQuadEqExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t          rc   = kOkDspRC;
  cmDspBiQuadEq_t* p    = (cmDspBiQuadEq_t*)inst;
  cmSample_t*        y    = cmDspAudioBuf(ctx,inst,kOutBqId, 0);
  const cmSample_t*  x    = cmDspAudioBuf(ctx,inst,kInBqId,0);
  unsigned           n    = cmDspAudioBufSmpCount(ctx,inst,kOutBqId,0);
  
  cmBiQuadEqExec(p->p,x,y,n);
  
  return rc;
}

cmDspRC_t _cmDspBiQuadEqRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t rc;
  cmDspBiQuadEq_t* p  = (cmDspBiQuadEq_t*)inst;
  
  if((rc =  cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  cmReal_t        f0Hz    = p->p->f0Hz;
  cmReal_t        Q       = p->p->Q;
  cmReal_t        gainDb  = p->p->gainDb;
  unsigned        mode    = p->p->mode;
  
  if( evt->dstVarId == kModeBqId )
  {
    if((mode = _cmDspBiQuadEqModeId(ctx,inst,cmDspSymbol(inst,kModeBqId) )) == cmInvalidId )
      rc = kVarNotValidDspRC;
  }
  else
  {
    cmReal_t v = cmDspDouble(inst,evt->dstVarId);
    
    switch( evt->dstVarId )
    {
    case kF0HzBqId:
      f0Hz = v;
      break;
      
    case kQBqId:
      Q = v;
      break;
      
    case kGainDbBqId:
      gainDb = v;
      break;
      
    case kBypassBqId:
      p->p->bypassFl = cmDspBool(inst,kBypassBqId);
      break;
      
    }
  }
  
  cmBiQuadEqSet(p->p,mode,f0Hz,Q,gainDb);
  
  return rc;
}

cmDspClass_t* cmBiQuadEqClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmBiQuadEqDC,ctx,"BiQuadEq",
  NULL,
  _cmDspBiQuadEqAlloc,
  _cmDspBiQuadEqFree,
  _cmDspBiQuadEqReset,
  _cmDspBiQuadEqExec,
  _cmDspBiQuadEqRecv,
  NULL,NULL,
  "Bi-Quad EQ Filters");
  
  return &_cmBiQuadEqDC;
}


cmDspDistDs : Guitar style distortion effect.

enum
{
  kBypassDsId,
  kInGainDsId,
  kSrateDsId,
  kBitsDsId,
  kRectDsId,
  kFullDsId,
  kClipDbDsId,
  kOutGainDsId,
  kInDsId,
  kOutDsId
};

cmDspClass_t _cmDistDsDC;

typedef struct
{
  cmDspInst_t  inst;
  cmDistDs*    p;
} cmDspDistDs_t;

cmDspInst_t*  _cmDspDistDsAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  
  cmDspDistDs_t* p = cmDspInstAllocV(cmDspDistDs_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "bypass", kBypassDsId,   0, 0, kInDsvFl  | kBoolDsvFl     | kOptArgDsvFl, "Bypass enable.",
  1,   "igain",  kInGainDsId,   0, 0, kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl, "Input gain.",
  1,   "srate",  kSrateDsId,    0, 0, kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl, "Down-sample rate.",
  1,   "bits",   kBitsDsId,     0, 0, kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl, "Bits per sample",
  1,   "rect",   kRectDsId,     0, 0, kInDsvFl  | kBoolDsvFl     | kOptArgDsvFl, "Rectify flag",
  1,   "full",   kFullDsId,     0, 0, kInDsvFl  | kBoolDsvFl     | kOptArgDsvFl, "1=Full 0=Half rectify",
  1,   "clip",   kClipDbDsId,   0, 0, kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl, "Clip dB",
  1,   "ogain",  kOutGainDsId,  0, 0, kInDsvFl  | kDoubleDsvFl   | kOptArgDsvFl, "Output gain",
  1,   "in",     kInDsId,       0, 0, kInDsvFl  | kAudioBufDsvFl,                "Audio input",
  1,   "out",    kOutDsId,      0, 1, kOutDsvFl | kAudioBufDsvFl,                "Audio output",
  0 );
  
  cmDspSetDefaultBool(   ctx, &p->inst, kBypassDsId, false, false );
  cmDspSetDefaultDouble( ctx, &p->inst, kInGainDsId, 0.0, 1.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kSrateDsId, 0.0, cmDspSampleRate(ctx));
  cmDspSetDefaultDouble( ctx, &p->inst, kBitsDsId,  0.0, 24.0 );
  cmDspSetDefaultBool(   ctx, &p->inst, kRectDsId,  false, false );
  cmDspSetDefaultBool(   ctx, &p->inst, kFullDsId,  false, false );
  cmDspSetDefaultDouble( ctx, &p->inst, kClipDbDsId,    0.0, 0.0 );
  cmDspSetDefaultDouble( ctx, &p->inst, kOutGainDsId, 0.0, 1.0 );
  
  p->p = cmDistDsAlloc(ctx->cmProcCtx, NULL, 0, 0, 0, 0, false, false, 0, 0, false ); 
  
  return &p->inst;
}

cmDspRC_t _cmDspDistDsFree(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspDistDs_t* p = (cmDspDistDs_t*)inst;
  cmDistDsFree(&p->p);
  return kOkDspRC;
}

cmDspRC_t _cmDspDistDsReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspDistDs_t* p = (cmDspDistDs_t*)inst;
  
  if((rc = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    cmDspZeroAudioBuf(ctx,inst,kOutDsId);
    
    bool     bypassFl  = cmDspBool(   inst, kBypassDsId );
    cmReal_t inGain    = cmDspDouble( inst, kInGainDsId );
    cmReal_t downSrate = cmDspDouble( inst, kSrateDsId );
    cmReal_t bits      = cmDspDouble( inst, kBitsDsId );
    bool     rectFl    = cmDspBool(   inst, kRectDsId );
    bool     fullFl    = cmDspBool(   inst, kFullDsId );
    cmReal_t clipDb    = cmDspDouble( inst, kClipDbDsId );
    cmReal_t outGain   = cmDspDouble( inst, kOutGainDsId );
    cmDistDsInit(p->p, cmDspSampleRate(ctx), inGain, downSrate, bits, rectFl, fullFl, clipDb, outGain, bypassFl ); 
  }
  
  return rc;  
}

cmDspRC_t _cmDspDistDsExec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc = kOkDspRC;
  cmDspDistDs_t*    p  = (cmDspDistDs_t*)inst;
  unsigned          n  = cmDspAudioBufSmpCount(ctx,inst,kOutDsId,0);
  cmSample_t*       y  = cmDspAudioBuf(ctx,inst,kOutDsId,0);
  const cmSample_t* x  = cmDspAudioBuf(ctx,inst,kInDsId,0);
  
  cmDistDsExec(p->p,x,y,n);
  
  return rc;
}

cmDspRC_t _cmDspDistDsRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t     rc = kOkDspRC;
  cmDspDistDs_t* p  = (cmDspDistDs_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    switch( evt->dstVarId )
    {
    case kInGainDsId:
      p->p->inGain = cmDspDouble(inst,kInGainDsId);
      break;
      
    case kSrateDsId:        
      p->p->downSrate = cmDspDouble(inst,kSrateDsId);
      break;
      
    case kBitsDsId:
      p->p->bits = cmDspDouble(inst,kBitsDsId);
      break;
      
    case kRectDsId:
      p->p->rectFl = cmDspBool(inst,kRectDsId);
      break;
      
    case kFullDsId:
      p->p->fullFl = cmDspBool(inst,kFullDsId);
      break;
      
    case kClipDbDsId:
      p->p->clipDb = cmDspDouble(inst,kClipDbDsId);
      break;
      
    case kOutGainDsId:
      p->p->outGain = cmDspDouble(inst,kOutGainDsId);
      break;
      
    case kBypassDsId:
      p->p->bypassFl = cmDspBool(inst,kBypassDsId);
      break;
      
    }
  }
  
  return rc;
}

cmDspClass_t* cmDistDsClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmDistDsDC,ctx,"DistDs",
  NULL,
  _cmDspDistDsAlloc,
  _cmDspDistDsFree,
  _cmDspDistDsReset,
  _cmDspDistDsExec,
  _cmDspDistDsRecv,
  NULL,NULL,
  "Distortion and Downsampler");
  
  return &_cmDistDsDC;
}


cmDspDbToLin : Convert decibel units to linear units.
enum
{
  kInDlId,
  kOutDlId
};

cmDspClass_t _cmDbToLinDC;

typedef struct
{
  cmDspInst_t  inst;
} cmDspDbToLin_t;

cmDspInst_t*  _cmDspDbToLinAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  
  cmDspDbToLin_t* p = cmDspInstAllocV(cmDspDbToLin_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "in",   kInDlId,   0, 0, kInDsvFl   | kDoubleDsvFl, "Input",
  1,   "out",  kOutDlId,  0, 0, kOutDsvFl  | kDoubleDsvFl, "Output",
  0 );
  
  cmDspSetDefaultDouble( ctx, &p->inst, kInDlId,   0.0, -1000.0);
  cmDspSetDefaultDouble( ctx, &p->inst, kOutDlId,  0.0, 0.0 );
  
  return &p->inst;
}


cmDspRC_t _cmDspDbToLinReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return cmDspApplyAllDefaults(ctx,inst);
}


cmDspRC_t _cmDspDbToLinRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t     rc = kOkDspRC;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    if( evt->dstVarId == kInDlId )
    {
      double db  = cmMax(0.0,cmMin(100.0,cmDspDouble(inst,kInDlId)));
      double lin = db==0 ? 0.0 : pow(10.0, (db-100.0)/20.0);
      cmDspSetDouble(ctx,inst,kOutDlId,lin);
    }
  }
  
  return rc;
}

cmDspClass_t* cmDbToLinClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmDbToLinDC,ctx,"DbToLin",
  NULL,
  _cmDspDbToLinAlloc,
  NULL,
  _cmDspDbToLinReset,
  NULL,
  _cmDspDbToLinRecv,
  NULL,NULL,
  "dB to Linear converter");
  
  return &_cmDbToLinDC;
}


cmDspNofM2 : Pass an N of M possible inputs to the output.

// Pass any N of M inputs
enum
{
  kInChCntNoId,
  kOutChCntNoId,
  kXfadeMsNoId,
  kCmdNoId,
  kSelIdxNoId,
  kBaseGateNoId
};

cmDspClass_t _cmNofM_DC;

typedef struct
{
  cmDspInst_t   inst;
  
  unsigned      iChCnt;
  unsigned      oChCnt;
  
  unsigned*     map;              // map[ oChCnt ]
  cmXfader**    xf;               // xf[ oChCnt ];
  
  unsigned      cfgSymId;
  unsigned      onSymId;
  unsigned      offSymId;
  bool          verboseFl;
  
  unsigned      baseBaseInNoId;   // first data input port id
  unsigned      baseBaseOutNoId;  // first data output port id
  
  unsigned      baseInFloatNoId;
  unsigned      baseInBoolNoId;
  unsigned      baseInSymNoId;
  unsigned      baseInAudioNoId;
  
  unsigned      baseOutFloatNoId;
  unsigned      baseOutBoolNoId;
  unsigned      baseOutSymNoId;
  unsigned      baseOutAudioNoId;
  
  bool printFl;
  
} cmDspNofM_t;

cmDspInst_t*  _cmDspNofM_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 2 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'NofM' constructor must given input and output channel counts.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int      iChCnt           = va_arg(vl,int);
  int      oChCnt           = va_arg(vl,int);
  
  if( oChCnt > iChCnt )
  {
    va_end(vl1);
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'NofM' output count must be less than or equal to the input count.");
    return NULL;
  }
  
  
  unsigned baseInFloatNoId  = kBaseGateNoId    + iChCnt;
  unsigned baseInBoolNoId   = baseInFloatNoId  + iChCnt;
  unsigned baseInSymNoId    = baseInBoolNoId   + iChCnt;
  unsigned baseInAudioNoId  = baseInSymNoId    + iChCnt;
  unsigned baseOutFloatNoId = baseInAudioNoId  + iChCnt;
  unsigned baseOutBoolNoId  = baseOutFloatNoId + oChCnt;
  unsigned baseOutSymNoId   = baseOutBoolNoId  + oChCnt;
  unsigned baseOutAudioNoId = baseOutSymNoId   + oChCnt;
  
  unsigned      i;
  
  cmDspNofM_t* p = cmDspInstAllocV(cmDspNofM_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "ichs",  kInChCntNoId,     0, 0, kUIntDsvFl     | kReqArgDsvFl,"Input channel count.",
  1,         "ochs",  kOutChCntNoId,    0, 0, kUIntDsvFl     | kReqArgDsvFl,"Output channel count.", 
  1,         "ms",    kXfadeMsNoId,     0, 0, kDoubleDsvFl   | kInDsvFl | kOptArgDsvFl,"Audio Cross-fade time in milliseconds.",
  1,         "cmd",   kCmdNoId,         0, 0, kSymDsvFl      | kInDsvFl,    "Command input.",
  1,         "seli",  kSelIdxNoId,      0, 0, kUIntDsvFl     | kInDsvFl,    "Enable gate at index.",
  iChCnt,    "sel",   kBaseGateNoId,    0, 0, kBoolDsvFl     | kInDsvFl,    "Selector Gate inputs.",
  iChCnt,    "f-in",  baseInFloatNoId,  0, 0, kDoubleDsvFl   | kInDsvFl,    "Float input",
  iChCnt,    "b-in",  baseInBoolNoId,   0, 0, kBoolDsvFl     | kInDsvFl,    "Bool input",
  iChCnt,    "s-in",  baseInSymNoId,    0, 0, kSymDsvFl      | kInDsvFl,    "Symbol input",
  iChCnt,    "a-in",  baseInAudioNoId,  0, 0, kAudioBufDsvFl | kInDsvFl,    "Audio input",
  oChCnt,    "f-out", baseOutFloatNoId, 0, 0, kDoubleDsvFl   | kOutDsvFl,   "Float output",
  oChCnt,    "b-out", baseOutBoolNoId,  0, 0, kBoolDsvFl     | kOutDsvFl,   "Bool output",
  oChCnt,    "s-out", baseOutSymNoId,   0, 0, kSymDsvFl      | kOutDsvFl,   "Symbol output",
  oChCnt,    "a-out", baseOutAudioNoId, 0, 1, kAudioBufDsvFl | kOutDsvFl,   "Audio output",
  0 );
  
  p->iChCnt     = iChCnt;
  p->oChCnt     = oChCnt;
  p->map        = cmMemAllocZ(unsigned,oChCnt);
  p->xf         = cmMemAllocZ(cmXfader*,oChCnt);
  p->cfgSymId   = cmSymTblRegisterStaticSymbol(ctx->stH,"cfg");
  p->onSymId    = cmSymTblRegisterStaticSymbol(ctx->stH,"on");
  p->offSymId   = cmSymTblRegisterStaticSymbol(ctx->stH,"off");
  
  p->verboseFl  = false;
  
  p->baseBaseInNoId   =  baseInFloatNoId;
  p->baseBaseOutNoId  =  baseOutFloatNoId;
  
  p->baseInFloatNoId  =  baseInFloatNoId; 
  p->baseInBoolNoId   =  baseInBoolNoId;
  p->baseInSymNoId    =  baseInSymNoId;
  p->baseInAudioNoId  =  baseInAudioNoId;
  p->baseOutFloatNoId =  baseOutFloatNoId;
  p->baseOutBoolNoId  =  baseOutBoolNoId;
  p->baseOutSymNoId   =  baseOutSymNoId;
  p->baseOutAudioNoId =  baseOutAudioNoId;
  
  for(i=0; i<oChCnt; ++i)
  {
    cmDspSetDefaultDouble( ctx, &p->inst, baseOutFloatNoId + i, 0.0, 0.0 );
    cmDspSetDefaultBool(   ctx, &p->inst, baseOutBoolNoId  + i, false, false );
    cmDspSetDefaultSymbol( ctx, &p->inst, baseOutSymNoId   + i, cmInvalidId );
    p->xf[i] = cmXfaderAlloc( ctx->cmProcCtx, NULL, 0, 0, 0 );
  }
  
  cmDspSetDefaultDouble( ctx, &p->inst, kXfadeMsNoId, 0.0, 15.0 );
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspNofM_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspNofM_t* p = (cmDspNofM_t*)inst;
  unsigned i;
  for(i=0; i<p->oChCnt; ++i)
    cmXfaderFree(&p->xf[i]);
  
  cmMemFree(p->map);
  cmMemFree(p->xf);
  return kOkDspRC;
}

cmDspRC_t _cmDspNofM_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t        rc = kOkDspRC;
  cmDspNofM_t* p = (cmDspNofM_t*)inst;
  unsigned i;
  if((rc   = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    for(i=0; i<p->oChCnt; ++i)
    {
      cmDspZeroAudioBuf(ctx,inst,p->baseOutAudioNoId+i);
      p->map[i] = cmInvalidIdx;
      double xfadeMs = cmDspDouble(inst,kXfadeMsNoId);
      cmXfaderInit(p->xf[i], cmDspSampleRate(ctx), p->iChCnt, xfadeMs );
    }
  }
  
  return rc;  
}

cmDspRC_t _cmDspNofM_Exec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc = kOkDspRC;
  
  cmDspNofM_t* p  = (cmDspNofM_t*)inst;
  unsigned     i;
  const cmSample_t* x[ p->iChCnt ];
  
  for(i=0; i<p->iChCnt; ++i)
    x[i] = cmDspAudioBuf(ctx,inst,p->baseInAudioNoId + i ,0);
  
  // for each valid p-&gtmap[] element
  for(i=0; i<p->oChCnt; ++i)
  {
    cmSample_t* y = cmDspAudioBuf(ctx,inst,p->baseOutAudioNoId+ i,0);
    unsigned    n = cmDspAudioBufSmpCount(ctx,inst,p->baseOutAudioNoId+i,0);
    
    if( y != NULL )
    {
      y = cmVOS_Zero(y,n);      
      cmXfaderExecAudio(p->xf[i],n,NULL,p->iChCnt,x,y);
    }
  }
  
  return rc;
}

cmDspRC_t _cmDspNofM_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDspNofM_t* p  = (cmDspNofM_t*)inst;
  unsigned     i,j;
  
  assert( evt->dstVarId < p->baseBaseOutNoId );
  
  // store the incoming value
  if((rc = cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  // if this is a fade time
  if( evt->dstVarId == kXfadeMsNoId )
  {
    for(i=0; i<p->oChCnt; ++i)
      cmXfaderSetXfadeTime( p->xf[i], cmDspDouble(inst,kXfadeMsNoId) );   
    
    return rc;
  }
  
  // if this is an index selector
  if( kSelIdxNoId == evt->dstVarId )
  {
    unsigned idx;
    if((idx = cmDspUInt(inst,kSelIdxNoId)) >= p->iChCnt )
      rc = cmDspInstErr(ctx,inst,kInvalidArgDspRC,"The selection index ('%i') is out of the channel range, (%i).",idx,p->iChCnt); 
    else
    {
      cmDspSetBool( ctx, inst, kBaseGateNoId+idx , true );
      
      if( p->verboseFl )
        cmRptPrintf(ctx->rpt,"nom seli:%i\n",idx);
    }
    return rc;
  }
  
  // NOTE: the internal state DOES NOT CHANGE until a message arrives
  // on the 'cmd' port  
  
  // if anything arrives on the command port - then read the gate states and rebuild the map
  if( evt->dstVarId == kCmdNoId )
  {
    
    unsigned cmdSymId = cmDspSymbol(inst,kCmdNoId);
    
    // if cmdSymId == 'on' then turn on all selection gates
    if(cmdSymId  == p->onSymId )
    {
      for(i=0; i<p->oChCnt; ++i)
        cmDspSetBool(ctx,inst,kBaseGateNoId+i,true);
    }
    else
      // if cmdSymId == 'off' then turn off all selection  gates
    if( cmdSymId == p->offSymId )
    {
      if( p->verboseFl )
        cmRptPrintf(ctx->rpt,"nom: off\n");
      
      for(i=0; i<p->oChCnt; ++i)
        cmDspSetBool(ctx,inst,kBaseGateNoId+i,false);
    }
    else
      // cmdSymId == 'print' then print the in/out map[]
    if( cmdSymId == cmSymTblId(ctx->stH,"print") )
    {
      for(i=0; i<p->oChCnt; ++i)
        cmRptPrintf(ctx->rpt,"%i:%i ",i,p->map[i]);
      cmRptPrintf(ctx->rpt,"\n");
      cmRptPrintf(ctx->rpt,"%i usecs\n",ctx->execDurUsecs);
      p->printFl = !p->printFl;
    }
    
    if( p->verboseFl )
      cmRptPrintf(ctx->rpt,"Nom: %s ", inst->symId != cmInvalidId ? cmSymTblLabel(ctx->stH,inst->symId) : "");
    
    // 5/26
    if( p->iChCnt == p->oChCnt )
      cmVOU_Fill(p->map,p->oChCnt,cmInvalidIdx);
    
    // for each input 
    for(i=0,j=0; i<p->iChCnt; ++i)
    {
      
      // if this input is switched on
      if( cmDspBool(inst,kBaseGateNoId+i) )
      {
        if( j >= p->oChCnt )
        {
          cmDspInstErr(ctx,inst,kVarNotValidDspRC,"To many inputs have been turned on for %i outputs.",p->oChCnt);
          break;
        }
        
        // assign input i to output j
        p->map[j] = i;
        
        // fade in ch i and fade out all others
        cmXfaderSelectOne(p->xf[j],i);
        
        ++j;
        
        if( p->verboseFl )
          cmRptPrintf(ctx->rpt,"%i ",i);
        
      }
      else  // 5/26
      {
        if( p->iChCnt == p->oChCnt )
          ++j;
      }
    }
    
    // deselect all other output channels
    //for(; j&ltp-&gtoChCnt; ++j)
    //{
    //  p-&gtmap[j] = cmInvalidIdx;
    //  cmXfaderAllOff(p-&gtxf[j]);
    //}
    // 5/26    
    if( p->iChCnt == p->oChCnt )
    {
      for(j=0; j<p->oChCnt; ++j)
        if( p->map[j] == cmInvalidIdx )
          cmXfaderAllOff(p->xf[j]);
    }
    else
    {
      for(; j<p->oChCnt; ++j)
      {
        p->map[j] = cmInvalidIdx;
        cmXfaderAllOff(p->xf[j]);
      }
    }
    
    
    if( p->verboseFl )
      cmRptPrintf(ctx->rpt,"\n");
    
    // zero the audio buffers of unused output channels
    for(i=0; i<p->oChCnt; ++i)
      if( p->map[i] == cmInvalidIdx )
        cmDspZeroAudioBuf(ctx,inst,p->baseOutAudioNoId+i); 
    
  }
  
  
  // if this is an input data event
  if( p->baseBaseInNoId <= evt->dstVarId && evt->dstVarId < p->baseBaseOutNoId  )
  {
    // get the input channel this event occurred on
    unsigned iChIdx = (evt->dstVarId - p->baseBaseInNoId) % p->iChCnt;
    
    // is iChIdx mapped to an output ...
    for(i=0; i<p->oChCnt; ++i)
      if( p->map[i] == iChIdx )
        break;
    
    // ... no - nothing else to do
    if( i==p->oChCnt )
      return kOkDspRC;
    
    // ... yes set the output ...
    
    // double
    if( p->baseInFloatNoId <= evt->dstVarId && evt->dstVarId < p->baseInFloatNoId + p->iChCnt )
    {
      cmDspSetDouble(ctx,inst,p->baseOutFloatNoId + i, cmDspDouble(inst,evt->dstVarId));
    }
    else
      
    // bool  
    if( p->baseInBoolNoId <= evt->dstVarId && evt->dstVarId < p->baseInBoolNoId + p->iChCnt )
    {
      cmDspSetBool(ctx,inst,p->baseOutBoolNoId + i, cmDspBool(inst,evt->dstVarId));
      if(p->printFl)
        cmRptPrintf(ctx->rpt,"%i %i\n",p->baseOutBoolNoId + i, cmDspBool(inst,evt->dstVarId));
    }
    else
      
    // symbol
    if( p->baseInSymNoId <= evt->dstVarId && evt->dstVarId < p->baseInSymNoId + p->iChCnt )
      cmDspSetSymbol(ctx,inst,p->baseOutSymNoId + i, cmDspSymbol(inst,evt->dstVarId));
    
  }
  
  
  return rc;
}

cmDspClass_t* cmNofMClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmNofM_DC,ctx,"NofM",
  NULL,
  _cmDspNofM_Alloc,
  _cmDspNofM_Free,
  _cmDspNofM_Reset,
  _cmDspNofM_Exec,
  _cmDspNofM_Recv,
  NULL,NULL,
  "N of M Switch");
  
  return &_cmNofM_DC;
}



cmDsp1ofN : Pass 1 or N possible inputs to the output.

enum
{
  kInChCnt1oId,
  kInChIdx1oId,
  kOutFloat1oId,
  kOutBool1oId,
  kOutSym1oId,
  kOutAudio1oId,
  kBaseInFloat1oId
};

cmDspClass_t _cm1ofN_DC;

typedef struct
{
  cmDspInst_t   inst;
  
  unsigned      iChCnt;
  unsigned      oChCnt;
  
  unsigned      iChIdx;
  
  unsigned      baseBaseIn1oId;   // first data input port id
  
  unsigned      baseInFloat1oId;
  unsigned      baseInBool1oId;
  unsigned      baseInSym1oId;
  unsigned      baseInAudio1oId;
  
  unsigned      baseOutFloat1oId;
  unsigned      baseOutBool1oId;
  unsigned      baseOutSym1oId;
  unsigned      baseOutAudio1oId;
  
  
} cmDsp1ofN_t;

cmDspInst_t*  _cmDsp1ofN_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The '1ofN' constructor must given input channel count.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int      iChCnt           = va_arg(vl,int);
  
  unsigned baseInFloat1oId  = kBaseInFloat1oId;
  unsigned baseInBool1oId   = baseInFloat1oId  + iChCnt;
  unsigned baseInSym1oId    = baseInBool1oId   + iChCnt;
  unsigned baseInAudio1oId  = baseInSym1oId    + iChCnt;
  
  cmDsp1ofN_t* p = cmDspInstAllocV(cmDsp1ofN_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "ichs",  kInChCnt1oId,     0, 0, kUIntDsvFl   | kReqArgDsvFl,"Input channel count.",
  1,         "chidx", kInChIdx1oId,     0, 0, kUIntDsvFl   | kReqArgDsvFl | kInDsvFl,    "Input channel selector index.",
  1,         "f-out", kOutFloat1oId,    0, 0, kDoubleDsvFl | kOutDsvFl,   "Float output",
  1,         "b-out", kOutBool1oId,     0, 0, kBoolDsvFl   | kOutDsvFl,   "Bool output",
  1,         "s-out", kOutSym1oId,      0, 0, kSymDsvFl    | kOutDsvFl,   "Symbol output",
  1,         "a-out", kOutAudio1oId,    0, 1, kAudioBufDsvFl  | kOutDsvFl,   "Audio output",
  iChCnt,    "f-in",  baseInFloat1oId,  0, 0, kDoubleDsvFl | kInDsvFl,    "Float input",
  iChCnt,    "b-in",  baseInBool1oId,   0, 0, kBoolDsvFl   | kInDsvFl,    "Bool input",
  iChCnt,    "s-in",  baseInSym1oId,    0, 0, kSymDsvFl    | kInDsvFl,    "Symbol input",
  iChCnt,    "a-in",  baseInAudio1oId,  0, 0, kAudioBufDsvFl  | kInDsvFl,    "Audio input",
  0 );
  
  p->iChCnt     = iChCnt;
  
  p->baseBaseIn1oId   =  kBaseInFloat1oId;
  
  p->baseInFloat1oId  =  baseInFloat1oId; 
  p->baseInBool1oId   =  baseInBool1oId;
  p->baseInSym1oId    =  baseInSym1oId;
  p->baseInAudio1oId  =  baseInAudio1oId;
  
  
  cmDspSetDefaultDouble( ctx, &p->inst, kOutFloat1oId, 0.0, 0.0 );
  cmDspSetDefaultBool(   ctx, &p->inst, kOutBool1oId, false, false );
  cmDspSetDefaultSymbol( ctx, &p->inst, kOutSym1oId, cmInvalidId );
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDsp1ofN_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return kOkDspRC;
}

cmDspRC_t _cmDsp1ofN_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc = kOkDspRC;
  
  if((rc   = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    cmDspZeroAudioBuf(ctx,inst,kOutAudio1oId);
  }
  
  return rc;  
}

cmDspRC_t _cmDsp1ofN_Exec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc     = kOkDspRC;
  cmDsp1ofN_t*      p      = (cmDsp1ofN_t*)inst;
  unsigned          iChIdx = cmDspUInt(inst,kInChIdx1oId);
  cmSample_t*       dp     = cmDspAudioBuf(ctx,inst,kOutAudio1oId,0);
  const cmSample_t* sp     = cmDspAudioBuf(ctx,inst,p->baseInAudio1oId + iChIdx ,0);
  unsigned          n      = cmDspAudioBufSmpCount(ctx,inst,kOutAudio1oId,0);
  
  if( dp != NULL )
  {
    if( sp == NULL )
      cmVOS_Zero(dp,n);
    else
      cmVOS_Copy(dp,n,sp);
  }
  
  return rc;
}

cmDspRC_t _cmDsp1ofN_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDsp1ofN_t* p  = (cmDsp1ofN_t*)inst;
  
  
  // ignore out of range input channel
  if( evt->dstVarId == kInChIdx1oId && cmDsvGetUInt(evt->valuePtr) >= p->iChCnt )
  {
    cmDspInstErr(ctx,inst,kVarNotValidDspRC,"The selector channel index %i is out of range.",cmDsvGetUInt(evt->valuePtr));
    return kOkDspRC;
  } 
  
  // store the incoming value
  if((rc = cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  
  // if this is an input data event
  if( p->baseBaseIn1oId <= evt->dstVarId  )
  {
    // get the input channel this event occurred on
    unsigned iChIdx = (evt->dstVarId - p->baseBaseIn1oId) % p->iChCnt;
    
    // if the event did not arrive on the selected input channel - there is nothing else to do
    if( iChIdx != cmDspUInt(inst,kInChIdx1oId) )
      return kOkDspRC;
    
    // The event arrived on the input channel - send it out the output
    
    // double
    if( p->baseInFloat1oId <= evt->dstVarId && evt->dstVarId < p->baseInFloat1oId + p->iChCnt )
      cmDspSetDouble(ctx,inst,kOutFloat1oId, cmDspDouble(inst,evt->dstVarId));
    else
      
    // bool  
    if( p->baseInBool1oId <= evt->dstVarId && evt->dstVarId < p->baseInBool1oId + p->iChCnt )
      cmDspSetBool(ctx,inst,kOutBool1oId, cmDspBool(inst,evt->dstVarId));
    else
      
    // symbol
    if( p->baseInSym1oId <= evt->dstVarId && evt->dstVarId < p->baseInSym1oId + p->iChCnt )
      cmDspSetSymbol(ctx,inst,kOutSym1oId, cmDspSymbol(inst,evt->dstVarId));
    
  }
  
  
  return rc;
}

cmDspClass_t* cm1ofNClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cm1ofN_DC,ctx,"1ofN",
  NULL,
  _cmDsp1ofN_Alloc,
  _cmDsp1ofN_Free,
  _cmDsp1ofN_Reset,
  _cmDsp1ofN_Exec,
  _cmDsp1ofN_Recv,
  NULL,NULL,
  " 1 of N Switch");
  
  return &_cm1ofN_DC;
}


cmDsp1Up : Send 'true' on the selected channel and 'false' on the deselected channel.

// Send a 'true' out on the selected channel.
// Send a 'false' out on the deselected channel.
enum
{
  kChCnt1uId,
  kSel1uId,
  kBaseOut1uId
};

cmDspClass_t _cm1Up_DC;

typedef struct
{
  cmDspInst_t   inst;
  
} cmDsp1Up_t;

cmDspInst_t*  _cmDsp1Up_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The '1Up' constructor must given output channel count.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int      chCnt       = va_arg(vl,int);
  
  cmDsp1Up_t* p = cmDspInstAllocV(cmDsp1Up_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "chcnt",  kChCnt1uId,      0, 0, kUIntDsvFl   | kReqArgDsvFl,               "Output channel count.",
  1,         "sel",    kSel1uId,        0, 0, kUIntDsvFl   | kOptArgDsvFl | kInDsvFl,    "Channel index selector.",
  chCnt,     "out",    kBaseOut1uId,    0, 0, kBoolDsvFl   | kOutDsvFl,                  "Gate outputs",
  0 );
  
  
  unsigned i;
  
  cmDspSetDefaultUInt( ctx, &p->inst, kSel1uId, 0.0, 0.0 );
  
  for(i=0; i<chCnt; ++i)
    cmDspSetDefaultBool(   ctx, &p->inst, kBaseOut1uId + i, false, false );
  
  va_end(vl1);
  
  return &p->inst;
}


cmDspRC_t _cmDsp1Up_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc = kOkDspRC;
  
  if((rc   = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    unsigned chIdx = cmDspUInt(inst,kSel1uId);
    unsigned chCnt = cmDspUInt(inst,kChCnt1uId);
    unsigned i;
    for(i=0; i<chCnt; ++i)
      cmDspSetBool(ctx,inst,kBaseOut1uId+i, i == chIdx );
  }
  
  return rc;  
}


cmDspRC_t _cmDsp1Up_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  
  if( evt->dstVarId == kSel1uId)
  {
    unsigned chIdx = cmDspUInt(inst,kSel1uId);
    unsigned chCnt = cmDspUInt(inst,kChCnt1uId);
    
    // turn off the previously selected channel
    if( chIdx != cmInvalidIdx && chIdx < chCnt )
      cmDspSetBool(ctx,inst,kBaseOut1uId+chIdx,false);
    
    // set the new channel index
    cmDspSetEvent(ctx,inst,evt);
    
    // get the new channel index
    chIdx = cmDspUInt(inst,kSel1uId);
    
    // send the new channel index
    if( chIdx != cmInvalidIdx && chIdx < chCnt )
      cmDspSetBool(ctx,inst,kBaseOut1uId+chIdx,true);
    
  }
  
  return rc;
}

cmDspClass_t* cm1UpClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cm1Up_DC,ctx,"1Up",
  NULL,
  _cmDsp1Up_Alloc,
  NULL,
  _cmDsp1Up_Reset,
  NULL,
  _cmDsp1Up_Recv,
  NULL,NULL,
  "Set one input high and all others low.");
  
  return &_cm1Up_DC;
}


cmDspGateToSym : Convert a 'true'/'false' gate to an 'on'/'off' symbol.
// 
enum
{
  kOnSymGsId,
  kOffSymGsId,
  kOnGsId,
  kOffGsId,
  kBothGsId,
  kOutGsId
};

cmDspClass_t _cmGateToSym_DC;

typedef struct
{
  cmDspInst_t   inst;
} cmDspGateToSym_t;

cmDspInst_t*  _cmDspGateToSym_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspGateToSym_t* p = cmDspInstAllocV(cmDspGateToSym_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,     "on_sym", kOnSymGsId,   0, 0, kSymDsvFl    | kInDsvFl | kOptArgDsvFl,"'on' symbol id (default:'on')",
  1,     "off_sym",kOffSymGsId,  0, 0, kSymDsvFl    | kInDsvFl | kOptArgDsvFl,"'off' symbol id (default:'off')",
  1,     "on",    kOnGsId,       0, 0, kBoolDsvFl   | kInDsvFl,                "On - send out 'on' symbol when a 'true' is received.",
  1,     "off",   kOffGsId,      0, 0, kBoolDsvFl   | kInDsvFl,                "Off - send out 'off' symbol when a 'false' is received.",
  1,     "both",  kBothGsId,     0, 0, kBoolDsvFl   | kInDsvFl,                "Send 'on' on 'true' and 'off' on 'false'.",
  1,     "out",   kOutGsId,      0, 0, kSymDsvFl    | kOutDsvFl,               "Output",
  0 );
  
  
  unsigned onSymId  = cmSymTblRegisterStaticSymbol(ctx->stH,"on");
  unsigned offSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"off");
  
  cmDspSetDefaultSymbol( ctx, &p->inst, kOnSymGsId,  onSymId );
  cmDspSetDefaultSymbol( ctx, &p->inst, kOffSymGsId, offSymId );
  cmDspSetDefaultSymbol( ctx, &p->inst, kOutGsId, cmInvalidId );
  return &p->inst;
}


cmDspRC_t _cmDspGateToSym_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc = kOkDspRC;
  
  if((rc   = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
  }
  
  return rc;  
}


cmDspRC_t _cmDspGateToSym_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    unsigned onSymId  = cmDspSymbol(inst,kOnSymGsId);
    unsigned offSymId = cmDspSymbol(inst,kOffSymGsId);
    
    switch( evt->dstVarId )
    {
    case kOnGsId:
      if( cmDspBool(inst,kOnGsId) )
        cmDspSetSymbol(ctx,inst,kOutGsId,onSymId);
      break;
      
    case kOffGsId:
      if( !cmDspBool(inst,kOffGsId) )
        cmDspSetSymbol(ctx,inst,kOutGsId,offSymId);
      break;
      
    case kBothGsId:
      cmDspSetSymbol(ctx,inst, kOutGsId, cmDspBool(inst,kBothGsId) ? onSymId : offSymId);
      break;
    }
    
  }
  return rc;
}

cmDspClass_t* cmGateToSymClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmGateToSym_DC,ctx,"GateToSym",
  NULL,
  _cmDspGateToSym_Alloc,
  NULL,
  _cmDspGateToSym_Reset,
  NULL,
  _cmDspGateToSym_Recv,
  NULL,NULL,
  "Convert a 'true'/'false' gate to an 'on'/'off' symbol.");
  
  return &_cmGateToSym_DC;
}


cmDspPortToSym : Send a pre-defined symbol every time a message arrives a given input port.
enum
{
  kOutPtsId,
  kBaseInPtsId
};

cmDspClass_t _cmPortToSym_DC;

typedef struct
{
  cmDspInst_t inst;
  unsigned*   symIdArray;
  unsigned    symIdCnt;
  unsigned    baseOutPtsId;
} cmDspPortToSym_t;

cmDspInst_t*  _cmDspPortToSym_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  va_list       vl1;
  va_copy(vl1,vl);
  
  if( va_cnt < 1 )
  {
    va_end(vl1);
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'PortToSym' constructor argument list must contain at least one symbol label.");
    return NULL;
  }
  
  unsigned      symCnt    = va_cnt;
  unsigned      argCnt    = 1 + 2*symCnt;
  cmDspVarArg_t args[argCnt+1];
  
  unsigned* symIdArray   = cmMemAllocZ(unsigned,symCnt);
  unsigned  baseOutPtsId = kBaseInPtsId + symCnt;
  
  // setup the output port arg recd
  cmDspArgSetup(ctx,args,"out",cmInvalidId,kOutPtsId,0,0,kOutDsvFl | kSymDsvFl, "Output" );
  
  unsigned i;
  
  for(i=0; i<symCnt; ++i)
  {
    // get the symbol label
    const cmChar_t* symLabel = va_arg(vl,const char*);
    assert( symLabel != NULL );
    
    // register the symbol
    symIdArray[i] = cmSymTblRegisterSymbol(ctx->stH,symLabel);
    
    // input port - any msg in this port will generate an output from 'out' as well as the associated output port
    cmDspArgSetup(ctx, args+kBaseInPtsId+i, symLabel, cmInvalidId, kBaseInPtsId+i, 0, 0, kInDsvFl  | kTypeDsvMask, cmTsPrintfH(ctx->lhH,"%s Input.",symLabel) );
    
    cmDspArgSetup(ctx, args+baseOutPtsId+i, symLabel, cmInvalidId, baseOutPtsId+i, 0, 0, kOutDsvFl | kSymDsvFl,    cmTsPrintfH(ctx->lhH,"%s Output.",symLabel) );
    
  }
  
  cmDspArgSetupNull(args + argCnt);
  
  cmDspPortToSym_t* p = cmDspInstAlloc(cmDspPortToSym_t,ctx,classPtr,args,instSymId,id,storeSymId,0,vl1);
  
  p->symIdCnt     = symCnt;
  p->symIdArray   = symIdArray;
  p->baseOutPtsId = baseOutPtsId;
  
  cmDspSetDefaultSymbol(ctx,&p->inst,kOutPtsId,cmInvalidId);
  
  va_end(vl1);
  
  return &p->inst;
}
cmDspRC_t _cmDspPortToSym_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspPortToSym_t* p = (cmDspPortToSym_t*)inst;
  cmMemFree(p->symIdArray);
  return kOkDspRC;
}

cmDspRC_t _cmDspPortToSym_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return cmDspApplyAllDefaults(ctx,inst);
}


cmDspRC_t _cmDspPortToSym_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDspPortToSym_t* p = (cmDspPortToSym_t*)inst;
  
  // if a msg of any type is recieved on an input port - send out the associated symbol
  if( kBaseInPtsId <= evt->dstVarId && evt->dstVarId < kBaseInPtsId + p->symIdCnt )
  {
    unsigned idx = evt->dstVarId - kBaseInPtsId;
    assert( idx < p->symIdCnt );
    cmDspSetSymbol(ctx,inst,p->baseOutPtsId + idx, p->symIdArray[idx]);
    return cmDspSetSymbol(ctx,inst,kOutPtsId,      p->symIdArray[idx]);
  }
  
  return rc;
}

cmDspClass_t* cmPortToSymClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmPortToSym_DC,ctx,"PortToSym",
  NULL,
  _cmDspPortToSym_Alloc,
  _cmDspPortToSym_Free,
  _cmDspPortToSym_Reset,
  NULL,
  _cmDspPortToSym_Recv,
  NULL,NULL,
  "If a message of any kind is received on a port then send the symbol associated with the port.");
  
  return &_cmPortToSym_DC;
}


cmDspIntToSym : Send a pre-defined symbol every time a message arrives a given input port.
enum
{
  kInItsId,
  kOutItsId,
  kBaseInItsId
};

cmDspClass_t _cmIntToSym_DC;

typedef struct
{
  cmDspInst_t inst;
  int*        intArray;
  unsigned*   symIdArray;
  unsigned    symIdCnt;
  unsigned    baseIntItsId;
  unsigned    baseOutItsId;
  
} cmDspIntToSym_t;

cmDspInst_t*  _cmDspIntToSym_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  va_list       vl1;
  va_copy(vl1,vl);
  
  if( va_cnt < 2 || va_cnt % 2 !=0  )
  {
    va_end(vl1);
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'IntToSym' constructor argument list must contain at least one int/symbol pair and all pairs must be complete.");
    return NULL;
  }
  
  unsigned      symCnt    = va_cnt/2;
  unsigned      argCnt    = 2 + 3*symCnt;
  cmDspVarArg_t args[argCnt+1];
  
  unsigned* symIdArray   = cmMemAllocZ(unsigned,symCnt);
  int*      intArray     = cmMemAllocZ(int,symCnt);
  unsigned  baseIntItsId = kBaseInItsId + symCnt;  
  unsigned  baseOutItsId = baseIntItsId + symCnt;
  
  // setup the integer input and symbol output port arg recd
  cmDspArgSetup(ctx,args,  "in",  cmInvalidId, kInItsId,  0, 0, kInDsvFl  | kIntDsvFl, "Integer input" );
  cmDspArgSetup(ctx,args+1,"out", cmInvalidId, kOutItsId, 0, 0, kOutDsvFl | kSymDsvFl, "Output" );
  
  unsigned i;
  
  for(i=0; i<symCnt; ++i)
  {
    // get the integer value
    intArray[i] = va_arg(vl,int);
    
    // get the symbol label
    const cmChar_t* symLabel = va_arg(vl,const char*);
    assert( symLabel != NULL );
    
    unsigned intLabelN = (symLabel==NULL ? 0 : strlen(symLabel)) + 5;
    cmChar_t intLabel[ intLabelN ];
    snprintf(intLabel,intLabelN,"%s%s", symLabel==NULL ? "" : symLabel, "-int" );
    
    // register the symbol
    symIdArray[i] = cmSymTblRegisterSymbol(ctx->stH,symLabel);
    
    // trigger port associated with this symbol (any msg on this port will trigger an output)
    cmDspArgSetup(ctx, args+kBaseInItsId+i, symLabel, cmInvalidId, kBaseInItsId+i, 0, 0, kInDsvFl  | kTypeDsvMask, cmTsPrintfH(ctx->lhH,"%s Input.",symLabel) );
    
    // this port is used to set the integer value associated with this symbol
    cmDspArgSetup(ctx, args+baseIntItsId+i, intLabel, cmInvalidId, baseIntItsId+i, 0, 0, kInDsvFl  | kIntDsvFl,  cmTsPrintfH(ctx->lhH,"Set the integer value associated with %s.",symLabel) );
    
    // symbol output port - when ever this symbol is sent out it will go out this port as well as the 'out' port
    cmDspArgSetup(ctx, args+baseOutItsId+i, symLabel, cmInvalidId, baseOutItsId+i, 0, 0, kOutDsvFl | kSymDsvFl,    cmTsPrintfH(ctx->lhH,"%s Output.",symLabel) );
    
    
  }
  
  cmDspArgSetupNull(args + argCnt);
  
  cmDspIntToSym_t* p = cmDspInstAlloc(cmDspIntToSym_t,ctx,classPtr,args,instSymId,id,storeSymId,0,vl1);
  
  p->symIdCnt     = symCnt;
  p->intArray     = intArray;
  p->symIdArray   = symIdArray;
  p->baseOutItsId = baseOutItsId;
  p->baseIntItsId = baseIntItsId;
  
  cmDspSetDefaultSymbol(ctx,&p->inst,kOutItsId,cmInvalidId);
  
  va_end(vl1);
  
  return &p->inst;
}
cmDspRC_t _cmDspIntToSym_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspIntToSym_t* p = (cmDspIntToSym_t*)inst;
  cmMemFree(p->symIdArray);
  return kOkDspRC;
}

cmDspRC_t _cmDspIntToSym_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return cmDspApplyAllDefaults(ctx,inst);
}

cmDspRC_t _cmDspIntToSymSendOut( cmDspCtx_t* ctx, cmDspInst_t* inst, unsigned idx )
{
  cmDspIntToSym_t* p = (cmDspIntToSym_t*)inst;
  assert( idx < p->symIdCnt );
  cmDspSetSymbol(ctx,inst,p->baseOutItsId + idx, p->symIdArray[idx]);
  return cmDspSetSymbol(ctx, inst, kOutItsId, p->symIdArray[ idx ]);
}


cmDspRC_t _cmDspIntToSym_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDspIntToSym_t* p = (cmDspIntToSym_t*)inst;
  
  // if an integer arrived at 'in'
  if( evt->dstVarId == kInItsId )
  {
    cmDspSetEvent(ctx,inst,evt);
    
    unsigned i;
    int      intVal = cmDspInt(inst,kInItsId);
    
    for(i=0; i<p->symIdCnt; ++i)
      if( intVal == p->intArray[i] )
    {
      rc = _cmDspIntToSymSendOut( ctx, inst, i );
      break;
    } 
  }
  else
  {  
    // if a msg of any type is recieved on an input port - send out the associated symbol
    if( kBaseInItsId <= evt->dstVarId && evt->dstVarId < kBaseInItsId + p->symIdCnt )
    {
      _cmDspIntToSymSendOut( ctx, inst, evt->dstVarId - kBaseInItsId );
    }
    else
      
    // if this is a new interger value for this symbol
    if( p->baseIntItsId <= evt->dstVarId && evt->dstVarId < p->baseIntItsId + p->symIdCnt )
    {
      cmDspSetEvent(ctx,inst,evt);
      
      p->intArray[ evt->dstVarId - p->baseIntItsId ] = cmDspInt( inst, evt->dstVarId );
    }
  }
  
  
  return rc;
}

cmDspClass_t* cmIntToSymClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmIntToSym_DC,ctx,"IntToSym",
  NULL,
  _cmDspIntToSym_Alloc,
  _cmDspIntToSym_Free,
  _cmDspIntToSym_Reset,
  NULL,
  _cmDspIntToSym_Recv,
  NULL,NULL,
  "If a message of any kind is received on a port then send the symbol associated with the port.");
  
  return &_cmIntToSym_DC;
}


cmDspRouter : Route the input value to one of multiple output ports.

enum
{
  kOutChCntRtId,
  kOutChIdxRtId,
  kInFloatRtId,
  kInBoolRtId,
  kInSymRtId,
  kInAudioRtId,
  kBaseOutFloatRtId
};

cmDspClass_t _cmRouter_DC;

typedef struct
{
  cmDspInst_t   inst;
  
  unsigned      oChCnt;
  unsigned      oChIdx;
  
  unsigned      baseBaseOutRtId;   // first data input port id
  
  unsigned      baseInFloatRtId;
  unsigned      baseInBoolRtId;
  unsigned      baseInSymRtId;
  unsigned      baseInAudioRtId;
  
  unsigned      baseOutFloatRtId;
  unsigned      baseOutBoolRtId;
  unsigned      baseOutSymRtId;
  unsigned      baseOutAudioRtId;
  
  
} cmDspRouter_t;

cmDspInst_t*  _cmDspRouter_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'Router' constructor must be given an output channel count.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int      oChCnt            = va_arg(vl,int);
  
  unsigned baseOutFloatRtId  = kBaseOutFloatRtId;
  unsigned baseOutBoolRtId   = baseOutFloatRtId  + oChCnt;
  unsigned baseOutSymRtId    = baseOutBoolRtId   + oChCnt;
  unsigned baseOutAudioRtId  = baseOutSymRtId    + oChCnt;
  
  cmDspRouter_t* p = cmDspInstAllocV(cmDspRouter_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "ochs",   kOutChCntRtId,     0, 0, kUIntDsvFl     | kReqArgDsvFl,"Output channel count.",
  1,         "sel",    kOutChIdxRtId,     0, 0, kUIntDsvFl     | kReqArgDsvFl | kInDsvFl,    "Output channel index selector.",
  1,         "f-in",   kInFloatRtId,      0, 0, kDoubleDsvFl   | kInDsvFl,     "Float input",
  1,         "b-in",   kInBoolRtId,       0, 0, kBoolDsvFl     | kInDsvFl,     "Bool input",
  1,         "s-in",   kInSymRtId,        0, 0, kSymDsvFl      | kInDsvFl,     "Symbol input",
  1,         "a-in",   kInAudioRtId,      0, 0, kAudioBufDsvFl | kInDsvFl,     "Audio input",
  oChCnt,    "f-out",  baseOutFloatRtId,  0, 0, kDoubleDsvFl   | kOutDsvFl,    "Float output",
  oChCnt,    "b-out",  baseOutBoolRtId,   0, 0, kBoolDsvFl     | kOutDsvFl,    "Bool output",
  oChCnt,    "s-out",  baseOutSymRtId,    0, 0, kSymDsvFl      | kOutDsvFl,    "Symbol output",
  oChCnt,    "a-out",  baseOutAudioRtId,  0, 1, kAudioBufDsvFl | kOutDsvFl,    "Audio output",
  0 );
  
  p->oChCnt     = oChCnt;
  
  p->baseBaseOutRtId   =  kBaseOutFloatRtId;
  p->baseOutFloatRtId  =  baseOutFloatRtId; 
  p->baseOutBoolRtId   =  baseOutBoolRtId;
  p->baseOutSymRtId    =  baseOutSymRtId;
  p->baseOutAudioRtId  =  baseOutAudioRtId;
  
  unsigned i;
  for(i=0; i<oChCnt; ++i)
  {
    cmDspSetDefaultDouble( ctx, &p->inst, baseOutFloatRtId+i, 0.0, 0.0 );
    cmDspSetDefaultBool(   ctx, &p->inst, baseOutBoolRtId+i, false, false );
    cmDspSetDefaultSymbol( ctx, &p->inst, baseOutSymRtId+i, cmInvalidId );
  }
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspRouter_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return kOkDspRC;
}

cmDspRC_t _cmDspRouter_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t      rc = kOkDspRC;
  cmDspRouter_t* p  = (cmDspRouter_t*)inst;
  
  if((rc   = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    unsigned i;
    for(i=0; i<p->oChCnt; ++i)
      cmDspZeroAudioBuf(ctx,inst,p->baseOutAudioRtId+i);
    
  }
  
  return rc;  
}

cmDspRC_t _cmDspRouter_Exec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t         rc     = kOkDspRC;
  cmDspRouter_t*    p      = (cmDspRouter_t*)inst;
  unsigned          oChIdx = cmDspUInt(inst,kOutChIdxRtId);
  cmSample_t*       dp     = cmDspAudioBuf(ctx,inst,p->baseOutAudioRtId + oChIdx,0);
  const cmSample_t* sp     = cmDspAudioBuf(ctx,inst,kInAudioRtId ,0);
  unsigned          n      = cmDspAudioBufSmpCount(ctx,inst,p->baseOutAudioRtId,0);
  
  if( dp != NULL )
  {
    if( sp == NULL )
      cmVOS_Zero(dp,n);
    else
      cmVOS_Copy(dp,n,sp);
  }
  
  return rc;
}

cmDspRC_t _cmDspRouter_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDspRouter_t* p  = (cmDspRouter_t*)inst;
  
  
  // ignore out of range output channel selection
  if( evt->dstVarId == kOutChIdxRtId && cmDsvGetUInt(evt->valuePtr) >= p->oChCnt )
  {
    cmDspInstErr(ctx,inst,kVarNotValidDspRC,"The selector channel index %i  is out of of range.",cmDsvGetUInt(evt->valuePtr));
    return kOkDspRC;
  } 
  
  //  if( evt-&gtdstVarId == kOutChIdxRtId &amp&amp cmDsvGetUInt(evt-&gtvaluePtr) &lt p-&gtoChCnt )
  //  {
  //    const cmChar_t* symLbl = cmSymTblLabel(ctx-&gtstH,inst-&gtsymId);
  //    cmDspInstErr(ctx,inst,kOkDspRC,&quotRouter: ch:%i %s\n&quot,cmDsvGetUInt(evt-&gtvaluePtr),symLbl==NULL?&quot&quot:symLbl);
  //  }  
  
  // store the incoming value
  if( evt->dstVarId < p->baseBaseOutRtId )
    if((rc = cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
      return rc;
  
  unsigned chIdx = cmDspUInt(inst,kOutChIdxRtId);
  
  
  switch( evt->dstVarId )
  {
  case kInFloatRtId:
    cmDspSetDouble(ctx,inst,p->baseOutFloatRtId + chIdx, cmDspDouble(inst,kInFloatRtId) );
    break;
    
  case kInBoolRtId:
    cmDspSetBool(ctx,inst,p->baseOutBoolRtId + chIdx, cmDspBool(inst,kInBoolRtId) );
    break;
    
  case kInSymRtId:
    cmDspSetSymbol(ctx,inst,p->baseOutSymRtId + chIdx, cmDspSymbol(inst,kInSymRtId));
    break;
  }
  
  return rc;
}

cmDspClass_t* cmRouterClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmRouter_DC,ctx,"Router",
  NULL,
  _cmDspRouter_Alloc,
  _cmDspRouter_Free,
  _cmDspRouter_Reset,
  _cmDspRouter_Exec,
  _cmDspRouter_Recv,
  NULL,NULL,
  "1 to N Router");
  
  return &_cmRouter_DC;
}


cmDspAvailCh : Track active an inactive processing channels.
//
// Purpose: AvailCh can be used to implement a channel switching circuit.
// 
// Inputs:
//   chs         - The count of channels. Constructor only argument. 
//   trig        - Any input causes the next available channel, i, to be enabled.
//                 gate[i] transmits 'true'.  In 'exclusive (0) mode all active
//                 channels are then requested to shutdown by transmitting 'false' on
//                 gate[] - only the new channel will be active.  In 'multi' (1) mode
//                 no signal is sent out the gate[].
//   dis[chCnt]  - Recieves a gate signal from an external object which indicates 
//                 when a channel is no longer active. When a 'false' is received on dis[i]
//                 the channel i is marked as available.  In 'multi' mode 'false' is 
//                 then transmitted on gate[i].
// Outputs:
//   gate[chCnt] - 'true' is transmitted when a channel is made active (see trig)
//                 'false' is transmitted to notify the channel that it should shutdown.
//                 The channel is not considered actually shutdown until dis[i]
//                 recieves a 'false'.
//   ch            The next prospective available channel is sent whenever it
//                 becomes available.  A next channel becomes available when
//                 a channel is marked as inactive via dis[i] or when
//                 a new channel is made active, via trigger, and another
//                 channel active channel exists.  Note that this channel is
//                 sent &quotprospectively&quot - possibly long before the associated
//                 gate[ch] is raised - in order to switch parameter routers away
//                 from the newly active channel and a currently in-active channel.
// Notes:
//   The gate[] output is designed to work with the gate[] input of Xfader.  When
//   availCh.gate[] goes high Xfader fades in, when availCh.gate[] goes low
//   Xfader fades out.  The dis[] channel is designed to connect from Xfader.state[].
//   Xfader.state[] goes low when a fade-out is complete, the connected AvailCh 
//   is then marked as available. 
enum
{
  kChCntAvId,
  kModeAvId,
  kTrigAvId,
  kResetAvId,
  kChIdxAvId,  
  kBaseDisInAvId,
  
  kExclusiveModeAvId=0,
  kMultiModeAvId=1
};

cmDspClass_t _cmAvailCh_DC;

typedef struct
{
  cmDspInst_t   inst;
  unsigned      chCnt;
  unsigned      baseDisInAvId;
  unsigned      baseGateOutAvId;
  bool*         stateArray;
  unsigned      nextAvailChIdx;
  unsigned      audioCycleCnt;
} cmDspAvailCh_t;

cmDspInst_t*  _cmDspAvailCh_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  if( va_cnt < 1 )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'AvailCh' constructor must be given an channel count.");
    return NULL;
  }
  
  va_list vl1;
  va_copy(vl1,vl);
  
  int     chCnt            = va_arg(vl,int);
  
  if( chCnt <= 0 )
  {
    va_end(vl1);
    cmDspClassErr(ctx,classPtr,kInvalidArgDspRC,"The 'AvailCh' constructor must be given a positive channel count.");
    return NULL;
  }
  
  unsigned baseDisInAvId    = kBaseDisInAvId;
  unsigned baseGateOutAvId  = baseDisInAvId + chCnt;
  
  cmDspAvailCh_t* p = cmDspInstAllocV(cmDspAvailCh_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl1,
  1,         "chs",    kChCntAvId,        0, 0, kUIntDsvFl     | kReqArgDsvFl, "Channel count.",
  1,         "mode",   kModeAvId,         0, 0, kUIntDsvFl     | kInDsvFl,     "Mode: 0=exclusive (dflt) 1=multi",
  1,         "trig",   kTrigAvId,         0, 0, kTypeDsvMask   | kInDsvFl,     "Trigger the unit to select the next available channel.", 
  1,         "reset",  kResetAvId,        0, 0, kBoolDsvFl     | kInDsvFl | kOutDsvFl,     "Reset to default state",
  1,         "ch",     kChIdxAvId,        0, 0, kUIntDsvFl     | kOutDsvFl,    "Currently selected channel.",
  chCnt,     "dis",    baseDisInAvId,     0, 0, kBoolDsvFl     | kInDsvFl,     "Disable channel gate",
  chCnt,     "gate",   baseGateOutAvId,   0, 0, kBoolDsvFl     | kOutDsvFl,    "Active channel gate",
  0 );
  
  p->chCnt     = chCnt;
  
  p->baseDisInAvId   =  baseDisInAvId;
  p->baseGateOutAvId =  baseGateOutAvId;
  p->nextAvailChIdx  = cmInvalidIdx;
  p->audioCycleCnt   = 0;
  
  unsigned i;
  for(i=0; i<chCnt; ++i)
  {
    cmDspSetDefaultBool( ctx, &p->inst, baseDisInAvId+i,   false, false );
    cmDspSetDefaultBool( ctx, &p->inst, baseGateOutAvId+i, false, false );
  }
  cmDspSetDefaultUInt( ctx, &p->inst, kModeAvId,  0, kExclusiveModeAvId );
  cmDspSetDefaultUInt( ctx, &p->inst, kChIdxAvId, 0, 0 );
  
  va_end(vl1);
  
  return &p->inst;
}

cmDspRC_t _cmDspAvailCh_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  return kOkDspRC;
}

cmDspRC_t _cmDspAvailCh_DoReset( cmDspCtx_t* ctx, cmDspInst_t* inst )
{
  unsigned i;      
  cmDspAvailCh_t* p = (cmDspAvailCh_t*)inst;
  
  
  // ch 0 is the channel receiving parameters
  cmDspSetUInt(ctx,inst,kChIdxAvId,0);
  
  for(i=0; i<p->chCnt; ++i)
  {
    cmDspSetBool(ctx, inst, p->baseDisInAvId   + i, i==0);  // disable all channels except ch zero
    cmDspSetBool(ctx, inst, p->baseGateOutAvId + i, i==0);  // enable channel 0
  }
  
  p->audioCycleCnt  = 0;
  p->nextAvailChIdx = cmInvalidIdx;
  
  // transmit reset
  cmDspSetBool(ctx,inst, kResetAvId, false );
  
  return kOkDspRC;  
}


cmDspRC_t _cmDspAvailCh_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t rc;
  if((rc = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    rc = _cmDspAvailCh_DoReset(ctx,inst);
  }
  return rc;
}

void _cmDspAvailCh_SetNextAvailCh( cmDspCtx_t* ctx, cmDspInst_t* inst, bool warnFl, const char* label )
{
  cmDspAvailCh_t* p = (cmDspAvailCh_t*)inst;
  unsigned i;
  
  // if a valid next avail ch already exists then do nothing
  if( p->nextAvailChIdx != cmInvalidIdx )
    return;
  
  // for each channel
  for(i=0; i<p->chCnt; ++i)
  {
    // the channel's active state is held in the 'dis' variable.
    bool activeFl = cmDspBool(inst,p->baseDisInAvId+i);
    
    // if ch[i] is the first avail inactive channel
    if( !activeFl )
    {
      p->nextAvailChIdx  = i;      // then make it the next available channel
      break;
    }
    
  }
  
  // if no available channels were found
  if( p->nextAvailChIdx == cmInvalidIdx )
  {
    if( warnFl )
      cmDspInstErr(ctx,inst,kInvalidStateDspRC,"No available channels exist.");
  }
  else
  {
    // Notify the external world which channel is to be used next.
    // This allows routers which are switching parameters between
    // xfade channels to switch new parameter values to go to the
    // next available channel rather than the current channel.
    // The next available channel will then be faded up with the
    // new parameters on the next trigger command.    
    cmDspSetUInt(ctx,inst,kChIdxAvId,p->nextAvailChIdx);
  }  
}

cmDspRC_t _cmDspAvailCh_Exec( cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t       rc = kOkDspRC;
  cmDspAvailCh_t* p  = (cmDspAvailCh_t*)inst;
  
  p->audioCycleCnt += 1;
  
  // Setting the next available channel here solves the problem of sending the
  // first 'ch' output after the program starts executing.
  // The problem is that 'ch' should be set to 0 for the first
  // execution cycle so that parameters may be set to the initial active channel
  // during the first cycle.  After the first cycle however parameters should be
  // sent to the next channel which will be faded up.  Setting
  // 'ch' here accomplishes this without relying on an external signal.
  // Note that we wait until the second cycle because we don't know where
  // this 'availCh' will be in the execution cycle relative to other processors.
  // If it is at the beginning then other processors that might be setting
  // initial parameters will not have had a chance to run before the
  // 'ch' change.  Waiting unitl the second cycle guarantees that all the
  // other processors had at least one chance to run.  
  if( p->audioCycleCnt == 2 )
  {
    _cmDspAvailCh_SetNextAvailCh(ctx,inst,true,"exec");
  }
  
  return rc;
}

cmDspRC_t _cmDspAvailCh_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDspAvailCh_t* p  = (cmDspAvailCh_t*)inst;
  
  bool exclModeFl  = cmDspUInt(inst, kModeAvId ) == kExclusiveModeAvId;
  
  // if this is a reset
  if( evt->dstVarId == kResetAvId )
  {
    return _cmDspAvailCh_DoReset(ctx,inst);
  }
  
  // if this is a trigger
  if( evt->dstVarId == kTrigAvId )
  {
    // if no available channels were previously found
    if( p->nextAvailChIdx == cmInvalidIdx )
      cmDspInstErr(ctx,inst,kInvalidStateDspRC,"There are no available channels to trigger.");
    else
    {
      // indicate that ch[nexAvailChIdx] is no longer available
      cmDspSetBool(ctx, inst, p->baseDisInAvId   + p->nextAvailChIdx, true);
      
      // raise the gate to start the xfade.
      cmDspSetBool(ctx, inst, p->baseGateOutAvId + p->nextAvailChIdx, true); 
      
      if( exclModeFl )
      {
        unsigned i;
        for(i=0; i<p->chCnt; ++i)
          if( i!=p->nextAvailChIdx && cmDspBool(inst,p->baseDisInAvId+i) )
            cmDspSetBool(ctx,inst, p->baseGateOutAvId+i, false );
      }
      
      // invalidate nextAvailChIdx
      p->nextAvailChIdx = cmInvalidIdx;
      
      // It may be possible to know the next avail ch so try to set it here.
      _cmDspAvailCh_SetNextAvailCh(ctx, inst, false, "trig" );
      
    }
    
    
    return rc;
  }
  
  // if this is an incoming disable message.
  if( p->baseDisInAvId <= evt->dstVarId && evt->dstVarId < p->baseDisInAvId+p->chCnt && cmDsvGetBool(evt->valuePtr) == false)
  {    
    cmDspSetEvent(ctx,inst,evt);
    
    // a channel was disabled so a new channel should be available for selection
    if( p->audioCycleCnt > 0 )
      _cmDspAvailCh_SetNextAvailCh(ctx, inst, true, "dis" );
    
    if( !exclModeFl )
      cmDspSetBool(ctx, inst, p->baseGateOutAvId + (evt->dstVarId - p->baseDisInAvId), false);
  }
  
  return rc;
}

cmDspClass_t* cmAvailChClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmAvailCh_DC,ctx,"AvailCh",
  NULL,
  _cmDspAvailCh_Alloc,
  _cmDspAvailCh_Free,
  _cmDspAvailCh_Reset,
  _cmDspAvailCh_Exec,
  _cmDspAvailCh_Recv,
  NULL,NULL,
  "Enable the next availabled channel");
  
  return &_cmAvailCh_DC;
}



cmDspPreset : Store and recall preset. Show a preset list user interface unit.

enum
{
  kGroupSymPrId,
  kLabelPrId,
  kCmdPrId,
  kDonePrId,
  kListPrId,
  kSelPrId
};

cmDspClass_t _cmPreset_DC;

typedef struct
{
  cmDspInst_t   inst;
  unsigned      storeCmdSymId;
  unsigned      recallCmdSymId;
  unsigned      doneSymId;
  cmJsonH_t     jsH;
  cmJsonNode_t* np;
} cmDspPreset_t;

cmDspRC_t  _cmDspPresetUpdateList( cmDspCtx_t* ctx, cmDspPreset_t* p, unsigned groupSymId )
{
  cmDspRC_t rc = kOkDspRC;
  
  // initialize the JSON tree
  if( cmJsonInitialize(&p->jsH,ctx->cmCtx) != kOkJsRC )
  {
    rc = cmDspInstErr(ctx,&p->inst,kJsonFailDspRC,"JSON preset list handle initialization failed.");
    goto errLabel;
  }
  
  // create the JSON tree root container
  if( cmJsonCreateObject(p->jsH,NULL) == NULL )
  {
    rc = cmDspInstErr(ctx,&p->inst,kJsonFailDspRC,"JSON preset list root object create failed.");
    goto errLabel;    
  }
  
  // if a valid preset group symbol was given
  if( groupSymId != cmInvalidId )
  {
    // get the JSON list containing the preset labels and symId's for this preset group
    if( cmDspSysPresetPresetJsonList(ctx->dspH, groupSymId, &p->jsH ) != kOkDspRC )
    {
      rc = cmDspInstErr(ctx,&p->inst,kSubSysFailDspRC,"Request for a preset list failed.");
      goto errLabel;
    }
    
    // get a pointer to the JSON 'presetArray' array node
    if(( p->np = cmJsonFindValue(p->jsH,"presetArray",NULL,kArrayTId)) == NULL )
    {
      rc = cmDspInstErr(ctx,&p->inst,kJsonFailDspRC,"Preset list is empty or synatax is not recognized.");
      goto errLabel;      
    }
    
    // set the JSON list
    if((rc = cmDspSetJson(ctx,&p->inst,kListPrId,p->np)) != kOkDspRC )
    {
      rc = cmDspInstErr(ctx,&p->inst,rc,"Preset list set failed.");
      goto errLabel;
    }
  }
  errLabel:
  return rc;
}

cmDspRC_t _cmDspPresetDoRecall( cmDspCtx_t* ctx, cmDspPreset_t* p, const cmChar_t* groupLabel, const cmChar_t* presetLabel )
{
  cmDspRC_t rc;
  
  // recall the preset
  if(( rc = cmDspSysPresetRecall(ctx->dspH, groupLabel, presetLabel )) != kOkDspRC )
    return cmDspInstErr(ctx,&p->inst,kSubSysFailDspRC,"Preset recall failed for group:'%s' preset:'%s'.",cmStringNullGuard(groupLabel),cmStringNullGuard(presetLabel));
  
  // send out a notification that a new preset has been loaded
  return cmDspSetSymbol(ctx,&p->inst,kDonePrId,p->doneSymId);
}

// selIdx is base 1, not base 0, because it references the JSON tree rows where the
// first row contains the titles.
cmDspRC_t _cmDspPresetListSelectRecall( cmDspCtx_t* ctx, cmDspPreset_t* p, unsigned selIdx )
{
  cmDspRC_t rc = kOkDspRC;
  const cmChar_t* presetLabel;
  const cmChar_t* groupLabel;
  unsigned        groupSymId;
  unsigned        presetSymId;
  const cmJsonNode_t* rnp;
  
  // validate the JSON tree
  if( cmJsonIsValid(p->jsH) == false || p->np == NULL )
  {
    rc = cmDspInstErr(ctx,&p->inst,kJsonFailDspRC,"Preset recall failed. The preset JSON tree does not exist.");
    goto errLabel;
  }
  
  // validate the group id
  if( (groupSymId = cmDspSymbol(&p->inst,kGroupSymPrId)) == cmInvalidId ) 
  {
    rc = cmDspInstErr(ctx,&p->inst,kVarNotValidDspRC,"Preset recall failed. The preset group symbol has not been set.");
    goto errLabel;
  }
  
  // validate the selection index
  if( selIdx >= cmJsonChildCount(p->np) )
  {
    rc = cmDspInstErr(ctx,&p->inst,kVarNotValidDspRC,"Preset recall failed. The preset index: %i is out of range 0-%i", selIdx, cmJsonChildCount(p->np));
    goto errLabel;    
  } 
  
  // get the preset element
  if(( rnp = cmJsonArrayElementC(p->np, selIdx )) == NULL )
  {
    rc = cmDspInstErr(ctx,&p->inst,kJsonFailDspRC,"Preset recall failed. Unable to retrieve preset JSON element.");
    goto errLabel;    
  }
  
  // verify the JSON syntax
  assert( rnp->typeId==kArrayTId && cmJsonChildCount(rnp)==2 && cmJsonArrayElementC(rnp,1)->typeId == kIntTId );
  
  // get the preset symbol id
  if( cmJsonUIntValue( cmJsonArrayElementC(rnp,1), &presetSymId ) != kOkJsRC )
  {
    rc = cmDspInstErr(ctx,&p->inst,kJsonFailDspRC,"Preset recall failed. Unable to retrieve preset symbol id.");
    goto errLabel;    
  }
  
  // convert symbols to strings
  groupLabel  = cmSymTblLabel(ctx->stH,groupSymId);
  presetLabel = cmSymTblLabel(ctx->stH,presetSymId);
  
  rc = _cmDspPresetDoRecall(ctx,p,groupLabel,presetLabel);
  
  errLabel:
  return rc;
}

cmDspInst_t*  _cmDspPreset_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  
  cmDspPreset_t* p = cmDspInstAllocV(cmDspPreset_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,         "sym",    kGroupSymPrId,     0, 0, kInDsvFl  | kSymDsvFl  | kReqArgDsvFl, "Preset group symbol.",
  1,         "label",  kLabelPrId,        0, 0, kInDsvFl  | kStrzDsvFl | kOptArgDsvFl, "Preset label",
  1,         "cmd",    kCmdPrId,          0, 0, kInDsvFl  | kSymDsvFl,                 "Command input",
  1,         "done",   kDonePrId,         0, 0, kOutDsvFl | kSymDsvFl,                 "Send 'done' symbol after preset recall.",
  1,         "list",   kListPrId,         0, 0, kInDsvFl  | kJsonDsvFl,                "Preset list as a JSON array.",
  1,         "sel",    kSelPrId,          0, 0, kInDsvFl  | kUIntDsvFl,                "Preset index selection index",
  0 );
  
  p->jsH            = cmJsonNullHandle;
  p->np             = NULL;
  p->storeCmdSymId  = cmSymTblRegisterStaticSymbol(ctx->stH,"store");
  p->recallCmdSymId = cmSymTblRegisterStaticSymbol(ctx->stH,"recall"); 
  p->doneSymId      = cmSymTblRegisterStaticSymbol(ctx->stH,"done"); 
  
  cmDspSetDefaultBool(   ctx, &p->inst, kDonePrId,false,false);
  cmDspSetDefaultSymbol( ctx, &p->inst, kGroupSymPrId, cmInvalidId);
  cmDspSetDefaultStrcz(  ctx, &p->inst, kLabelPrId,    NULL,"");
  
  unsigned height = 5;
  cmDspUiMsgListCreate(ctx, &p->inst, height, kListPrId, kSelPrId );
  
  return &p->inst;
}

cmDspRC_t _cmDspPreset_Free(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspPreset_t* p = (cmDspPreset_t*)inst;
  
  cmJsonFinalize(&p->jsH);
  return kOkDspRC;
}

cmDspRC_t _cmDspPreset_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t rc;
  cmDspPreset_t* p  = (cmDspPreset_t*)inst;
  
  if((rc = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
    _cmDspPresetUpdateList(ctx, p, cmDspSymbol(inst,kGroupSymPrId) );
  }
  
  return rc;
}

cmDspRC_t _cmDspPreset_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  cmDspPreset_t* p  = (cmDspPreset_t*)inst;
  
  switch( evt->dstVarId )
  {
  case kListPrId:
    return rc;   // we don't yet handle lists arriving by the input port
    
  case kSelPrId:
    {
      // sel idx is base 1 because the first row in the msg list contains the titles
      unsigned  selIdx = cmDsvGetUInt(evt->valuePtr);
      if((rc = _cmDspPresetListSelectRecall(ctx,p,selIdx)) != kOkDspRC)
        return rc;
    }
    break;
  }
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) != kOkDspRC )
    return rc;
  
  // if this is a store or recall command
  if( evt->dstVarId == kCmdPrId )
  {
    unsigned cmdSymId = cmDspSymbol(inst,kCmdPrId);
    
    if(  cmdSymId == p->storeCmdSymId || cmdSymId==p->recallCmdSymId )
    {
      unsigned groupSymId;
      const cmChar_t* groupLabel;
      const cmChar_t* presetLabel;
      
      // get the group symbol
      if((groupSymId = cmDspSymbol(inst,kGroupSymPrId)) == cmInvalidId )
        return cmDspInstErr(ctx,inst,kVarNotValidDspRC,"The preset group symbol id is not set.");
      
      // get the group label
      if((groupLabel = cmSymTblLabel(ctx->stH,groupSymId)) == NULL )
        return cmDspInstErr(ctx,inst,kVarNotValidDspRC,"The preset group label was not found.");
      
      // get the preset label
      if(( presetLabel = cmDspStrcz(inst,kLabelPrId)) == NULL || strlen(presetLabel)==0 )
        return cmDspInstErr(ctx,inst,kVarNotValidDspRC,"The preset label was not set.");
      
      // if this is a store command
      if( cmdSymId == p->storeCmdSymId )
      {
        // create a new preset
        if((rc = cmDspSysPresetCreate(ctx->dspH,groupLabel, presetLabel)) != kOkDspRC )
          return cmDspInstErr(ctx,inst,kSubSysFailDspRC,"Preset create failed for group:'%s' preset:'%s'.",cmStringNullGuard(groupLabel),cmStringNullGuard(presetLabel));
        
        // update the list with the new preset
        rc =  _cmDspPresetUpdateList(ctx, p, groupSymId );
      }
      else  // otherwise this must be a recall command
      {
        rc = _cmDspPresetDoRecall(ctx,p,groupLabel, presetLabel);
      }
    }
  }
  
  return rc;
}

cmDspClass_t* cmPresetClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmPreset_DC,ctx,"Preset",
  NULL,
  _cmDspPreset_Alloc,
  _cmDspPreset_Free,
  _cmDspPreset_Reset,
  NULL,
  _cmDspPreset_Recv,
  NULL,NULL,
  "Preset Manager");
  
  return &_cmPreset_DC;
}



cmDspBcastSym : Broadcast a symbol/value to all units registered to listen for the symbol.

enum
{
  kAttrBcId,
  kMsgBcId
};

cmDspClass_t _cmBcastSym_DC;

typedef struct
{
  cmDspInst_t   inst;
  unsigned onSymId;
  unsigned offSymId;
} cmDspBcastSym_t;

cmDspInst_t*  _cmDspBcastSym_Alloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  cmDspBcastSym_t* p = cmDspInstAllocV(cmDspBcastSym_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,     "attr",  kAttrBcId,    0, 0, kSymDsvFl    | kInDsvFl | kOptArgDsvFl,    "Instance which have this attribute symbol will receive the message.",
  1,     "msg",   kMsgBcId,     0, 0, kTypeDsvMask | kInDsvFl,    "Msg to broadcast.",
  0 );
  
  cmDspSetDefaultSymbol( ctx, &p->inst, kAttrBcId, cmInvalidId );
  
  return &p->inst;
}


cmDspRC_t _cmDspBcastSym_Reset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspRC_t    rc = kOkDspRC;
  
  if((rc   = cmDspApplyAllDefaults(ctx,inst)) == kOkDspRC )
  {
  }
  
  return rc;  
}


cmDspRC_t _cmDspBcastSym_Recv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t    rc = kOkDspRC;
  
  if( evt->dstVarId == kAttrBcId )
    return cmDspSetEvent(ctx,inst,evt);
  
  if( evt->dstVarId == kMsgBcId )
  {
    unsigned attrSymId = cmDspSymbol(inst,kAttrBcId);
    if( cmDsvIsSymbol(evt->valuePtr) )
    {
      printf("bcast: %i %s\n",attrSymId,cmSymTblLabel(ctx->stH,cmDsvSymbol(evt->valuePtr)));
    }
    
    return cmDspSysBroadcastValue(ctx->dspH, attrSymId, evt->valuePtr );
  }
  
  return rc;
}

cmDspClass_t* cmBcastSymClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmBcastSym_DC,ctx,"BcastSym",
  NULL,
  _cmDspBcastSym_Alloc,
  NULL,
  _cmDspBcastSym_Reset,
  NULL,
  _cmDspBcastSym_Recv,
  NULL,NULL,
  "Set one input high and all others low.");
  
  return &_cmBcastSym_DC;
}


cmDspSegLine : Line segment generator.
enum
{
  kRsrcSlId,
  kCmdSlId,
  kTrigSlId,
  kOutSlId,
};

cmDspClass_t _cmSegLineDC;


typedef struct
{
  cmDspInst_t inst;
  double*     x;    // x[n]
  unsigned    n;
  unsigned    i;    // current segment 
  unsigned    m;    // cur cnt
} cmDspSegLine_t;

cmDspInst_t*  _cmDspSegLineAlloc(cmDspCtx_t* ctx, cmDspClass_t* classPtr, unsigned storeSymId, unsigned instSymId, unsigned id, unsigned va_cnt, va_list vl )
{
  
  cmDspSegLine_t* p = cmDspInstAllocV(cmDspSegLine_t,ctx,classPtr,instSymId,id,storeSymId,va_cnt,vl,
  1,   "rsrc",  kRsrcSlId,  0, 0, kInDsvFl   | kStrzDsvFl | kReqArgDsvFl, "Name of resource array.",
  1,   "cmd",   kCmdSlId,   0, 0, kInDsvFl   | kSymDsvFl,                 "Command",
  1,   "trig",  kTrigSlId,  0, 0, kInDsvFl   | kTypeDsvMask,              "Trigger",
  1,   "out",   kOutSlId,   0, 0, kOutDsvFl  | kDoubleDsvFl,              "Output",
  0 );
  
  if( p == NULL )
    return NULL;
  
  // The array is expected to contain interleaved values:
  // cnt_0, val_0, cnt_1, val_1 .... cnt_n val_n
  // The 'cnt_x' values give the count of trigger values upon which the output will be 'val_x'.  
  
  if( cmDspRsrcDblArray(ctx->dspH,&p->n,&p->x,cmDspDefaultStrcz(&p->inst,kRsrcSlId),NULL) != kOkDspRC )
  {
    cmDspClassErr(ctx,classPtr,kVarArgParseFailDspRC,"The 'SegLine' constructor resource array could not be read.");
    return NULL;
  }
  
  
  cmDspSetDefaultDouble( ctx, &p->inst, kOutSlId, 0.0, p->n >= 2 ? p->x[1] : 0.0 );
  
  p->i = 0;
  p->m = 0;
  
  return &p->inst;
}


cmDspRC_t _cmDspSegLineReset(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{
  cmDspSegLine_t* p   = (cmDspSegLine_t*)inst;
  
  p->i = 0;
  p->m = 0;
  
  return cmDspApplyAllDefaults(ctx,inst);
}


cmDspRC_t _cmDspSegLineRecv(cmDspCtx_t* ctx, cmDspInst_t* inst, const cmDspEvt_t* evt )
{ 
  cmDspRC_t rc = kOkDspRC;
  cmDspSegLine_t* p = (cmDspSegLine_t*)inst;
  
  if((rc = cmDspSetEvent(ctx,inst,evt)) == kOkDspRC )
  {
    switch( evt->dstVarId )
    {
    case kTrigSlId:
      {
        double val = cmDspDouble(inst,kOutSlId);
        if( p->i < p->n )
        {
          if( p->m >= p->x[p->i] )
            p->i += 2;
          
          if( p->i < p->n )
          {
            double x0 = p->x[p->i-2];
            double y0 = p->x[p->i-1];
            double x1 = p->x[p->i+0];
            double y1 = p->x[p->i+1];
            
            double dx = x1 - x0;
            double dy = y1 - y0;
            
            val = y0 + (p->m - x0) * dy / dx;
            
            printf("i:%i m=%i x0:%f y0:%f x1:%f y1:%f : %f\n",p->i,p->m,x0,y0,x1,y1,val);
            
          }
          else
          {
            val = p->x[p->n-1];
          }
          
          ++p->m;
          
        }
        cmDspSetDouble(ctx,inst,kOutSlId,val);
      }
      break;
      
    case kCmdSlId:
      p->i = 0;
      p->m = 0;
      break;
    }
  }
  return rc;
}

cmDspClass_t* cmSegLineClassCons( cmDspCtx_t* ctx )
{
  cmDspClassSetup(&_cmSegLineDC,ctx,"SegLine",
  NULL,
  _cmDspSegLineAlloc,
  NULL,
  _cmDspSegLineReset,
  NULL,
  _cmDspSegLineRecv,
  NULL,NULL,
  "SegLine");
  
  return &_cmSegLineDC;
}