diff --git a/Makefile.am b/Makefile.am index 44aa39e..5240b1e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -30,8 +30,11 @@ cmSRC += src/libcm/cmMidiFilePlay.c src/libcm/cmMidiPort.c src/libcm/cmMidiFile. cmHDR += src/libcm/cmAudioFile.h src/libcm/cmAudioFileMgr.h src/libcm/cmMsgProtocol.h src/libcm/cmAudioSys.h src/libcm/cmAudioPortFile.h src/libcm/cmAudioFileDev.h cmSRC += src/libcm/cmAudioFile.c src/libcm/cmAudioFileMgr.c src/libcm/cmMsgProtocol.c src/libcm/cmAudioSys.c src/libcm/cmAudioPortFile.c src/libcm/cmAudioFileDev.c -cmHDR += src/libcm/cmDevCfg.h src/libcm/cmUi.h src/libcm/cmUiDrvr.h src/libcm/cmUiAudioSysMstr.h -cmSRC += src/libcm/cmDevCfg.c src/libcm/cmUi.c src/libcm/cmUiDrvr.c src/libcm/cmUiAudioSysMstr.c +cmHDR += src/libcm/cmRtSys.h src/libcm/cmUiRtSysMstr.h src/libcm/cmRtSysMsg.h +cmHDR += src/libcm/cmRtSys.c src/libcm/cmUiRtSysMstr.c + +cmHDR += src/libcm/cmDevCfg.h src/libcm/cmUi.h src/libcm/cmUiDrvr.h +cmSRC += src/libcm/cmDevCfg.c src/libcm/cmUi.c src/libcm/cmUiDrvr.c cmHDR += src/libcm/cmFrameFile.h src/libcm/cmFeatFile.h src/libcm/cmCsv.h src/libcm/cmAudLabelFile.h src/libcm/cmTagFile.h cmSRC += src/libcm/cmFrameFile.c src/libcm/cmFeatFile.c src/libcm/cmCsv.c src/libcm/cmAudLabelFile.c src/libcm/cmTagFile.c diff --git a/cmRtSys.c b/cmRtSys.c new file mode 100644 index 0000000..2fb2f9b --- /dev/null +++ b/cmRtSys.c @@ -0,0 +1,1465 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmAudioPort.h" +#include "cmAudioNrtDev.h" +#include "cmAudioPortFile.h" +#include "cmApBuf.h" +#include "cmJson.h" +#include "cmThread.h" +#include "cmUdpPort.h" +#include "cmUdpNet.h" +#include "cmRtSysMsg.h" +#include "cmRtSys.h" +#include "cmMidi.h" +#include "cmMidiPort.h" + +#include "cmMath.h" + +typedef enum +{ + kNoCmdId, + kEnableCbCmdId, + kDisableCbCmdId +} kRtCmdId_t; + +cmRtSysH_t cmRtSysNullHandle = { NULL }; + +struct cmRt_str; + +typedef struct +{ + struct cmRt_str* p; // pointer to the audio system instance which owns this sub-system + cmRtSysSubSys_t ss; // sub-system configuration record + cmRtSysCtx_t ctx; // DSP context + cmRtSysStatus_t status; // current runtime status of this sub-system + cmThreadH_t threadH; // audio system thread + cmTsMp1cH_t htdQueueH; // host-to-dsp thread safe msg queue + cmThreadMutexH_t engMutexH; // thread mutex and condition variable + cmUdpNetH_t netH; + bool runFl; // false during finalization otherwise true + bool statusFl; // true if regular status notifications should be sent + bool syncInputFl; + + kRtCmdId_t cmdId; // written by app thread, read by rt thread + unsigned cbEnableFl; // written by rt thread, read by app thread + + double* iMeterArray; // + double* oMeterArray; // + + unsigned statusUpdateSmpCnt; // transmit a state update msg every statusUpdateSmpCnt samples + unsigned statusUpdateSmpIdx; // state update phase + +} _cmRtCfg_t; + +typedef struct cmRt_str +{ + cmErr_t err; + _cmRtCfg_t* ssArray; + unsigned ssCnt; + unsigned waitRtSubIdx; // index of the next sub-system to try with cmRtSysIsMsgWaiting(). + cmTsMp1cH_t dthQueH; + bool initFl; // true if the audio system is initialized +} cmRt_t; + + +cmRt_t* _cmRtHandleToPtr( cmRtSysH_t h ) +{ + cmRt_t* p = (cmRt_t*)h.h; + assert(p != NULL); + return p; +} + +cmRtRC_t _cmRtError( cmRt_t* p, cmRtRC_t rc, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + cmErrVMsg(&p->err,rc,fmt,vl); + va_end(vl); + return rc; +} + +// Wrapper function to put msgs into thread safe queues and handle related errors. +cmRtRC_t _cmRtEnqueueMsg( cmRt_t* p, cmTsMp1cH_t qH, const void* msgDataPtrArray[], unsigned msgCntArray[], unsigned segCnt, const char* queueLabel ) +{ + cmRtRC_t rc = kOkRtRC; + + switch( cmTsMp1cEnqueueSegMsg(qH, msgDataPtrArray, msgCntArray, segCnt) ) + { + case kOkThRC: + break; + + case kBufFullThRC: + { + unsigned i; + unsigned byteCnt = 0; + for(i=0; idspToHostFunc. +// It is called by the DSP proces to pass msgs to the host. +// therefore it is always called from inside of _cmRtDspExecCallback(). +cmRtRC_t _cmRtDspToHostMsgCallback(struct cmRtSysCtx_str* ctx, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt) +{ + cmRt_t* p = (cmRt_t*)ctx->reserved; + assert( ctx->rtSubIdx < p->ssCnt ); + return _cmRtEnqueueMsg(p,p->dthQueH,msgDataPtrArray,msgByteCntArray,msgSegCnt,"DSP-to-Host"); +} + +cmRtRC_t _cmRtSysDspToHostSegMsg( cmRt_t* p, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt) +{ + return _cmRtEnqueueMsg(p,p->dthQueH,msgDataPtrArray,msgByteCntArray,msgSegCnt,"DSP-to-Host"); +} + +cmRtRC_t cmRtSysDspToHostSegMsg( cmRtSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + return _cmRtSysDspToHostSegMsg(p,msgDataPtrArray,msgByteCntArray,msgSegCnt); +} + +cmRtRC_t cmRtSysDspToHost( cmRtSysH_t h, const void* msgDataPtr, unsigned msgByteCnt) +{ + const void* msgDataArray[] = { msgDataPtr }; + unsigned msgByteCntArray[] = { msgByteCnt }; + return cmRtSysDspToHostSegMsg(h,msgDataArray,msgByteCntArray,1); +} + +cmRtRC_t _cmRtParseNonSubSysMsg( cmRt_t* p, const void* msg, unsigned msgByteCnt ) +{ + cmRtRC_t rc = kOkRtRC; + cmRtSysMstr_t* m = (cmRtSysMstr_t*)msg; + /* + unsigned devIdx = cmRtSysUiInstIdToDevIndex(h->instId); + unsigned chIdx = cmRtSysUiInstIdToChIndex(h->instId); + unsigned inFl = cmRtSysUiInstIdToInFlag(h->instId); + unsigned ctlId = cmRtSysUiInstIdToCtlId(h->instId); + */ + + // if the valuu associated with this msg is a mtx then set + // its mtx data area pointer to just after the msg header. + //if( cmDsvIsMtx(&h->value) ) + // h->value.u.m.u.vp = ((char*)msg) + sizeof(cmDspUiHdr_t); + + unsigned flags = m->inFl ? kInApFl : kOutApFl; + + switch( m->ctlId ) + { + + case kSliderUiRtId: // slider + cmApBufSetGain(m->devIdx,m->chIdx, flags, m->value); + break; + + case kMeterUiRtId: // meter + break; + + case kMuteUiRtId: // mute + flags += m->value == 0 ? kEnableApFl : 0; + cmApBufEnableChannel(m->devIdx,m->chIdx,flags); + break; + + case kToneUiRtId: // tone + flags += m->value > 0 ? kEnableApFl : 0; + cmApBufEnableTone(m->devIdx,m->chIdx,flags); + break; + + case kPassUiRtId: // pass + flags += m->value > 0 ? kEnableApFl : 0; + cmApBufEnablePass(m->devIdx,m->chIdx,flags); + break; + + default: + { assert(0); } + } + + return rc; +} + +// Process a UI msg sent from the host to the audio system +cmRtRC_t _cmRtHandleNonSubSysMsg( cmRt_t* p, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt ) +{ + cmRtRC_t rc = kOkRtRC; + + // if the message is contained in a single segment it can be dispatched immediately ... + if( msgSegCnt == 1 ) + rc = _cmRtParseNonSubSysMsg(p,msgDataPtrArray[0],msgByteCntArray[0]); + else + { + // ... otherwise deserialize the message into contiguous memory .... + unsigned byteCnt = 0; + unsigned i; + + for(i=0; istatus.hdr.rtSubIdx = cp->ctx.rtSubIdx; + cp->status.hdr.selId = kStatusSelRtId; + + cmApBufGetStatus( cp->ss.args.inDevIdx, kInApFl, cp->iMeterArray, cp->status.iMeterCnt, &cp->status.overflowCnt ); + cmApBufGetStatus( cp->ss.args.outDevIdx, kOutApFl, cp->oMeterArray, cp->status.oMeterCnt, &cp->status.underflowCnt ); + + unsigned iMeterByteCnt = sizeof(cp->iMeterArray[0]) * cp->status.iMeterCnt; + unsigned oMeterByteCnt = sizeof(cp->oMeterArray[0]) * cp->status.oMeterCnt; + const void* msgDataPtrArray[] = { &cp->status, cp->iMeterArray, cp->oMeterArray }; + unsigned msgByteCntArray[] = { sizeof(cp->status), iMeterByteCnt, oMeterByteCnt }; + unsigned segCnt = sizeof(msgByteCntArray)/sizeof(unsigned); + + _cmRtSysDspToHostSegMsg(cp->p,msgDataPtrArray,msgByteCntArray, segCnt ); + + return rc; +} + + +// The DSP execution callback happens through this function. +// This function is only called from inside _cmRtThreadCallback() +// with the engine mutex locked. +void _cmRtDspExecCallback( _cmRtCfg_t* cp ) +{ + + // Fill iChArray[] and oChArray[] with pointers to the incoming and outgoing sample buffers. + // Notes: + // 1) Buffers associated with disabled input/output channels will be set to NULL in iChArray[]/oChArray[]. + // 2) Buffers associated with channels marked for pass-through will be set to NULL in oChArray[]. + // 3) All samples returned in oChArray[] buffers will be set to zero. + cmApBufGetIO(cp->ss.args.inDevIdx, cp->ctx.iChArray, cp->ctx.iChCnt, cp->ss.args.outDevIdx, cp->ctx.oChArray, cp->ctx.oChCnt ); + + // call the application provided DSP process + if( cp->cbEnableFl ) + { + cp->ctx.audioRateFl = true; + cp->ss.cbFunc( &cp->ctx, 0, NULL ); + cp->ctx.audioRateFl = false; + } + + // Notice client callback enable/disable + // requests from the client thread + switch( cp->cmdId ) + { + case kNoCmdId: + break; + + case kDisableCbCmdId: + if( cp->cbEnableFl ) + cmThUIntDecr(&cp->cbEnableFl,1); + break; + + case kEnableCbCmdId: + if( cp->cbEnableFl==0) + cmThUIntIncr(&cp->cbEnableFl,1); + break; + } + + // advance the audio buffer + cmApBufAdvance( cp->ss.args.outDevIdx, kOutApFl ); + cmApBufAdvance( cp->ss.args.inDevIdx, kInApFl ); + + // handle periodic status messages to the host + if( (cp->statusUpdateSmpIdx += cp->ss.args.dspFramesPerCycle) >= cp->statusUpdateSmpCnt ) + { + cp->statusUpdateSmpIdx -= cp->statusUpdateSmpCnt; + + if( cp->statusFl ) + _cmRtSendStateStatusToHost(cp); + } + +} + +// Returns true if audio buffer is has waiting incoming samples and +// available outgoing space. +bool _cmRtBufIsReady( const _cmRtCfg_t* cp ) +{ + // if there neither the input or output device is valid + if( cp->ss.args.inDevIdx==cmInvalidIdx && cp->ss.args.outDevIdx == cmInvalidIdx ) + return false; + + bool ibFl = cmApBufIsDeviceReady(cp->ss.args.inDevIdx, kInApFl); + bool obFl = cmApBufIsDeviceReady(cp->ss.args.outDevIdx, kOutApFl); + bool iFl = (cp->ss.args.inDevIdx == cmInvalidIdx) || ibFl; + bool oFl = (cp->ss.args.outDevIdx == cmInvalidIdx) || obFl; + + //printf("br: %i %i %i %i\n",ibFl,obFl,iFl,oFl); + + return iFl && oFl; +} + + +// This is only called with _cmRtRecd.engMutexH locked +cmRtRC_t _cmRtDeliverMsgsWithLock( _cmRtCfg_t* cp ) +{ + int i; + cmRtRC_t rc = kOkThRC; + + // as long as their may be a msg wating in the incoming msg queue + for(i=0; rc == kOkThRC; ++i) + { + // if a msg is waiting transmit it via cfg->cbFunc() + if((rc = cmTsMp1cDequeueMsg(cp->htdQueueH,NULL,0)) == kOkThRC) + ++cp->status.msgCbCnt; + } + + return rc; +} + + +// This is the main audio system loop (and thread callback function). +// It blocks by waiting on a cond. var (which simultaneously unlocks a mutex). +// With the mutex unlocked messages can pass directly to the DSP process +// via calls to cmRtDeliverMsg(). +// When the audio buffers need to be serviced the audio device callback +// signals the cond. var. which results in this thread waking up (and +// simultaneously locking the mutex) as soon as the mutex is available. +bool _cmRtThreadCallback(void* arg) +{ + cmRtRC_t rc; + _cmRtCfg_t* cp = (_cmRtCfg_t*)arg; + + // lock the cmRtSys mutex + if((rc = cmThreadMutexLock(cp->engMutexH)) != kOkRtRC ) + { + _cmRtError(cp->p,rc,"The cmRtSys thread mutex lock failed."); + return false; + } + + // runFl is always set except during finalization + while( cp->runFl ) + { + + // if the buffer is NOT ready or the cmRtSys is disabled + if(_cmRtBufIsReady(cp) == false || cp->cbEnableFl==false ) + { + // block on the cond var and unlock the mutex + if( cmThreadMutexWaitOnCondVar(cp->engMutexH,false) != kOkRtRC ) + { + cmThreadMutexUnlock(cp->engMutexH); + _cmRtError(cp->p,rc,"The cmRtSys cond. var. wait failed."); + return false; + } + + // + // the cond var was signaled and the mutex is now locked + // + ++cp->status.wakeupCnt; + } + + // be sure we are still enabled and the buffer is still ready + if( 1 /*cp->runFl*/ ) + { + while( cp->runFl && _cmRtBufIsReady(cp) ) + { + ++cp->status.audioCbCnt; + + // calling this function results in callbacks to cmAudDsp.c:_cmAdUdpNetCallback() + // which in turn calls cmRtSysDeliverMsg() which queues any incoming messages + // which are then transferred to the DSP processes by the the call to + // _cmRtDeliverMsgWithLock() below. + cmUdpNetReceive(cp->netH,NULL); + + // if there are msgs waiting to be sent to the DSP process send them. + if( cp->cbEnableFl ) + if( cmTsMp1cMsgWaiting(cp->htdQueueH) ) + _cmRtDeliverMsgsWithLock(cp); + + // make the cmRtSys callback + _cmRtDspExecCallback( cp ); + + // update the signal time + cp->ctx.begSmpIdx += cp->ss.args.dspFramesPerCycle; + } + } + + } + + // unlock the mutex + cmThreadMutexUnlock(cp->engMutexH); + + return true; +} + +void _cmRtGenSignal( cmApAudioPacket_t* outPktArray, unsigned outPktCnt, bool sineFl ) +{ + static unsigned rtPhase = 0; + + //fill output with noise + unsigned i = 0,j =0, k = 0, phs = 0; + for(; iaudioBytesPtr; + + phs = a->audioFramesCnt; + + if( sineFl ) + { + for(j=0; jaudioFramesCnt; ++j) + { + cmApSample_t v = (cmApSample_t)(0.7 * sin(2*M_PI/44100.0 * rtPhase + j )); + + for(k=0; kchCnt; ++k,++dp) + *dp = v; + } + } + else + { + for(j=0; jaudioFramesCnt*a->chCnt; ++j,++dp) + *dp = (cmApSample_t)(rand() - (RAND_MAX/2))/(RAND_MAX/2); + } + } + + rtPhase += phs; + +} + +// This is the audio port callback function. +// +// _cmRtSysAudioUpdate() assumes that at most two audio device threads +// (input and output) may call it. cmApBufUpdate() is safe under these conditions +// since the input and output buffers are updated separately. +// p->syncInputFl is used to allow either the input or output thread to signal +// the condition variable. This flag is necessary to prevent both threads from simultaneously +// attempting to signal the condition variable (which will lock the system). +// +// If more than two audio device threads call the function then this function is not safe. +void _cmRtSysAudioUpdate( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) +{ + _cmRtCfg_t* cp = (_cmRtCfg_t*)(inPktArray!=NULL ? inPktArray[0].userCbPtr : outPktArray[0].userCbPtr); + + ++cp->status.updateCnt; + + if( cp->runFl ) + { + + // transfer incoming/outgoing samples from/to the audio device + cmApBufUpdate(inPktArray,inPktCnt,outPktArray,outPktCnt); + + + // generate a test signal + //_cmRtGenSignal( cmApAudioPacket_t* outPktArray, unsigned outPktCnt, bool sineFl ); + //return; + + bool testBufFl = (cp->syncInputFl==true && inPktCnt>0) || (cp->syncInputFl==false && outPktCnt>0); + + //printf("%i %i %i %i\n",testBufFl,cp->syncInputFl,inPktCnt,outPktCnt); + + // if the input/output buffer contain samples to be processed then signal the condition variable + // - this will cause the audio system thread to unblock and the used defined DSP process will be called. + if( testBufFl && _cmRtBufIsReady(cp) ) + { + if( cmThreadMutexSignalCondVar(cp->engMutexH) != kOkThRC ) + _cmRtError(cp->p,kMutexErrRtRC,"CmRtSys signal cond. var. failed."); + + } + } + +} + +// Called when MIDI messages arrive from external MIDI ports. +void _cmRtSysMidiCallback( const cmMidiPacket_t* pktArray, unsigned pktCnt ) +{ + unsigned i; + for(i=0; icbDataPtr); + + if( !cp->runFl ) + continue; + + cmRtSysH_t asH; + asH.h = cp->p; + + cmRtSysMidi_t m; + m.hdr.rtSubIdx = cp->ctx.rtSubIdx; + m.hdr.selId = kMidiMsgArraySelRtId; + m.devIdx = pkt->devIdx; + m.portIdx = pkt->portIdx; + m.msgCnt = pkt->msgCnt; + + + /* + unsigned selId = kMidiMsgArraySelRtId; + const void* msgPtrArray[] = { &cp->ctx.rtSubIdx, &selId, &pkt->devIdx, &pkt->portIdx, &pkt->msgCnt, pkt->msgArray }; + unsigned msgByteCntArray[] = { sizeof(cp->ctx.rtSubIdx), sizeof(selId), sizeof(pkt->devIdx), sizeof(pkt->portIdx), sizeof(pkt->msgCnt), pkt->msgCnt*sizeof(cmMidiMsg) }; + unsigned msgSegCnt = sizeof(msgByteCntArray)/sizeof(unsigned); + */ + + const void* msgPtrArray[] = { &m, pkt->msgArray }; + unsigned msgByteCntArray[] = { sizeof(m), pkt->msgCnt*sizeof(cmMidiMsg) }; + unsigned msgSegCnt = sizeof(msgByteCntArray)/sizeof(unsigned); + + cmRtSysDeliverSegMsg(asH,msgPtrArray,msgByteCntArray,msgSegCnt,cmInvalidId); + } + +} + +cmRtRC_t cmRtSysAllocate( cmRtSysH_t* hp, cmRpt_t* rpt, const cmRtSysCfg_t* cfg ) +{ + cmRtRC_t rc; + + if((rc = cmRtSysFree(hp)) != kOkRtRC ) + return rc; + + cmRt_t* p = cmMemAllocZ( cmRt_t, 1 ); + + cmErrSetup(&p->err,rpt,"Audio System"); + + hp->h = p; + + if( cfg != NULL ) + if((rc = cmRtSysInitialize( *hp, cfg )) != kOkRtRC ) + cmRtSysFree(hp); + + return rc; +} + +cmRtRC_t cmRtSysFree( cmRtSysH_t* hp ) +{ + cmRtRC_t rc; + + if( hp == NULL || hp->h == NULL ) + return kOkRtRC; + + if((rc = cmRtSysFinalize(*hp)) != kOkRtRC ) + return rc; + + cmRt_t* p = _cmRtHandleToPtr(*hp); + + cmMemFree(p); + + hp->h = NULL; + + return rc; +} + +cmRtRC_t _cmRtSysEnable( cmRt_t* p, bool enableFl ) +{ + cmRtRC_t rc = kOkRtRC; + + unsigned i; + unsigned n; + unsigned tickMs = 20; + unsigned timeOutMs = 10000; + + for(i=0; issCnt; ++i) + { + _cmRtCfg_t* cp = p->ssArray + i; + + if( enableFl ) + { + cp->cmdId = kNoCmdId; + cmThUIntIncr(&cp->cmdId,kEnableCbCmdId); + + for(n=0; ncbEnableFl==false; n+=tickMs ) + cmSleepMs(tickMs); + + cmThUIntDecr(&cp->cmdId,kEnableCbCmdId); + + } + else + { + cp->cmdId = kNoCmdId; + cmThUIntIncr(&cp->cmdId,kDisableCbCmdId); + + // wait for the rt thread to return from a client callbacks + for(n=0; ncbEnableFl; n+=tickMs ) + cmSleepMs(tickMs); + + cmThUIntDecr(&cp->cmdId,kDisableCbCmdId); + + } + + if( n >= timeOutMs ) + rc = cmErrMsg(&p->err,kTimeOutErrRtRC,"RT System %s timed out after %i milliseconds.",enableFl?"enable":"disable",timeOutMs); + + } + + return rc; +} + +cmRtRC_t _cmRtSysFinalize( cmRt_t* p ) +{ + cmRtRC_t rc = kOkRtRC; + unsigned i; + + // mark the audio system as NOT initialized + p->initFl = false; + + // be sure all audio callbacks are disabled before continuing. + if((rc = _cmRtSysEnable(p,false)) != kOkRtRC ) + return _cmRtError(p,rc,"Audio system finalize failed because device halting failed."); + + // stop the audio devices + for(i=0; issCnt; ++i) + { + _cmRtCfg_t* cp = p->ssArray + i; + + // stop the input device + if((rc = cmApDeviceStop( cp->ss.args.inDevIdx )) != kOkRtRC ) + return _cmRtError(p,kAudioDevStopFailRtRC,"The audio input device stop failed."); + + // stop the output device + if((rc = cmApDeviceStop( cp->ss.args.outDevIdx )) != kOkRtRC ) + return _cmRtError(p,kAudioDevStopFailRtRC,"The audio output device stop failed."); + } + + + for(i=0; issCnt; ++i) + { + _cmRtCfg_t* cp = p->ssArray + i; + + if( cmThreadIsValid( cp->threadH )) + { + // inform the thread that it should exit + cp->runFl = false; + cp->statusFl = false; + + + // signal the cond var to cause the thread to run + if((rc = cmThreadMutexSignalCondVar(cp->engMutexH)) != kOkThRC ) + _cmRtError(p,kMutexErrRtRC,"Finalize signal cond. var. failed."); + + // wait to take control of the mutex - this will occur when the thread function exits + if((rc = cmThreadMutexLock(cp->engMutexH)) != kOkThRC ) + _cmRtError(p,kMutexErrRtRC,"Finalize lock failed."); + + // unlock the mutex because it is no longer needed and must be unlocked to be destroyed + if((rc = cmThreadMutexUnlock(cp->engMutexH)) != kOkThRC ) + _cmRtError(p,kMutexErrRtRC,"Finalize unlock failed."); + + // destroy the thread + if((rc = cmThreadDestroy( &cp->threadH )) != kOkThRC ) + _cmRtError(p,kThreadErrRtRC,"Thread destroy failed."); + + } + + // destroy the mutex + if( cmThreadMutexIsValid(cp->engMutexH) ) + if((rc = cmThreadMutexDestroy( &cp->engMutexH )) != kOkThRC ) + _cmRtError(p,kMutexErrRtRC,"Mutex destroy failed."); + + + // remove the MIDI callback + if( cmMpIsInitialized() && cmMpUsesCallback(-1,-1, _cmRtSysMidiCallback, cp) ) + if( cmMpRemoveCallback( -1, -1, _cmRtSysMidiCallback, cp ) != kOkMpRC ) + _cmRtError(p,kMidiSysFailRtRC,"MIDI callback removal failed."); + + // destroy the host-to-dsp msg queue + if( cmTsMp1cIsValid(cp->htdQueueH ) ) + if((rc = cmTsMp1cDestroy( &cp->htdQueueH )) != kOkThRC ) + _cmRtError(p,kTsQueueErrRtRC,"Host-to-DSP msg queue destroy failed."); + + // destroy the dsp-to-host msg queue + if( cmTsMp1cIsValid(p->dthQueH) ) + if((rc = cmTsMp1cDestroy( &p->dthQueH )) != kOkThRC ) + _cmRtError(p,kTsQueueErrRtRC,"DSP-to-Host msg queue destroy failed."); + + + cmMemPtrFree(&cp->ctx.iChArray); + cmMemPtrFree(&cp->ctx.oChArray); + cp->ctx.iChCnt = 0; + cp->ctx.oChCnt = 0; + + cmMemPtrFree(&cp->iMeterArray); + cmMemPtrFree(&cp->oMeterArray); + cp->status.iMeterCnt = 0; + cp->status.oMeterCnt = 0; + + } + + + cmMemPtrFree(&p->ssArray); + p->ssCnt = 0; + + return rc; +} + +// A given device may be used as an input device exactly once and an output device exactly once. +// When the input to a given device is used by one sub-system and the output is used by another +// then both sub-systems must use the same srate,devFramesPerCycle, audioBufCnt and dspFramesPerCycle. +cmRtRC_t _cmRtSysValidate( cmErr_t* err, const cmRtSysCfg_t* cfg ) +{ + unsigned i,j,k; + for(i=0; i<2; ++i) + { + // examine input devices - then output devices + bool inputFl = i==0; + bool outputFl = !inputFl; + + for(j=0; jssCnt; ++j) + { + cmRtSysArgs_t* s0 = &cfg->ssArray[j].args; + unsigned devIdx = inputFl ? s0->inDevIdx : s0->outDevIdx; + + for(k=0; kssCnt && devIdx != cmInvalidIdx; ++k) + if( k != j ) + { + cmRtSysArgs_t* s1 = &cfg->ssArray[k].args; + + // if the device was used as input or output multple times then signal an error + if( (inputFl && (s1->inDevIdx == devIdx) && s1->inDevIdx != cmInvalidIdx) || (outputFl && (s1->outDevIdx == devIdx) && s1->outDevIdx != cmInvalidIdx) ) + return cmErrMsg(err,kInvalidArgRtRC,"The device %i was used as an %s by multiple sub-systems.", devIdx, inputFl ? "input" : "output"); + + // if this device is being used by another subsystem ... + if( (inputFl && (s1->outDevIdx == devIdx) && s1->inDevIdx != cmInvalidIdx) || (outputFl && (s1->outDevIdx == devIdx) && s1->outDevIdx != cmInvalidIdx ) ) + { + // ... then some of its buffer spec's must match + if( s0->srate != s1->srate || s0->audioBufCnt != s1->audioBufCnt || s0->dspFramesPerCycle != s1->dspFramesPerCycle || s0->devFramesPerCycle != s1->devFramesPerCycle ) + return cmErrMsg(err,kInvalidArgRtRC,"The device %i is used by different sub-system with different audio buffer parameters.",devIdx); + } + } + } + } + + return kOkRtRC; +} + +cmRtRC_t cmRtSysInitialize( cmRtSysH_t h, const cmRtSysCfg_t* cfg ) +{ + cmRtRC_t rc; + unsigned i; + cmRt_t* p = _cmRtHandleToPtr(h); + + // validate the device setup + if((rc =_cmRtSysValidate(&p->err, cfg )) != kOkRtRC ) + return rc; + + // always finalize before iniitalize + if((rc = cmRtSysFinalize(h)) != kOkRtRC ) + return rc; + + + p->ssArray = cmMemAllocZ( _cmRtCfg_t, cfg->ssCnt ); + p->ssCnt = cfg->ssCnt; + + for(i=0; issCnt; ++i) + { + _cmRtCfg_t* cp = p->ssArray + i; + const cmRtSysSubSys_t* ss = cfg->ssArray + i; + + cp->p = p; + cp->ss = *ss; // copy the cfg into the internal audio system state + cp->runFl = false; + cp->statusFl = false; + cp->ctx.reserved = p; + cp->ctx.rtSubIdx = i; + cp->ctx.ss = &cp->ss; + cp->ctx.begSmpIdx = 0; + cp->ctx.dspToHostFunc = _cmRtDspToHostMsgCallback; + + // validate the input device index + if( ss->args.inDevIdx != cmInvalidIdx && ss->args.inDevIdx >= cmApDeviceCount() ) + { + rc = _cmRtError(p,kAudioDevSetupErrRtRC,"The audio input device index %i is invalid.",ss->args.inDevIdx); + goto errLabel; + } + + // validate the output device index + if( ss->args.outDevIdx != cmInvalidIdx && ss->args.outDevIdx >= cmApDeviceCount() ) + { + rc = _cmRtError(p,kAudioDevSetupErrRtRC,"The audio output device index %i is invalid.",ss->args.outDevIdx); + goto errLabel; + } + + // setup the input device + if( ss->args.inDevIdx != cmInvalidIdx ) + if((rc = cmApDeviceSetup( ss->args.inDevIdx, ss->args.srate, ss->args.devFramesPerCycle, _cmRtSysAudioUpdate, cp )) != kOkRtRC ) + { + rc = _cmRtError(p,kAudioDevSetupErrRtRC,"Audio input device setup failed."); + goto errLabel; + } + + // setup the output device + if( ss->args.outDevIdx != ss->args.inDevIdx && ss->args.outDevIdx != cmInvalidIdx ) + if((rc = cmApDeviceSetup( ss->args.outDevIdx, ss->args.srate, ss->args.devFramesPerCycle, _cmRtSysAudioUpdate, cp )) != kOkRtRC ) + { + rc = _cmRtError(p,kAudioDevSetupErrRtRC,"Audio output device setup failed."); + goto errLabel; + } + + // setup the input device buffer + if( ss->args.inDevIdx != cmInvalidIdx ) + if((rc = cmApBufSetup( ss->args.inDevIdx, ss->args.srate, ss->args.dspFramesPerCycle, ss->args.audioBufCnt, cmApDeviceChannelCount(ss->args.inDevIdx, true), ss->args.devFramesPerCycle, cmApDeviceChannelCount(ss->args.inDevIdx, false), ss->args.devFramesPerCycle )) != kOkRtRC ) + { + rc = _cmRtError(p,kAudioBufSetupErrRtRC,"Audio buffer input setup failed."); + goto errLabel; + } + + cmApBufEnableMeter(ss->args.inDevIdx, -1, kInApFl | kEnableApFl ); + cmApBufEnableMeter(ss->args.outDevIdx,-1, kOutApFl | kEnableApFl ); + + // setup the input audio buffer ptr array - used to send input audio to the DSP system in _cmRtDspExecCallback() + if((cp->ctx.iChCnt = cmApDeviceChannelCount(ss->args.inDevIdx, true)) != 0 ) + cp->ctx.iChArray = cmMemAllocZ( cmSample_t*, cp->ctx.iChCnt ); + + // setup the output device buffer + if( ss->args.outDevIdx != ss->args.inDevIdx ) + if((rc = cmApBufSetup( ss->args.outDevIdx, ss->args.srate, ss->args.dspFramesPerCycle, ss->args.audioBufCnt, cmApDeviceChannelCount(ss->args.outDevIdx, true), ss->args.devFramesPerCycle, cmApDeviceChannelCount(ss->args.outDevIdx, false), ss->args.devFramesPerCycle )) != kOkRtRC ) + return _cmRtError(p,kAudioBufSetupErrRtRC,"Audio buffer ouput device setup failed."); + + // setup the output audio buffer ptr array - used to recv output audio from the DSP system in _cmRtDspExecCallback() + if((cp->ctx.oChCnt = cmApDeviceChannelCount(ss->args.outDevIdx, false)) != 0 ) + cp->ctx.oChArray = cmMemAllocZ( cmSample_t*, cp->ctx.oChCnt ); + + // determine the sync source + cp->syncInputFl = ss->args.syncInputFl; + + // if sync'ing to an unavailable device then sync to the available device + if( ss->args.syncInputFl && cp->ctx.iChCnt == 0 ) + cp->syncInputFl = false; + + if( ss->args.syncInputFl==false && cp->ctx.oChCnt == 0 ) + cp->syncInputFl = true; + + // setup the status record + cp->status.hdr.rtSubIdx = cp->ctx.rtSubIdx; + cp->status.iDevIdx = ss->args.inDevIdx; + cp->status.oDevIdx = ss->args.outDevIdx; + cp->status.iMeterCnt = cp->ctx.iChCnt; + cp->status.oMeterCnt = cp->ctx.oChCnt; + cp->iMeterArray = cmMemAllocZ( double, cp->status.iMeterCnt ); + cp->oMeterArray = cmMemAllocZ( double, cp->status.oMeterCnt ); + cp->netH = cfg->netH; + + // create the audio System thread + if((rc = cmThreadCreate( &cp->threadH, _cmRtThreadCallback, cp, ss->args.rpt )) != kOkThRC ) + { + rc = _cmRtError(p,kThreadErrRtRC,"Thread create failed."); + goto errLabel; + } + + // create the audio System mutex + if((rc = cmThreadMutexCreate( &cp->engMutexH, ss->args.rpt )) != kOkThRC ) + { + rc = _cmRtError(p,kMutexErrRtRC,"Thread mutex create failed."); + goto errLabel; + } + + // create the host-to-dsp thread safe msg queue + if((rc = cmTsMp1cCreate( &cp->htdQueueH, ss->args.msgQueueByteCnt, ss->cbFunc, &cp->ctx, ss->args.rpt )) != kOkThRC ) + { + rc = _cmRtError(p,kTsQueueErrRtRC,"Host-to-DSP msg queue create failed."); + goto errLabel; + } + + // create the dsp-to-host thread safe msg queue + if( cmTsMp1cIsValid( p->dthQueH ) == false ) + { + if((rc = cmTsMp1cCreate( &p->dthQueH, ss->args.msgQueueByteCnt, cfg->clientCbFunc, cfg->clientCbData, ss->args.rpt )) != kOkThRC ) + { + rc = _cmRtError(p,kTsQueueErrRtRC,"DSP-to-Host msg queue create failed."); + goto errLabel; + } + } + + //cp->dthQueueH = p->dthQueH; + + // install an external MIDI port callback handler for incoming MIDI messages + if( cmMpIsInitialized() ) + if( cmMpInstallCallback( -1, -1, _cmRtSysMidiCallback, cp ) != kOkMpRC ) + { + rc = _cmRtError(p,kMidiSysFailRtRC,"MIDI system callback installation failed."); + goto errLabel; + } + + // setup the sub-system status notification + cp->statusUpdateSmpCnt = floor(cmApBufMeterMs() * cp->ss.args.srate / 1000.0 ); + cp->statusUpdateSmpIdx = 0; + + cp->runFl = true; + + // start the audio System thread + if( cmThreadPause( cp->threadH, 0 ) != kOkThRC ) + { + rc = _cmRtError(p,kThreadErrRtRC,"Thread start failed."); + goto errLabel; + } + } + + + //_cmRtHostInitNotify(p); + + for(i=0; issCnt; ++i) + { + _cmRtCfg_t* cp = p->ssArray + i; + + // start the input device + if((rc = cmApDeviceStart( cp->ss.args.inDevIdx )) != kOkRtRC ) + return _cmRtError(p,kAudioDevStartFailRtRC,"The audio input device start failed."); + + // start the output device + if( cmApDeviceStart( cp->ss.args.outDevIdx ) != kOkRtRC ) + return _cmRtError(p,kAudioDevStartFailRtRC,"The audio ouput device start failed."); + } + + p->initFl = true; + + errLabel: + if( rc != kOkRtRC ) + _cmRtSysFinalize(p); + + return rc; +} + + +cmRtRC_t cmRtSysFinalize(cmRtSysH_t h ) +{ + cmRtRC_t rc = kOkRtRC; + + if( cmRtSysHandleIsValid(h) == false ) + return rc; + + cmRt_t* p = _cmRtHandleToPtr(h); + + rc = _cmRtSysFinalize(p); + + h.h = NULL; + + return rc; +} + + +bool cmRtSysIsInitialized( cmRtSysH_t h ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + return p->initFl; +} + +cmRtRC_t _cmRtSysVerifyInit( cmRt_t* p, bool errFl ) +{ + if( p->initFl == false ) + { + + // if the last msg generated was also a not init msg then don't + // generate another message - just return the error + if( errFl ) + if( cmErrLastRC(&p->err) != kNotInitRtRC ) + cmErrMsg(&p->err,kNotInitRtRC,"The audio system is not initialized."); + + return kNotInitRtRC; + } + + return kOkRtRC; +} + + +bool cmRtSysIsEnabled( cmRtSysH_t h ) +{ + if( cmRtSysIsInitialized(h) == false ) + return false; + + cmRt_t* p = _cmRtHandleToPtr(h); + unsigned i; + for(i=0; issCnt; ++i) + if( p->ssArray[i].cbEnableFl ) + return true; + + return false; +} + + +cmRtRC_t cmRtSysEnable( cmRtSysH_t h, bool enableFl ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + return _cmRtSysEnable(p,enableFl); +} + +cmRtRC_t cmRtSysDeliverSegMsg( cmRtSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt, unsigned srcNetNodeId ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + cmRtRC_t rc; + + // the system must be initialized to use this function + if((rc = _cmRtSysVerifyInit(p,true)) != kOkRtRC ) + return rc; + + if( msgSegCnt == 0 ) + return kOkRtRC; + + // BUG BUG BUG - there is no reason that both the rtSubIdx and the selId must + // be in the first segment but it would be nice. + assert( msgByteCntArray[0] >= 2*sizeof(unsigned) || (msgSegCnt>1 && msgByteCntArray[0]==sizeof(unsigned) && msgByteCntArray[1]>=sizeof(unsigned)) ); + + // The audio sub-system index is always the first field of the msg + // and the msg selector id is always the second field + + unsigned* array = (unsigned*)msgDataPtrArray[0]; + unsigned rtSubIdx = array[0]; + unsigned selId = array[1]; + + if( selId == kUiMstrSelRtId ) + return _cmRtHandleNonSubSysMsg( p, msgDataPtrArray, msgByteCntArray, msgSegCnt ); + + if( selId == kNetSyncSelRtId ) + { + assert( msgSegCnt==1); + assert( rtSubIdx < p->ssCnt ); + p->ssArray[rtSubIdx].ctx.srcNetNodeId = srcNetNodeId; + p->ssArray[rtSubIdx].ss.cbFunc(&p->ssArray[rtSubIdx].ctx,msgByteCntArray[0],msgDataPtrArray[0]); + return kOkRtRC; + } + + return _cmRtEnqueueMsg(p,p->ssArray[rtSubIdx].htdQueueH,msgDataPtrArray,msgByteCntArray,msgSegCnt,"Host-to-DSP"); +} + +cmRtRC_t cmRtSysDeliverMsg( cmRtSysH_t h, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ) +{ + const void* msgDataPtrArray[] = { msgPtr }; + unsigned msgByteCntArray[] = { msgByteCnt }; + return cmRtSysDeliverSegMsg(h,msgDataPtrArray,msgByteCntArray,1,srcNetNodeId); +} + +cmRtRC_t cmRtSysDeliverIdMsg( cmRtSysH_t h, unsigned rtSubIdx, unsigned id, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ) +{ + cmRtRC_t rc; + cmRt_t* p = _cmRtHandleToPtr(h); + + // the system must be initialized to use this function + if((rc = _cmRtSysVerifyInit(p,true)) != kOkRtRC ) + return rc; + + const void* msgDataPtrArray[] = { &rtSubIdx, &id, msgPtr }; + unsigned msgByteCntArray[] = { sizeof(rtSubIdx), sizeof(id), msgByteCnt }; + return cmRtSysDeliverSegMsg(h,msgDataPtrArray,msgByteCntArray,3,srcNetNodeId); +} + +unsigned cmRtSysIsMsgWaiting( cmRtSysH_t h ) +{ + cmRtRC_t rc; + cmRt_t* p = _cmRtHandleToPtr(h); + + // the system must be initialized to use this function + if((rc = _cmRtSysVerifyInit(p,false)) != kOkRtRC ) + return 0; + + unsigned n = 0; + unsigned retByteCnt; + + for(n=0; n < p->ssCnt; ++n ) + { + if( (retByteCnt = cmTsMp1cDequeueMsgByteCount(p->dthQueH)) > 0 ) + return retByteCnt; + + p->waitRtSubIdx = (p->waitRtSubIdx + 1) % p->ssCnt; + } + + return 0; +} + +cmRtRC_t cmRtSysReceiveMsg( cmRtSysH_t h, void* msgDataPtr, unsigned msgByteCnt ) +{ + cmRtRC_t rc; + cmRt_t* p = _cmRtHandleToPtr(h); + + // the system must be initialized to use this function + if((rc = _cmRtSysVerifyInit(p,true)) != kOkRtRC ) + return rc; + + //switch( cmTsMp1cDequeueMsg(p->ssArray[p->waitRtSubIdx].dthQueueH,msgDataPtr,msgByteCnt) ) + switch( cmTsMp1cDequeueMsg(p->dthQueH,msgDataPtr,msgByteCnt) ) + { + case kOkThRC: + p->waitRtSubIdx = (p->waitRtSubIdx + 1) % p->ssCnt; + return kOkRtRC; + + case kBufTooSmallThRC: + return kBufTooSmallRtRC; + + case kBufEmptyThRC: + return kNoMsgWaitingRtRC; + } + + return _cmRtError(p,kTsQueueErrRtRC,"A deque operation failed on the DSP-to-Host message queue."); +} + + +void cmRtSysStatus( cmRtSysH_t h, unsigned rtSubIdx, cmRtSysStatus_t* statusPtr ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + + // the system must be initialized to use this function + if( _cmRtSysVerifyInit(p,true) != kOkRtRC ) + return; + + if( rtSubIdx < p->ssCnt ) + *statusPtr = p->ssArray[rtSubIdx].status; +} + +void cmRtSysStatusNotifyEnable( cmRtSysH_t h, unsigned rtSubIdx, bool enableFl ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + + // the system must be initialized to use this function + if( _cmRtSysVerifyInit(p,true) != kOkRtRC ) + return; + + unsigned i = rtSubIdx == cmInvalidIdx ? 0 : rtSubIdx; + unsigned n = rtSubIdx == cmInvalidIdx ? p->ssCnt : rtSubIdx+1; + for(; issArray[i].statusFl = enableFl; +} + +bool cmRtSysHandleIsValid( cmRtSysH_t h ) +{ return h.h != NULL; } + +cmRtSysCtx_t* cmRtSysContext( cmRtSysH_t h, unsigned rtSubIdx ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + + if( _cmRtSysVerifyInit(p,true) != kOkRtRC ) + return NULL; + + return &p->ssArray[rtSubIdx].ctx; +} + +unsigned cmRtSysSubSystemCount( cmRtSysH_t h ) +{ + cmRt_t* p = _cmRtHandleToPtr(h); + if( _cmRtSysVerifyInit(p,true) != kOkRtRC ) + return 0; + + return p->ssCnt; +} + +//=========================================================================================================================== +// +// cmRtTest() +// + +/// [cmRtSysTest] + +typedef struct +{ + double hz; // current synth frq + long phs; // current synth phase + double srate; // audio sample rate + unsigned cbCnt; // DSP cycle count + bool synthFl; // true=synth false=pass through +} _cmRtTestCbRecd; + +typedef struct +{ + unsigned rtSubIdx; // rtSubIdx must always be the first field in the msg + unsigned id; // 0 = set DSP Hz, 1 = report cbCount to host + double hz; + unsigned uint; +} _cmRtTestMsg; + + +long _cmRtSynthSine( _cmRtTestCbRecd* r, cmApSample_t* p, unsigned chCnt, unsigned frmCnt ) +{ + long ph = 0; + unsigned i; + + + for(i=0; iphs; + for(j=0; jhz * ph / r->srate )); + } + + return ph; +} + +unsigned _cmRtTestChIdx = 0; + +cmRC_t _cmRtTestCb( void* cbPtr, unsigned msgByteCnt, const void* msgDataPtr ) +{ + cmRC_t rc = cmOkRC; + cmRtSysCtx_t* ctx = (cmRtSysCtx_t*)cbPtr; + cmRtSysSubSys_t* ss = ctx->ss; + _cmRtTestCbRecd* r = (_cmRtTestCbRecd*)ss->cbDataPtr; + + // update the calback counter + ++r->cbCnt; + + // if this is an audio update request + if( msgByteCnt == 0 ) + { + unsigned i; + if( r->synthFl ) + { + long phs = 0; + if(0) + { + for(i=0; ioChCnt; ++i) + if( ctx->oChArray[i] != NULL ) + phs = _cmRtSynthSine(r, ctx->oChArray[i], 1, ss->args.dspFramesPerCycle ); + } + else + { + if( _cmRtTestChIdx < ctx->oChCnt ) + phs = _cmRtSynthSine(r, ctx->oChArray[_cmRtTestChIdx], 1, ss->args.dspFramesPerCycle ); + } + + r->phs = phs; + } + else + { + // BUG BUG BUG - this assumes that the input and output channels are the same. + unsigned chCnt = cmMin(ctx->oChCnt,ctx->iChCnt); + for(i=0; ioChArray[i],ctx->iChArray[i],sizeof(cmSample_t)*ss->args.dspFramesPerCycle); + } + + } + else // ... otherwise it is a msg for the DSP process from the host + { + _cmRtTestMsg* msg = (_cmRtTestMsg*)msgDataPtr; + + msg->rtSubIdx = ctx->rtSubIdx; + + switch(msg->id) + { + case 0: + r->hz = msg->hz; + break; + + case 1: + msg->uint = r->cbCnt; + msgByteCnt = sizeof(_cmRtTestMsg); + rc = ctx->dspToHostFunc(ctx,(const void **)&msg,&msgByteCnt,1); + break; + } + + } + + return rc; +} + +// print the usage message for cmAudioPortTest.c +void _cmRtPrintUsage( cmRpt_t* rpt ) +{ +char msg[] = + "cmRtSysTest() command switches:\n" + "-r -c -b -f -i -o -m -d -t -p -h \n" + "\n" + "-r = sample rate (48000)\n" + "-c = audio channels (2)\n" + "-b = count of buffers (3)\n" + "-f = count of samples per buffer (512)\n" + "-i = input device index (0)\n" + "-o = output device index (2)\n" + "-m = message queue byte count (1024)\n" + "-d = samples per DSP frame (64)\n" + "-s = true: sync to input port false: sync to output port\n" + "-t = copy input to output otherwise synthesize a 1000 Hz sine (false)\n" + "-p = report but don't start audio devices\n" + "-h = print this usage message\n"; + + cmRptPrintf(rpt,"%s",msg); +} + +// Get a command line option. +int _cmRtGetOpt( int argc, const char* argv[], const char* label, int defaultVal, bool boolFl ) +{ + int i = 0; + for(; i 0 ) + { + char buf[ msgByteCnt ]; + + // rcv a msg from the DSP process + if( cmRtSysReceiveMsg(h,buf,msgByteCnt) == kOkRtRC ) + { + _cmRtTestMsg* msg = (_cmRtTestMsg*)buf; + switch(msg->id) + { + case 1: + printf("RCV: Callback count:%i\n",msg->uint); + break; + } + + } + } + + // report the audio buffer status + //cmApBufReport(ss.args.rpt); + } + + // stop the audio system + cmRtSysEnable(h,false); + + + goto exitLabel; + + errLabel: + printf("AUDIO SYSTEM TEST ERROR\n"); + + exitLabel: + + cmRtSysFree(&h); + cmApFinalize(); + cmApFileFree(); + cmApNrtFree(); + cmApBufFinalize(); + +} + +/// [cmRtSysTest] diff --git a/cmRtSys.h b/cmRtSys.h new file mode 100644 index 0000000..89d9f01 --- /dev/null +++ b/cmRtSys.h @@ -0,0 +1,314 @@ +// cmRtSys.h +// Implements a real-time audio processing engine. +// +// The audio system is composed a collection of independent sub-systems. +// Each sub-system maintains a thread which runs asynchrounsly +// from the application, the MIDI devices, and the audio devices. +// To faciliate communication between these components each sub-system maintains +// two thread-safe data buffers one for control information and a second +// for audio data. +// +// The audio devices are the primary driver for the system. +// Callbacks from the audio devices (See #cmApCallbackPtr_t) +// inserts incoming audio samples into the audio +// record buffers and extracts samples from the playback buffer. +// When sufficient incoming samples and outgoing empty buffer space exists +// a sub-system thread is waken up by the callback. This triggers a DSP audio +// processing cycle which empties/fills the audio buffers. During a DSP +// processing cycle control messages from the application and MIDI are blocked and +// buffered. Upon completetion of the DSP cycle a control message +// transfer cycles occurs - buffered incoming messages are passed to +// the DSP system and messages originating in the DSP system are +// buffered by the audio system for later pickup by the application +// or MIDI system. +// +// Note that control messages that arrive when the DSP cycle is not +// occurring can pass directly through to the DSP system. +// +// The DSP system sends messages back to the host by calling +// cmRtDspToHostFunc_t provided by cmRtSysCtx_t. These +// calls are always made from within an audio system call to +// audio or control update within cmRtCallback_t. cmRtDspToHostFunc_t +// simply stores the message in a message buffer. The host picks +// up the message at some later time when it notices that messages +// are waiting via polling cmRtSysIsMsgWaiting(). +// +// Implementation: \n +// The audio sub-systems work by maintaining an internal thread +// which blocks on a mutex condition variable. +// While the thread is blocked the mutex is unlocked allowing messages +// to pass directly through to the DSP procedure via cmRtCallback(). +// +// Periodic calls from running audio devices update the audio buffer. +// When the audio buffer has input samples waiting and output space +// available the condition variable is signaled, the mutex is +// then automatically locked by the system, and the DSP execution +// procedure is called via cmRtCallback(). +// +// Messages arriving while the mutex is locked are queued and +// delivered to the DSP procedure at the end of the DSP execution +// procedure. +// +// Usage example and testing code: +// See cmRtSysTest(). +// \snippet cmRtSys.c cmRtSysTest + +#ifndef cmRtSys_h +#define cmRtSys_h + +#ifdef __cplusplus +extern "C" { +#endif + + // Audio system result codes + enum + { + kOkRtRC = cmOkRC, + kThreadErrRtRC, + kMutexErrRtRC, + kTsQueueErrRtRC, + kMsgEnqueueFailRtRC, + kAudioDevSetupErrRtRC, + kAudioBufSetupErrRtRC, + kAudioDevStartFailRtRC, + kAudioDevStopFailRtRC, + kBufTooSmallRtRC, + kNoMsgWaitingRtRC, + kMidiSysFailRtRC, + kMsgSerializeFailRtRC, + kStateBufFailRtRC, + kInvalidArgRtRC, + kNotInitRtRC, + kTimeOutErrRtRC + }; + + enum + { + kAsDfltMsgQueueByteCnt = 0xffff, + kAsDfltDevFramesPerCycle = 512, + kAsDfltDspFramesPerCycle = 64, + kAsDfltBufCnt = 3, + kAsDfltSrate = 44100, + kAsDfltSyncToInputFl = 1, + kAsDfltMinMeterMs = 10, + kAsDfltMeterMs = 50, + kAsDfltMaxMeterMs = 1000 + }; + + typedef cmHandle_t cmRtSysH_t; //< Audio system handle type + typedef unsigned cmRtRC_t; //< Audio system result code + + struct cmRtSysCtx_str; + + // + // DSP system callback function. + // + // This is the sole point of entry into the DSP system while the audio system is running. + // + // ctxPtr is pointer to a cmRtSysCtx_t record. + // + // This function is called under two circumstances: + // + // 1) To notify the DSP system that the audio input/output buffers need to be serviced. + // This is a perioidic request which the DSP system uses as its execution trigger. + // cmRtSysCtx_t.audioRateFl is set to true to indicate this type of callback. + // + // 2) To pass messages from the host application to the DSP system. + // The DSP system is asyncronous with the host because it executes in the + // audio system thread rather than the host thread. The cmRtSysDeliverMsg() + // function synchronizes incoming messages with the internal audio system + // thread to prevent thread collisions. + // + // Notes: + // This callback is always made with the internal audio system mutex locked. + // + // The signal time covered by the callback is from + // ctx->begSmpIdx to ctx->begSmpIdx+cfg->dspFramesPerCycle. + // + // The return value is currently not used. + typedef cmRC_t (*cmRtCallback_t)(void* ctxPtr, unsigned msgByteCnt, const void* msgDataPtr ); + + + // Audio device sub-sytem configuration record + typedef struct cmRtSysArgs_str + { + cmRpt_t* rpt; // system console object + unsigned inDevIdx; // input audio device + unsigned outDevIdx; // output audio device + bool syncInputFl; // true/false sync the DSP update callbacks with audio input/output + unsigned msgQueueByteCnt; // Size of the internal msg queue used to buffer msgs arriving via cmRtSysDeliverMsg(). + unsigned devFramesPerCycle; // (512) Audio device samples per channel per device update buffer. + unsigned dspFramesPerCycle; // (64) Audio samples per channel per DSP cycle. + unsigned audioBufCnt; // (3) Audio device buffers. + double srate; // Audio sample rate. + } cmRtSysArgs_t; + + // Audio sub-system configuration record. + // This record is provided by the host to configure the audio system + // via cmRtSystemAllocate() or cmRtSystemInitialize(). + typedef struct cmRtSysSubSys_str + { + cmRtSysArgs_t args; // Audio device configuration + cmRtCallback_t cbFunc; // DSP system entry point function. + void* cbDataPtr; // Host provided data for the DSP system callback. + } cmRtSysSubSys_t; + + + // Signature of a callback function provided by the audio system to receive messages + // from the DSP system for later dispatch to the host application. + // This declaration is used by the DSP system implementation and the audio system. + // Note that this function is intended to convey one message broken into multiple parts. + // See cmTsQueueEnqueueSegMsg() for the equivalent interface. + typedef cmRtRC_t (*cmRtDspToHostFunc_t)(struct cmRtSysCtx_str* p, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt); + + // Record passed with each call to the DSP callback function cmRtCallback_t + typedef struct cmRtSysCtx_str + { + void* reserved; // used internally by the audio system + + bool audioRateFl; // true if this is an audio update callback + + unsigned srcNetNodeId; // Source net node if this is a msg callback originating from a remote network node. + unsigned rtSubIdx; // index of the sub-system this DSP process is serving + + cmRtSysSubSys_t* ss; // ptr to a copy of the cfg recd used to initialize the audio system + unsigned begSmpIdx; // gives signal time as a sample count + + cmRtDspToHostFunc_t dspToHostFunc; // Callback used by the DSP process to send messages to the host + // via the audio system. Returns a cmRtRC_t result code. + + // output (playback) buffers + cmSample_t** oChArray; // each ele is a ptr to buffer with cfg.dspFramesPerCycle samples + unsigned oChCnt; // count of output channels (ele's in oChArray[]) + + // input (recording) buffers + cmSample_t** iChArray; // each ele is a ptr to buffer with cfg.dspFramesPerCycle samples + unsigned iChCnt; // count of input channels (ele's in iChArray[]) + + } cmRtSysCtx_t; + + + // Audio system configuration record used by cmRtSysAllocate(). + typedef struct cmRtSysCfg_str + { + cmRtSysSubSys_t* ssArray; // sub-system cfg record array + unsigned ssCnt; // count of sub-systems + unsigned meterMs; // Meter sample period in milliseconds + void* clientCbData; // User arg. for clientCbFunc(). + cmTsQueueCb_t clientCbFunc; // Called by cmRtSysReceiveMsg() to deliver internally generated msg's to the host. + // Set to NULL if msg's will be directly returned by buffers passed to cmRtSysReceiveMsg(). + cmUdpNetH_t netH; + } cmRtSysCfg_t; + + extern cmRtSysH_t cmRtSysNullHandle; + + // Allocate and initialize an audio system as a collection of 'cfgCnt' sub-systems. + // Prior to call this function the audio audio ports system must be initalized + // (via cmApInitialize()) and the MIDI port system must be initialized + // (via cmMpInitialize()). Note also that cmApFinalize() and cmMpFinalize() + // cannot be called prior to cmRtSysFree(). + // See cmRtSystemTest() for a complete example. + cmRtRC_t cmRtSysAllocate( cmRtSysH_t* hp, cmRpt_t* rpt, const cmRtSysCfg_t* cfg ); + + // Finalize and release any resources held by the audio system. + cmRtRC_t cmRtSysFree( cmRtSysH_t* hp ); + + // Returns true if 'h' is a handle which was successfully allocated by + // cmRtSysAllocate(). + bool cmRtSysHandleIsValid( cmRtSysH_t h ); + + // Reinitialize a previously allocated audio system. This function + // begins with a call to cmRtSysFinalize(). + // Use cmRtSysEnable(h,true) to begin processing audio following this call. + cmRtRC_t cmRtSysInitialize( cmRtSysH_t h, const cmRtSysCfg_t* cfg ); + + // Complements cmRtSysInitialize(). In general there is no need to call this function + // since calls to cmRtSysInitialize() and cmRtSysFree() automaticatically call it. + cmRtRC_t cmRtSysFinalize( cmRtSysH_t h ); + + // Returns true if the audio system has been successfully initialized. + bool cmRtSysIsInitialized( cmRtSysH_t ); + + // Returns true if the audio system is enabled. + bool cmRtSysIsEnabled( cmRtSysH_t h ); + + // Enable/disable the audio system. Enabling the starts audio stream + // in/out of the system. + cmRtRC_t cmRtSysEnable( cmRtSysH_t h, bool enableFl ); + + // + // DSP to Host delivery function + // + + // This function is used to pass messages from a DSP process to the HOST it + // is always called from within the real-time thread. + cmRtRC_t cmRtSysDspToHostSegMsg( cmRtSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt); + cmRtRC_t cmRtSysDspToHost( cmRtSysH_t h, const void* msgDataPtr, unsigned msgByteCnt); + + + // + // Host to DSP delivery functions + // + + // Deliver a message from the host application to the DSP process. (host -> DSP); + // The message is formed as a concatenation of the bytes in each of the segments + // pointed to by 'msgDataPtrArrary[segCnt][msgByteCntArray[segCnt]'. + // This is the canonical msg delivery function in so far as the other host->DSP + // msg delivery function are written in terms of this function. + // The first 4 bytes in the first segment must contain the index of the audio sub-system + // which is to receive the message. + cmRtRC_t cmRtSysDeliverSegMsg( cmRtSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt, unsigned srcNetNodeId ); + + // Deliver a single message from the host to the DSP system. + cmRtRC_t cmRtSysDeliverMsg( cmRtSysH_t h, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ); + + // Deliver a single message from the host to the DSP system. + // Prior to delivery the 'id' is prepended to the message. + cmRtRC_t cmRtSysDeliverIdMsg( cmRtSysH_t h, unsigned rtSubIdx, unsigned id, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ); + + + // + // DSP to Host message functions + // + + // Is a msg from the DSP waiting to be picked up by the host? (host <- DSP) + // 0 = no msgs are waiting or the msg queue is locked by the DSP process. + // >0 = the size of the buffer required to hold the next msg returned via + // cmRtSysReceiveMsg(). + unsigned cmRtSysIsMsgWaiting( cmRtSysH_t h ); + + // Copy the next available msg sent from the DSP process to the host into the host supplied msg buffer + // pointed to by 'msgBufPtr'. Set 'msgDataPtr' to NULL to receive msg by callback from cmRtSysCfg_t.clientCbFunc. + // Returns kBufTooSmallRtRC if msgDataPtr[msgByteCnt] is too small to hold the msg. + // Returns kNoMsgWaitingRtRC if no messages are waiting for delivery or the msg queue is locked by the DSP process. + // Returns kOkRtRC if a msg was delivered. + // Call cmRtSysIsMsgWaiting() prior to calling this function to get + // the size of the data buffer required to hold the next message. + cmRtRC_t cmRtSysReceiveMsg( cmRtSysH_t h, void* msgDataPtr, unsigned msgByteCnt ); + + + // Fill an audio system status record. + void cmRtSysStatus( cmRtSysH_t h, unsigned rtSubIdx, cmRtSysStatus_t* statusPtr ); + + // Enable cmRtSysStatus_t notifications to be sent periodically to the host. + // Set rtSubIdx to cmInvalidIdx to enable/disable all sub-systems. + // The notifications occur approximately every cmRtSysCfg_t.meterMs milliseconds. + void cmRtSysStatusNotifyEnable( cmRtSysH_t, unsigned rtSubIdx, bool enableFl ); + + // Return a pointer the context record associated with a sub-system + cmRtSysCtx_t* cmRtSysContext( cmRtSysH_t h, unsigned rtSubIdx ); + + // Return the count of audio sub-systems. + // This is the same as the count of cfg recds passed to cmRtSystemInitialize(). + unsigned cmRtSysSubSystemCount( cmRtSysH_t h ); + + // Audio system test and example function. + void cmRtSysTest( cmRpt_t* rpt, int argc, const char* argv[] ); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmRtSysMsg.h b/cmRtSysMsg.h new file mode 100644 index 0000000..8f67ee1 --- /dev/null +++ b/cmRtSysMsg.h @@ -0,0 +1,99 @@ +#ifndef cmRtSysMsg_h +#define cmRtSysMsg_h + +#ifdef __cplusplus +extern "C" { +#endif + + /// Reserved DSP message selector id's (second field of all host<->audio system messages) + enum + { + kMidiMsgArraySelRtId = 1000, + kMidiSysExSelRtId, + kUiDrvrSelRtId, // cmUiDriverArg_t message to/from the UI driver + kUiSelRtId, // cmUiDriverArg-t message from the UI mgr to a client + kUiMstrSelRtId, // indicates a cmDspUiHdr_t msg containing master control information for the audio system + kStatusSelRtId, // indicates the msg is of type cmRtSysStatus_t + kNetSyncSelRtId, // sent with a cmDspNetMsg_t object + }; + + typedef struct + { + unsigned rtSubIdx; + unsigned selId; // Message selector id See kXXXSelRtId above + } cmRtSysMsgHdr_t; + + // All of the UI messages that create a UI control contain an array of integers + // as in the 'value' field. The array contains the id's associated with + // the different programmable paramters which are part of the control. + // For example a slider control has minimum,maximum, step size, and value + // parameters. The location in the array is hard coded according to the + // parameters meaning but the actual value of the id is left up to the + // engine. This allows the engine to use whatever values work best for + // it on a per instance basis. + + + // Header record for all messages between the host and the DSP controllers. + typedef struct + { + cmRtSysMsgHdr_t hdr; + unsigned devIdx; + unsigned chIdx; + bool inFl; + unsigned ctlId; + double value; + } cmRtSysMstr_t; + + + + /// Control id's used to identify the control type of master contols. + enum + { + kSliderUiRtId = 0, + kMeterUiRtId = 1, + kMuteUiRtId = 2, + kToneUiRtId = 3, + kPassUiRtId = 4 + }; + + + /// Audio sub-system status record - this message can be transmitted to the host at + /// periodic intervals. See cmRtSysStatusNotifyEnable(). + /// When transmitted to the host this record acts as the message header. + /// This header is followed by two arrays of doubles containing the input and output meter values + /// associated with the input and output audio devices. + /// Message Layout: [ rtSubIdx kStatusSelId cmRtSysStatus_t iMeterArray[iMeterCnt] oMeterArray[oMeterCnt] ] + typedef struct + { + cmRtSysMsgHdr_t hdr; + + unsigned updateCnt; ///< count of callbacks from the audio devices. + unsigned wakeupCnt; ///< count of times the audio system thread has woken up after the cond. var has been signaled by the audio update thread. + unsigned msgCbCnt; ///< count of msgs delivered via cmRtCallback() . + unsigned audioCbCnt; ///< count of times the DSP execution was requested via cmRtCallback(). + + unsigned iDevIdx; ///< Input device index + unsigned oDevIdx; ///< Output device index + + unsigned overflowCnt; ///< count of times the audio input buffers overflowed + unsigned underflowCnt; ///< count of times the audio output buffers underflowed + unsigned iMeterCnt; ///< count of input meter channels + unsigned oMeterCnt; ///< count of output meter channels + + } cmRtSysStatus_t; + + + typedef struct + { + cmRtSysMsgHdr_t hdr; + unsigned devIdx; + unsigned portIdx; + unsigned msgCnt; + // cmMidiMsg msgArray[msgCnt] + } cmRtSysMidi_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmUiRtSysMstr.c b/cmUiRtSysMstr.c new file mode 100644 index 0000000..d7624e4 --- /dev/null +++ b/cmUiRtSysMstr.c @@ -0,0 +1,509 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmJson.h" +#include "cmThread.h" +#include "cmUdpPort.h" +#include "cmUdpNet.h" +#include "cmRtSysMsg.h" +#include "cmRtSys.h" +#include "cmUiDrvr.h" +#include "cmUi.h" +#include "cmUiRtSysMstr.h" + +enum +{ + kLabelAmId, + kInSliderAmId, + kInMeterAmId, + kInToneAmId, + kInPassAmId, + kInMuteAmId, + kOutSliderAmId, + kOutMeterAmId, + kOutToneAmId, + kOutPassAmId, + kOutMuteAmId, + kAmCnt +}; + + +enum +{ + kMinDb = 24, + kMaxDb = -24, + kMtrMin = 0, + kMtrMax = 100 +}; + +typedef struct cmAmPanel_str +{ + unsigned rtSubIdx; + + unsigned panelId; + + unsigned updateId; + unsigned wakeupId; + unsigned msgCbId; + unsigned audioCbId; + + unsigned updateCnt; + unsigned wakeupCnt; + unsigned msgCbCnt; + unsigned audioCbCnt; + + unsigned baseOutId; + unsigned iDevIdx; + unsigned oDevIdx; + unsigned iChCnt; + unsigned oChCnt; + + unsigned a[ kAmCnt ]; + + + struct cmAmPanel_str* link; +} cmAmPanel_t; + +typedef struct +{ + cmErr_t err; + unsigned appId; + cmUiH_t uiH; + cmRtSysH_t asH; + unsigned nextId; + cmAmPanel_t* list; +} cmAm_t; + +cmUiRtMstrH_t cmUiRtMstrNullHandle = cmSTATIC_NULL_HANDLE; + +cmAm_t* _cmUiAmHandleToPtr( cmUiRtMstrH_t h ) +{ + cmAm_t* p = (cmAm_t*)h.h; + assert( p!=NULL); + return p; +} + +cmAmRC_t _cmUiAmFreePanels( cmAm_t* p, bool callDriverFl ) +{ + cmAmRC_t rc = kOkAmRC; + cmAmPanel_t* pp = p->list; + + while( pp != NULL ) + { + cmAmPanel_t* np = pp->link; + + unsigned panelId = pp->panelId; + + if( callDriverFl ) + if( cmUiClearPanel( p->uiH, p->appId, panelId ) != kOkUiRC ) + { + rc = cmErrMsg(&p->err,kUiFailAmRC,"The panel %i clear failed.",panelId); + goto errLabel; + } + + cmMemFree(pp); + + pp = np; + } + + p->nextId = 0; + p->list = NULL; + + errLabel: + return rc; +} + + +cmAmRC_t _cmUiAmFree( cmAm_t* p ) +{ + _cmUiAmFreePanels(p,false); + + if( cmUiDestroyApp( p->uiH, p->appId ) != kOkUiRC ) + cmErrMsg(&p->err,kUiFailAmRC,"UI Mgr. app destroy failed."); + + cmMemFree(p); + return kOkAmRC; +} + +cmAmRC_t cmUiRtSysMstrAlloc( cmCtx_t* ctx, cmUiRtMstrH_t* hp, cmUiH_t uiH, cmRtSysH_t asH, unsigned appId ) +{ + cmAmRC_t rc = kOkAmRC; + + if((rc = cmUiRtSysMstrFree(hp)) != kOkAmRC ) + return rc; + + cmAm_t* p = cmMemAllocZ(cmAm_t,1); + cmErrSetup(&p->err,&ctx->rpt,"Audio System Master UI"); + + p->appId = appId; + p->uiH = uiH; + p->asH = asH; + p->nextId = 0; + + // allocate the UI Mgr. app. slot for the audio system master control UI. + if( cmUiCreateApp( uiH, appId, cmInvalidId ) != kOkUiRC ) + { + rc = cmErrMsg(&p->err,kUiFailAmRC,"The UI Mgr. failed while creating the Audio System UI app. slot."); + goto errLabel; + } + + + hp->h = p; + errLabel: + if( rc != kOkAmRC ) + _cmUiAmFree(p); + return rc; +} + +cmAmRC_t cmUiRtSysMstrFree( cmUiRtMstrH_t* hp ) +{ + cmAmRC_t rc = kOkAmRC; + + if(hp==NULL || cmUiRtSysMstrIsValid(*hp)==false ) + return kOkAmRC; + + cmAm_t* p = _cmUiAmHandleToPtr(*hp); + + if((rc = _cmUiAmFree(p)) != kOkAmRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmUiRtSysMstrIsValid( cmUiRtMstrH_t h ) +{ return h.h != NULL; } + + +cmAmRC_t cmUiRtSysMstrInitialize( cmUiRtMstrH_t amH, const cmRtSysCtx_t* c, const cmChar_t* inDevLabel, const cmChar_t* outDevLabel ) +{ + cmAmRC_t rc = kOkAmRC; + cmAm_t* p = _cmUiAmHandleToPtr(amH); + cmUiH_t uiH = p->uiH; + unsigned panelId = cmInvalidId; + unsigned colW = 50; + unsigned ctlW = 45; + unsigned n = 31; + cmChar_t chNumStr[ n+1 ]; + int w; + cmAmPanel_t* pp = NULL; + + + // This function is called once for each audio sub-system. + // If this is the first call in the sequence then clear the previous setup. + if( c->rtSubIdx == 0 ) + { + if((rc = _cmUiAmFreePanels(p,true)) != kOkAmRC ) + goto errLabel; + + assert(p->list == NULL ); + } + + // create the panel recd and link it to the beginning of the list + pp = cmMemAllocZ(cmAmPanel_t,1); + + pp->link = p->list; + p->list = pp; + pp->rtSubIdx = c->rtSubIdx; + pp->iDevIdx = c->ss->args.inDevIdx; + pp->oDevIdx = c->ss->args.outDevIdx; + pp->iChCnt = c->iChCnt; + pp->oChCnt = c->oChCnt; + + + pp->panelId = p->nextId++; + pp->updateId = p->nextId++; + pp->wakeupId = p->nextId++; + pp->msgCbId = p->nextId++; + pp->audioCbId = p->nextId++; + + pp->a[kLabelAmId] = p->nextId; + pp->a[kInSliderAmId] = p->nextId += c->iChCnt; + pp->a[kInMeterAmId] = p->nextId += c->iChCnt; + pp->a[kInToneAmId] = p->nextId += c->iChCnt; + pp->a[kInPassAmId] = p->nextId += c->iChCnt; + pp->a[kInMuteAmId] = p->nextId += c->iChCnt; + + pp->baseOutId = p->nextId += c->iChCnt; + + pp->a[kOutSliderAmId] = pp->baseOutId; + pp->a[kOutMeterAmId] = p->nextId += c->oChCnt; + pp->a[kOutToneAmId] = p->nextId += c->oChCnt; + pp->a[kOutPassAmId] = p->nextId += c->oChCnt; + pp->a[kOutMuteAmId] = p->nextId += c->oChCnt; + p->nextId += c->oChCnt; + + panelId = pp->panelId; + + if( cmUiCreatePanel(uiH, p->appId, panelId, "Master", 0 ) != kOkUiRC ) + { + rc = cmErrMsg(&p->err,kUiFailAmRC,"Panel %i create failed.",panelId); + goto errLabel; + } + + cmUiSetFillRows( uiH, p->appId, panelId, true ); + cmUiCreateProgress(uiH, p->appId, panelId, pp->updateId, "Update", 0, 0, 1, 0 ); + cmUiCreateProgress(uiH, p->appId, panelId, pp->wakeupId, "Wakeup", 0, 0, 1, 0 ); + cmUiCreateProgress(uiH, p->appId, panelId, pp->msgCbId, "Message", 0, 0, 1, 0 ); + cmUiCreateProgress(uiH, p->appId, panelId, pp->audioCbId,"Audio", 0, 0, 1, 0 ); + cmUiSetFillRows( uiH, p->appId, panelId, false ); + + cmUiNewLine( uiH, p->appId, panelId ); + cmUiCreateLabel( uiH, p->appId, panelId, cmInvalidId, inDevLabel, kInsideUiFl | kLeftUiFl ); + cmUiNewLine( uiH, p->appId, panelId ); + + unsigned i; + for(i=0; iiChCnt; ++i) + { + snprintf(chNumStr,n,"%i",i); + + cmUiSetNextW( uiH, p->appId, panelId, ctlW ); + cmUiCreateLabel( uiH, p->appId, panelId, cmInvalidId, chNumStr, 0 ); + cmUiCreateVSlider(uiH, p->appId, panelId, pp->a[kInSliderAmId] + i, NULL, 0, kMinDb, kMaxDb, 0.1, 0 ); + cmUiPlaceRight( uiH, p->appId, panelId ); + cmUiCreateVMeter( uiH, p->appId, panelId, pp->a[kInMeterAmId] + i, NULL, 0, kMtrMin, kMtrMax, 0 ); + w = cmUiSetW( uiH, p->appId, panelId, ctlW ); + cmUiCreateCheck( uiH, p->appId, panelId, pp->a[kInToneAmId] + i, "T", 0, false ); + cmUiCreateCheck( uiH, p->appId, panelId, pp->a[kInPassAmId] + i, "P", 0, false ); + cmUiCreateCheck( uiH, p->appId, panelId, pp->a[kInMuteAmId] + i, "M", 0, false ); + cmUiSetW( uiH, p->appId, panelId, w ); + cmUiSetBaseCol( uiH, p->appId, panelId, 5 + (i+1)*colW); + } + + cmUiSetBaseCol( uiH, p->appId, panelId, 0); + cmUiNewLine( uiH, p->appId, panelId ); + cmUiCreateLabel( uiH,p->appId, panelId, cmInvalidId, outDevLabel, kInsideUiFl | kLeftUiFl ); + cmUiNewLine( uiH, p->appId, panelId ); + + for(i=0; ioChCnt; ++i) + { + snprintf(chNumStr,n,"%i",i); + + cmUiSetNextW( uiH, p->appId, panelId, ctlW ); + cmUiCreateLabel( uiH, p->appId, panelId, cmInvalidId, chNumStr, 0 ); + cmUiCreateVSlider(uiH, p->appId, panelId, pp->a[kOutSliderAmId] + i, NULL, 0, kMinDb, kMaxDb, 0.1, 0 ); + cmUiPlaceRight( uiH, p->appId, panelId ); + cmUiCreateVMeter( uiH, p->appId, panelId, pp->a[kOutMeterAmId] + i, NULL, 0, kMtrMin, kMtrMax, 0 ); + w = cmUiSetW( uiH, p->appId, panelId, ctlW ); + cmUiCreateCheck( uiH, p->appId, panelId, pp->a[kOutToneAmId] + i, "T", 0, false ); + cmUiCreateCheck( uiH, p->appId, panelId, pp->a[kOutPassAmId] + i, "P", 0, false ); + cmUiCreateCheck( uiH, p->appId, panelId, pp->a[kOutMuteAmId] + i, "M", 0, false ); + cmUiSetW( uiH, p->appId, panelId, w ); + cmUiSetBaseCol( uiH, p->appId, panelId, 5 + (i+1)*colW); + } + + errLabel: + return rc; +} + + +cmAmPanel_t* _cmUiAmFindPanel( cmAm_t* p, unsigned panelId, bool errFl ) +{ + cmAmPanel_t* pp = p->list; + for(; pp!=NULL; pp=pp->link) + if( pp->panelId == panelId ) + return pp; + + if( errFl ) + cmErrMsg(&p->err,kPanelNotFoundAmRC,"The panel %i was not found.",panelId); + + return NULL; +} + +unsigned _cmUiAmCtlTypeId( cmAm_t* p, cmAmPanel_t* pp, cmUiCId_t cId, unsigned usrId, + unsigned sliderId, unsigned toneId, unsigned passId, unsigned muteId ) +{ + switch( cId ) + { + case kSliderUiCId: + assert( pp->a[sliderId] <= usrId && usrId < pp->a[sliderId]+pp->oChCnt); + return sliderId; + break; + + case kCheckUiCId: + if( pp->a[toneId] <= usrId && usrId < pp->a[toneId]+pp->oChCnt ) + return toneId; + + if( pp->a[passId] <= usrId && usrId < pp->a[passId]+pp->oChCnt ) + return passId; + + if( pp->a[muteId] <= usrId && usrId < pp->a[muteId]+pp->oChCnt ) + return muteId; + break; + + default: + break; + + } + + return cmInvalidId; +} + + +cmUiRC_t cmUiRtSysMstrOnUiEvent( cmUiRtMstrH_t h, const cmUiDriverArg_t* a ) +{ + cmUiRC_t rc = kOkUiRC; + cmAm_t* p = _cmUiAmHandleToPtr(h); + cmAmPanel_t* pp; + cmRtSysMstr_t r; + unsigned typeId; + bool tabSelFl = a->dId==kSetValDId && a->cId == kPanelUiCId; + + if((pp = _cmUiAmFindPanel( p, a->panelId, !tabSelFl )) == NULL) + { + if( tabSelFl ) + return kOkUiRC; + + return cmErrLastRC(&p->err); + } + + // if the panel tab was selected/deslected ival will be equal to 1/0 + if( a->usrId == pp->panelId ) + { + cmRtSysStatusNotifyEnable(p->asH, pp->rtSubIdx, a->ival ); + return rc; + } + + // based on the usrId determine which control generated the event + if( a->usrId >= pp->baseOutId ) + typeId = _cmUiAmCtlTypeId(p,pp,a->cId,a->usrId,kOutSliderAmId,kOutToneAmId,kOutPassAmId,kOutMuteAmId); + else + typeId = _cmUiAmCtlTypeId(p,pp,a->cId,a->usrId,kInSliderAmId,kInToneAmId,kInPassAmId,kInMuteAmId); + + + // this control is not a slider or check btn so ignore it + if( typeId == cmInvalidId ) + return rc; + + + unsigned asInFl = 0; + unsigned asCtlId = cmInvalidId; + unsigned asCh = a->usrId - pp->a[typeId]; + double asValue = 0; + + switch( typeId ) + { + case kInSliderAmId: + asInFl = 1; + asCtlId = kSliderUiRtId; + asValue = a->fval; + break; + + case kInToneAmId: + asInFl = 1; + asCtlId = kToneUiRtId; + asValue = a->ival; + break; + + case kInPassAmId: + asInFl = 1; + asCtlId = kPassUiRtId; + asValue = a->ival; + break; + + case kInMuteAmId: + asInFl = 1;; + asCtlId = kMuteUiRtId; + asValue = a->ival; + break; + + case kOutSliderAmId: + asCtlId = kSliderUiRtId; + asValue = a->fval; + break; + + case kOutToneAmId: + asCtlId = kToneUiRtId; + asValue = a->ival; + break; + + case kOutPassAmId: + asCtlId = kPassUiRtId; + asValue = a->ival; + break; + + case kOutMuteAmId: + asCtlId = kMuteUiRtId; + asValue = a->ival; + break; + + } + + unsigned asDevIdx = asInFl ? pp->iDevIdx : pp->oDevIdx; + + r.hdr.rtSubIdx = pp->rtSubIdx; + r.hdr.selId = kUiMstrSelRtId; + r.devIdx = asDevIdx; + r.chIdx = asCh; + r.inFl = asInFl; + r.ctlId = asCtlId; + r.value = asValue; + + if( cmRtSysDeliverMsg(p->asH, &r, sizeof(r), cmInvalidId ) != kOkRtRC ) + rc = cmErrMsg(&p->err,kSubSysFailUiRC,"Audio System master control UI message delivery to the audio system failed."); + + return rc; +} + +int _cmUiAmLinToDb( double v ) +{ + if( v <= 0 ) + return 0; + + v = round(20.0*log10(v)+100.0); + return cmMin(kMtrMax,cmMax(kMtrMin,v)); + +} + +cmUiRC_t cmUiRtSysMstrOnStatusEvent( cmUiRtMstrH_t h, const cmRtSysStatus_t* m, const double* iMeterArray, const double* oMeterArray ) +{ + cmAm_t* p = _cmUiAmHandleToPtr(h); + cmAmPanel_t* pp = p->list; + + for(; pp!=NULL; pp=pp->link) + if(pp->rtSubIdx == m->hdr.rtSubIdx ) + break; + + if( pp == NULL ) + return cmErrMsg(&p->err,kPanelNotFoundUiRC,"The panel associated with Audio system index %i could not be found.",m->hdr.rtSubIdx); + + cmUiSetInt(p->uiH, p->appId, pp->updateId, m->updateCnt != pp->updateCnt ); + cmUiSetInt(p->uiH, p->appId, pp->wakeupId, m->wakeupCnt != pp->wakeupCnt ); + cmUiSetInt(p->uiH, p->appId, pp->msgCbId, m->msgCbCnt != pp->msgCbCnt ); + cmUiSetInt(p->uiH, p->appId, pp->audioCbId, m->audioCbCnt != pp->audioCbCnt ); + + pp->updateCnt = m->updateCnt; + pp->wakeupCnt = m->wakeupCnt; + pp->msgCbCnt = m->msgCbCnt; + pp->audioCbCnt= m->audioCbCnt; + + unsigned i; + + for(i=0; iiMeterCnt; ++i) + cmUiSetInt(p->uiH, p->appId, pp->a[kInMeterAmId]+i, _cmUiAmLinToDb(iMeterArray[i]) ); + + for(i=0; ioMeterCnt; ++i) + cmUiSetInt(p->uiH, p->appId, pp->a[kOutMeterAmId]+i, _cmUiAmLinToDb(oMeterArray[i]) ); + + return kOkUiRC; +} + +void cmUiRtSysMstrClearStatus( cmUiRtMstrH_t h ) +{ + cmAm_t* p = _cmUiAmHandleToPtr(h); + + cmAmPanel_t* pp = p->list; + for(; pp!=NULL; pp=pp->link) + { + cmUiSetInt(p->uiH, p->appId, pp->updateId, 0 ); + cmUiSetInt(p->uiH, p->appId, pp->wakeupId, 0 ); + cmUiSetInt(p->uiH, p->appId, pp->msgCbId, 0 ); + cmUiSetInt(p->uiH, p->appId, pp->audioCbId, 0 ); + } + +} diff --git a/cmUiRtSysMstr.h b/cmUiRtSysMstr.h new file mode 100644 index 0000000..10f1aec --- /dev/null +++ b/cmUiRtSysMstr.h @@ -0,0 +1,40 @@ +#ifndef cmUiRtSysMstr_h +#define cmUiRtSysMstr_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkAmRC = cmOkRC, + kUiFailAmRC, + kPanelNotFoundAmRC + }; + + typedef cmHandle_t cmUiRtMstrH_t; + typedef cmRC_t cmAmRC_t; + + extern cmUiRtMstrH_t cmUiRtMstrNullHandle; + + cmAmRC_t cmUiRtSysMstrAlloc( cmCtx_t* ctx, cmUiRtMstrH_t* hp, cmUiH_t uiH, cmRtSysH_t asH, unsigned appId ); + cmAmRC_t cmUiRtSysMstrFree( cmUiRtMstrH_t* hp ); + + bool cmUiRtSysMstrIsValid( cmUiRtMstrH_t h ); + + cmAmRC_t cmUiRtSysMstrInitialize( cmUiRtMstrH_t h, const cmRtSysCtx_t* c, const cmChar_t* inDevLabel, const cmChar_t* outDevLabel ); + + // Receive UI events. + cmUiRC_t cmUiRtSysMstrOnUiEvent( cmUiRtMstrH_t h, const cmUiDriverArg_t* a ); + + // Receive UI status events + cmUiRC_t cmUiRtSysMstrOnStatusEvent( cmUiRtMstrH_t h, const cmRtSysStatus_t* m, const double* iMeterArray, const double* oMeterArray ); + + // Clear the status indicators. + void cmUiRtSysMstrClearStatus( cmUiRtMstrH_t h ); + +#ifdef __cplusplus +} +#endif + +#endif