commit b108da1911a3b2ca4c11ceb0e428b9935c03b0eb Author: kevin Date: Mon Oct 29 20:52:39 2012 -0700 Initial commit diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..982bd72 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,75 @@ + + +cmHDR = +cmSRC = + +cmHDR += src/libcm/cmErr.h src/libcm/cmCtx.h src/libcm/cmRpt.h src/libcm/cmGlobal.h src/libcm/cmComplexTypes.h src/libcm/cmFloatTypes.h src/libcm/cmPrefix.h +cmSRC += src/libcm/cmErr.c src/libcm/cmCtx.c src/libcm/cmRpt.c src/libcm/cmGlobal.c + +cmHDR += src/libcm/cmSerialize.h src/libcm/cmSymTbl.h src/libcm/cmFileSys.h src/libcm/cmFile.h src/libcm/cmMem.h src/libcm/cmTime.h src/libcm/cmPgmOpts.h +cmSRC += src/libcm/cmSerialize.c src/libcm/cmSymTbl.c src/libcm/cmFileSys.c src/libcm/cmFile.c src/libcm/cmMem.c src/libcm/cmTime.c src/libcm/cmPgmOpts.c + +cmHDR += src/libcm/cmLib.h src/libcm/cmText.h src/libcm/cmMath.h src/libcm/cmOp.h src/libcm/cmGnuPlot.h src/libcm/cmKeyboard.h +cmSRC += src/libcm/cmLib.c src/libcm/cmText.c src/libcm/cmMath.c src/libcm/cmOp.c src/libcm/cmGnuPlot.c src/libcm/cmKeyboard.c + +cmHDR += src/libcm/cmLinkedHeap.h src/libcm/cmMallocDebug.h src/libcm/cmLex.h src/libcm/cmJson.h src/libcm/cmPrefs.h src/libcm/cmStack.h +cmSRC += src/libcm/cmLinkedHeap.c src/libcm/cmMallocDebug.c src/libcm/cmLex.c src/libcm/cmJson.c src/libcm/cmPrefs.c src/libcm/cmStack.c + +cmHDR += src/libcm/cmUdpPort.h src/libcm/cmUdpNet.h src/libcm/cmVirtNet.h +cmSRC += src/libcm/cmUdpPort.c src/libcm/cmUdpNet.c src/libcm/cmVirtNet.c + +cmHDR += src/libcm/cmAudioPort.h src/libcm/cmApBuf.h src/libcm/cmAudioAggDev.h src/libcm/cmAudioNrtDev.h src/libcm/cmThread.h +cmSRC += src/libcm/cmAudioPort.c src/libcm/cmApBuf.c src/libcm/cmAudioAggDev.c src/libcm/cmAudioNrtDev.c src/libcm/cmThread.c + +cmHDR += src/libcm/cmMidiFilePlay.h src/libcm/cmMidiPort.h src/libcm/cmMidiFile.h src/libcm/cmMidi.h +cmSRC += src/libcm/cmMidiFilePlay.c src/libcm/cmMidiPort.c src/libcm/cmMidiFile.c src/libcm/cmMidi.c + +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/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 + +cmSRC += src/libcm/cmGr.c src/libcm/cmGrDevCtx.c src/libcm/cmGrPage.c src/libcm/cmGrPlot.c src/libcm/cmGrPlotAudio.c +cmHDR += src/libcm/cmGr.h src/libcm/cmGrDevCtx.h src/libcm/cmGrPage.h src/libcm/cmGrPlot.h src/libcm/cmGrPlotAudio.h + +cmHDR += src/libcm/dsp/cmDspSys.h src/libcm/dsp/cmDspClass.h src/libcm/dsp/cmDspValue.h src/libcm/dsp/cmDspUi.h src/libcm/dsp/cmDspPreset.h src/libcm/dsp/cmDspNet.h +cmSRC += src/libcm/dsp/cmDspSys.c src/libcm/dsp/cmDspClass.c src/libcm/dsp/cmDspValue.c src/libcm/dsp/cmDspUi.c src/libcm/dsp/cmDspPreset.c src/libcm/dsp/cmDspNet.c + +cmHDR += src/libcm/dsp/cmDspBuiltIn.h src/libcm/dsp/cmDspFx.h +cmSRC += src/libcm/dsp/cmDspBuiltIn.c src/libcm/dsp/cmDspFx.c + +cmHDR += src/libcm/dsp/cmDspPgm.h src/libcm/dsp/cmDspKr.h src/libcm/dsp/cmDspPgmPP.h src/libcm/dsp/cmDspPgmPPMain.h +cmSRC += src/libcm/dsp/cmDspPgm.c src/libcm/dsp/cmDspKr.c src/libcm/dsp/cmDspPgmPP.c src/libcm/dsp/cmDspPgmPPMain.c + +cmHDR += src/libcm/cmAudDsp.h src/libcm/cmAudDspIF.h src/libcm/cmAudDspLocal.h +cmSRC += src/libcm/cmAudDsp.c src/libcm/cmAudDspIF.c src/libcm/cmAudDspLocal.c + +cmHDR += src/libcm/vop/cmVectOpsTemplateUndef.h src/libcm/vop/cmVectOpsTemplateHdr.h src/libcm/vop/cmVectOpsTemplateCode.h src/libcm/vop/cmVectOpsTemplateMain.h +cmHDR += src/libcm/vop/cmVectOpsRIHdr.h src/libcm/vop/cmVectOpsRICode.h +cmHDR += src/libcm/vop/cmProcTemplateUndef.h src/libcm/vop/cmProcTemplateHdr.h src/libcm/vop/cmProcTemplateCode.h src/libcm/vop/cmProcTemplateMain.h +cmHDR += src/libcm/vop/cmVectOps.h src/libcm/vop/cmProcTemplate.h + +cmSRC += src/libcm/vop/cmVectOps.c src/libcm/vop/cmProcTemplate.c + +cmHDR += src/libcm/cmProcObj.h src/libcm/cmProc.h src/libcm/cmProc2.h src/libcm/cmProc3.h src/libcm/cmProcTest.h +cmSRC += src/libcm/cmProcObj.c src/libcm/cmProc.c src/libcm/cmProc2.c src/libcm/cmProc3.c src/libcm/cmProcTest.c + + +cmHDR += src/libcm/app/cmOnset.h src/libcm/app/cmTimeLine.h src/libcm/app/cmScore.h src/libcm/app/cmPickup.h src/libcm/cmRbm.h +cmSRC += src/libcm/app/cmOnset.c src/libcm/app/cmTimeLine.c src/libcm/app/cmScore.c src/libcm/app/cmPickup.c src/libcm/cmRbm.c + + +if OS_LINUX + cmSRC += src/libcm/linux/cmFileSysLinux.c src/libcm/linux/cmAudioPortAlsa.c src/libcm/linux/cmMidiAlsa.c + cmHDR += src/libcm/linux/cmFileSysLinux.h src/libcm/linux/cmAudioPortAlsa.h +endif + +if OS_OSX + cmSRC += src/libcm/osx/clock_gettime_stub.c src/libcm/osx/cmMidiOsx.c src/libcm/osx/cmAudioPortOsx.c src/libcm/osx/cmFileSysOsx.c + cmHDR += src/libcm/osx/clock_gettime_stub.h +endif + + + + diff --git a/app/cmOnset.c b/app/cmOnset.c new file mode 100644 index 0000000..9226af5 --- /dev/null +++ b/app/cmOnset.c @@ -0,0 +1,377 @@ +#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 "cmSymTbl.h" +#include "cmAudioFile.h" +#include "cmMidi.h" +#include "cmFile.h" +#include "cmMath.h" + +#include "cmProcObj.h" +#include "cmProcTemplateMain.h" +#include "cmProc.h" +#include "cmProc2.h" +#include "cmVectOps.h" + +#include "cmOnset.h" + +typedef struct +{ + cmErr_t err; + cmOnsetCfg_t cfg; + + cmCtx* ctxPtr; + cmAudioFileRd* afRdPtr; + cmPvAnl* pvocPtr; + + cmAudioFileH_t afH; // output audio file + cmFileH_t txH; // output text file + + unsigned frmCnt; // spectral frame count + cmReal_t* sfV; // sfV[frmCnt] spectral flux vector + cmReal_t* dfV; // dfV[frmCnt] onset function vector + + cmAudioFileInfo_t afInfo; + unsigned fftSmpCnt; + unsigned hopSmpCnt; + unsigned binCnt; + +} _cmOn_t; + +cmOnH_t cmOnsetNullHandle = cmSTATIC_NULL_HANDLE; + +_cmOn_t* _cmOnsetHandleToPtr( cmOnH_t h ) +{ + _cmOn_t* p = (_cmOn_t*)h.h; + assert(p!=NULL); + return p; +} + +cmOnRC_t _cmOnsetFinalize( _cmOn_t* p ) +{ + cmOnRC_t rc = kOkOnRC; + + if( cmPvAnlFree(&p->pvocPtr) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC,"Phase voocoder free failed."); + goto errLabel; + } + + if( cmAudioFileRdFree(&p->afRdPtr) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC,"Audio file reader failed."); + goto errLabel; + } + + if( cmCtxFree(&p->ctxPtr) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC,"Context proc failed."); + goto errLabel; + } + + cmMemPtrFree(&p->sfV); + cmMemPtrFree(&p->dfV); + cmMemPtrFree(&p); + + errLabel: + return rc; +} + +cmOnRC_t cmOnsetInitialize( cmCtx_t* c, cmOnH_t* hp ) +{ + cmOnRC_t rc; + if((rc = cmOnsetFinalize(hp)) != kOkOnRC ) + return rc; + + _cmOn_t* p = cmMemAllocZ(_cmOn_t,1); + cmErrSetup(&p->err,&c->rpt,"Onset"); + + // create the proc context object + if((p->ctxPtr = cmCtxAlloc(NULL,&c->rpt,cmLHeapNullHandle,cmSymTblNullHandle)) == NULL ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC, "The ctx compoenent allocation failed."); + goto errLabel; + } + + // create the audio file reader + if((p->afRdPtr = cmAudioFileRdAlloc( p->ctxPtr, NULL, 0, NULL, cmInvalidIdx, 0, cmInvalidIdx )) == NULL ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC, "The audio file reader allocation failed."); + goto errLabel; + } + + // create the phase vocoder + if((p->pvocPtr = cmPvAnlAlloc( p->ctxPtr, NULL, 0, 0, 0, 0, 0 )) == NULL ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC,"The phase vocoder allocation failed."); + goto errLabel; + } + + hp->h = p; + + errLabel: + if( rc != kOkOnRC ) + _cmOnsetFinalize(p); + + return rc; +} + +cmOnRC_t cmOnsetFinalize( cmOnH_t* hp ) +{ + cmOnRC_t rc = kOkOnRC; + + if( hp==NULL || cmOnsetIsValid(*hp)==false ) + return kOkOnRC; + + _cmOn_t* p = _cmOnsetHandleToPtr(*hp); + + rc = _cmOnsetFinalize(p); + + return rc; +} + +bool cmOnsetIsValid( cmOnH_t h ) +{ return h.h!=NULL; } + +cmOnRC_t _cmOnsetExec( _cmOn_t* p, unsigned chCnt ) +{ + cmOnRC_t rc = kOkOnRC; + int fi = 0; + unsigned binCnt = p->binCnt; //p->pvocPtr->binCnt; + cmReal_t mag0V[ binCnt ]; + cmSample_t out0V[ p->hopSmpCnt ]; + cmSample_t out1V[ p->hopSmpCnt ]; + cmSample_t* aoutV[chCnt]; + double prog = 0.1; + cmReal_t b0 = 1; + cmReal_t b[] = {1 }; + cmReal_t a[] = {p->cfg.filtCoeff}; + cmReal_t d[] = {0}; + cmReal_t maxVal = 0; + + if( chCnt > 0 ) + aoutV[0] = out0V; + + if( chCnt > 1 ) + aoutV[1] = out1V; + + cmVOR_Zero(mag0V,binCnt); + + // for each frame - read the next block of audio + for(; fifrmCnt && cmAudioFileRdRead(p->afRdPtr) != cmEofRC; ++fi ) + { + // calc the spectrum + while( cmPvAnlExec(p->pvocPtr, p->afRdPtr->outV, p->afRdPtr->outN ) ) + { + unsigned i; + + // calc the spectral flux into sfV[fi]. + cmReal_t sf = 0; + for(i=0; ipvocPtr->magV[i] * 2.0; + + if( m1 > maxVal ) + maxVal = m1; + + cmReal_t dif = m1 - mag0V[i]; // calc. spectral flux + if( dif > 0 ) + sf += dif; // accum. flux + mag0V[i] = m1; // store magn. for next frame + } + + p->sfV[fi] = sf; + + // filter the spectral flux + cmVOR_Filter( p->sfV + fi, 1, &sf, 1, b0, b, a, d, 1 ); + + if( fi >= prog*p->frmCnt ) + { + cmRptPrintf(p->err.rpt,"%i ",lround(prog*10)); + prog += 0.1; + } + } + } + + p->frmCnt = fi; + + // normalize the spectral flux vector + cmReal_t mean = cmVOR_Mean(p->sfV,p->frmCnt); + cmReal_t stdDev = sqrt(cmVOR_Variance(p->sfV, p->frmCnt, &mean )); + cmVOR_SubVS(p->sfV,p->frmCnt,mean); + cmVOR_DivVS(p->sfV,p->frmCnt,stdDev); + cmReal_t maxSf = cmVOR_Max(p->sfV,p->frmCnt,1); + prog = 0.1; + + printf("max:%f ",maxVal); + printf("mean:%f max:%f sd:%f\n",mean,maxSf,stdDev); + + // Pick peaks from the onset detection function using a subset + // of the rules from Dixon, 2006, Onset Detection Revisited. + // locate the onsets and store them in dfV[] + for(fi=0; fifrmCnt; ++fi) + { + int bi = cmMax(0, fi - p->cfg.wndFrmCnt); // begin wnd index + int ei = cmMin(p->frmCnt, fi + p->cfg.wndFrmCnt); // end wnd index + int nn = ei - bi; // wnd frm cnt + int wi = fi < p->cfg.wndFrmCnt ? fi : p->cfg.wndFrmCnt; // cur wnd index + + // initialize the out + cmVOS_Fill(out1V,p->hopSmpCnt,p->sfV[fi]/maxSf); + cmVOS_Zero(out0V,p->hopSmpCnt); + + p->dfV[fi] = 0; + + // if cur index is a peak in the window + if( cmVOR_MaxIndex(p->sfV + bi, nn, 1 ) == wi ) + { + // calc an extended window going backwards in time + bi = cmMax(0, fi - p->cfg.wndFrmCnt * p->cfg.preWndMult ); + nn = ei - bi; + + // if the cur value is greater than the mean of the extended window plus a threshold + if( p->sfV[fi] > cmVOR_Mean(p->sfV + bi, nn ) + p->cfg.threshold ) + { + p->dfV[fi] = p->sfV[fi]; + out0V[ p->hopSmpCnt/2 ] = p->sfV[fi]/maxSf; + + unsigned smpIdx = fi * p->hopSmpCnt + p->hopSmpCnt/2; + + // write the output text file + if( cmFilePrintf(p->txH, "[ %i, %f ]\n", smpIdx, p->sfV[fi] ) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kDspTextFileFailOnRC,"Text output write to '%s' failed.", cmFileName(p->txH)); + goto errLabel; + } + } + } + + // write the output audio file + if( cmAudioFileWriteFloat(p->afH, p->hopSmpCnt, chCnt, aoutV ) != kOkAfRC ) + { + rc = cmErrMsg(&p->err,kDspAudioFileFailOnRC,"Audio file write to '%s' failed.",cmAudioFileName(p->afH)); + goto errLabel; + } + + if( fi >= prog*p->frmCnt ) + { + cmRptPrintf(p->err.rpt,"%i ",lround(prog*10)); + prog += 0.1; + } + + } + + errLabel: + + return rc; +} + +cmOnRC_t cmOnsetExec( cmOnH_t h, const cmOnsetCfg_t* cfg, const cmChar_t* inAudioFn, const cmChar_t* outAudioFn, const cmChar_t* outTextFn ) +{ + cmOnRC_t rc = kOkOnRC; + _cmOn_t* p = _cmOnsetHandleToPtr(h); + unsigned audioOutChCnt = 2; + p->cfg = *cfg; + + // get the audio file header information + if( cmAudioFileGetInfo(inAudioFn, &p->afInfo, p->err.rpt ) != kOkAfRC ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC,"The audio file open failed on '%s'.",cmStringNullGuard(inAudioFn)); + goto errLabel; + } + + p->fftSmpCnt = cmNearPowerOfTwo( (unsigned)floor( p->cfg.wndMs * p->afInfo.srate / 1000.0 ) ); + p->hopSmpCnt = p->fftSmpCnt / p->cfg.hopFact; + p->binCnt = cmMin(p->fftSmpCnt/2 + 1, floor(p->cfg.maxFrqHz / (p->afInfo.srate / p->fftSmpCnt))); + p->frmCnt = (p->afInfo.frameCnt - p->fftSmpCnt) / p->hopSmpCnt; + p->sfV = cmMemResizeZ(cmReal_t,p->sfV,p->frmCnt); + p->dfV = cmMemResizeZ(cmReal_t,p->dfV,p->frmCnt); + + // initialize the audio file reader + if( cmAudioFileRdOpen( p->afRdPtr, p->hopSmpCnt, inAudioFn, p->cfg.audioChIdx, 0, cmInvalidIdx ) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC, "The audio file reader open failed."); + goto errLabel; + } + + // initialize the phase vocoder + if( cmPvAnlInit( p->pvocPtr, p->hopSmpCnt, p->afInfo.srate, p->fftSmpCnt, p->hopSmpCnt, kNoCalcHzPvaFl ) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kDspProcFailOnRC," The phase vocoder initialization failed."); + goto errLabel; + } + + // initalize the audio output file + if( outAudioFn != NULL ) + if( cmAudioFileIsValid( p->afH = cmAudioFileNewCreate( outAudioFn, p->afInfo.srate, p->afInfo.bits, audioOutChCnt, NULL, p->err.rpt)) == false ) + { + rc = cmErrMsg(&p->err,kDspAudioFileFailOnRC, "The audio output file '%s' could not be opened.", outAudioFn); + goto errLabel; + } + + // open the text output file + if( outTextFn != NULL ) + { + if( cmFileOpen( &p->txH, outTextFn, kWriteFileFl, p->err.rpt ) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kDspTextFileFailOnRC, "The text output file '%s' could not be opened.",outTextFn); + goto errLabel; + } + + cmFilePrint(p->txH,"{\n onsetArray : \n[\n"); + } + + rc = _cmOnsetExec(p,audioOutChCnt); + + errLabel: + // close the output audio file + if( cmAudioFileDelete(&p->afH) != kOkAfRC ) + rc = cmErrMsg(&p->err,kDspAudioFileFailOnRC,"The audio file close failed."); + + // close the text file + if( cmFileIsValid(p->txH) ) + { + cmFilePrint(p->txH,"]\n}\n"); + + if( cmFileClose(&p->txH) != kOkFileRC ) + rc = cmErrMsg(&p->err,kDspTextFileFailOnRC,"The text file close failed."); + } + return rc; +} + + +cmOnRC_t cmOnsetTest( cmCtx_t* c ) +{ + cmOnsetCfg_t cfg; + cmOnH_t h = cmOnsetNullHandle; + cmOnRC_t rc = kOkOnRC; + const cmChar_t* inAudioFn = "/home/kevin/temp/onset0.wav"; + const cmChar_t* outAudioFn = "/home/kevin/temp/mas/mas0.aif"; + const cmChar_t* outTextFn = "/home/kevin/temp/mas/mas0.txt"; + + cfg.wndMs = 42; + cfg.hopFact = 4; + cfg.audioChIdx = 0; + cfg.wndFrmCnt = 3; + cfg.preWndMult = 3; + cfg.threshold = 0.6; + cfg.maxFrqHz = 24000; + cfg.filtCoeff = -0.7; + + if((rc = cmOnsetInitialize(c,&h)) != kOkOnRC ) + goto errLabel; + + rc = cmOnsetExec(h,&cfg,inAudioFn,outAudioFn,outTextFn); + + errLabel: + cmOnsetFinalize(&h); + + return rc; + +} diff --git a/app/cmOnset.h b/app/cmOnset.h new file mode 100644 index 0000000..54ab6dd --- /dev/null +++ b/app/cmOnset.h @@ -0,0 +1,55 @@ +#ifndef cmOnset_h +#define cmOnset_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkOnRC = cmOkRC, + kDspProcFailOnRC, + kDspAudioFileFailOnRC, + kDspTextFileFailOnRC, + }; + + typedef cmRC_t cmOnRC_t; + typedef cmHandle_t cmOnH_t; + + typedef struct + { + double wndMs; + unsigned hopFact; + unsigned audioChIdx; + + unsigned wndFrmCnt; // + double preWndMult; // + double threshold; // + double maxFrqHz; // + double filtCoeff; // + + } cmOnsetCfg_t; + + extern cmOnH_t cmOnsetNullHandle; + + cmOnRC_t cmOnsetInitialize( cmCtx_t* c, cmOnH_t* hp ); + + cmOnRC_t cmOnsetFinalize( cmOnH_t* hp ); + + bool cmOnsetIsValid( cmOnH_t h ); + + cmOnRC_t cmOnsetExec( + cmOnH_t h, + const cmOnsetCfg_t* cfg, + const cmChar_t* inAudioFn, + const cmChar_t* outAudioFn, + const cmChar_t* outTextFn ); + + cmOnRC_t cmOnsetTest( cmCtx_t* c ); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/app/cmPickup.c b/app/cmPickup.c new file mode 100644 index 0000000..ebcc914 --- /dev/null +++ b/app/cmPickup.c @@ -0,0 +1,876 @@ +#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 "cmSymTbl.h" +#include "cmJson.h" +#include "cmMidi.h" +#include "cmAudioFile.h" +#include "cmFile.h" +#include "cmFileSys.h" +#include "cmProcObj.h" +#include "cmProcTemplate.h" +#include "cmVectOpsTemplateMain.h" +#include "cmProc.h" +#include "cmProc2.h" +#include "cmProc3.h" +#include "cmPickup.h" +#include "cmAudLabelFile.h" + +enum +{ + kRmsPuId, + kMedPuId, + kDifPuId, + kAvgPuId, + kOnsPuId, + kFltPuId, + kSupPuId, + kAtkPuId, + kRlsPuId, + kSegPuId, + kPuCnt +}; + +typedef struct +{ + cmErr_t err; + unsigned chCnt; + cmPuCh_t* chArray; + cmCtx_t ctx; // stored ctx used by cmAudLabelFileAllocOpen() + + // cmProc objects + cmCtx* ctxp; + cmAudioFileRd* afrp; + cmShiftBuf* sbp; + cmGateDetect2* gdp; + cmBinMtxFile_t* mfp; + + const cmChar_t* inAudFn; + const cmChar_t* inLabelFn; + const cmChar_t* outMtx0Fn; + const cmChar_t* outMtx1Fn; + const cmChar_t* outAudFn; + cmReal_t hopMs; + + cmGateDetectParams gd0Args; + cmGateDetectParams gd1Args; +} cmPu_t; + +cmPuH_t cmPuNullHandle = cmSTATIC_NULL_HANDLE; + +cmPu_t* _cmPuHandleToPtr( cmPuH_t h ) +{ + cmPu_t* p = (cmPu_t*)h.h; + assert(p!=NULL); + return p; +} + +cmPuRC_t cmPuAlloc( cmCtx_t* ctx, cmPuH_t* hp ) +{ + cmPuRC_t rc; + if((rc = cmPuFree(hp)) != kOkPuRC ) + return rc; + + cmPu_t* p = cmMemAllocZ(cmPu_t,1); + cmErrSetup(&p->err,&ctx->rpt,"Pickup"); + + p->ctx = *ctx; + hp->h = p; + + return rc; +} + + +cmPuRC_t cmPuFree( cmPuH_t* hp ) +{ + if( hp == NULL || cmPuIsValid(*hp) == false ) + return kOkPuRC; + + cmPu_t* p = _cmPuHandleToPtr(*hp); + + cmMemPtrFree(&p->chArray); + cmMemPtrFree(&p); + hp->h = NULL; + return kOkPuRC; +} + +bool cmPuIsValid( cmPuH_t h ) +{ return h.h != NULL; } + + +cmPuRC_t _cmPuReadLabelsAndCreateArray( cmPu_t* p, const cmChar_t* labelFn, cmReal_t srate ) +{ + cmPuRC_t rc = kOkPuRC; + cmAlfH_t h = cmAlfNullHandle; + unsigned i; + + if( cmAudLabelFileAllocOpen(&p->ctx, &h, labelFn) != kOkAlfRC ) + return cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune audio label file open failed on '%s'",cmStringNullGuard(labelFn)); + + if((p->chCnt = cmAudLabelFileCount(h)) == 0 ) + { + rc = cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune audio label file '%s' does not contain any segment labels.",cmStringNullGuard(labelFn)); + goto errLabel; + } + + p->chArray = cmMemResizeZ(cmPuCh_t,p->chArray,p->chCnt); + + for(i=0; ichCnt; ++i) + { + const cmAlfLabel_t* lp; + if(( lp = cmAudLabelFileLabel(h,i)) == NULL ) + { + rc = cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune label in '%s' at row %i could not be read.",cmStringNullGuard(labelFn),i+1); + goto errLabel; + } + + p->chArray[i].begSmpIdx = floor(srate * lp->begSecs); + p->chArray[i].endSmpIdx = p->chArray[i].begSmpIdx; // default the segment to have 0 length. + + } + + errLabel: + if( cmAudLabelFileFree(&h) != kOkAlfRC ) + rc = cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune label file close failed."); + + return rc; +} + +cmPuRC_t _cmPuWriteMtxFile(cmPu_t* p, bool segFl ) +{ + cmPuRC_t rc = kOkPuRC; + + cmReal_t outV[ kPuCnt ]; + outV[ kRmsPuId ] = p->gdp->rms; + outV[ kMedPuId ] = p->gdp->med; + outV[ kDifPuId ] = p->gdp->dif; + outV[ kAvgPuId ] = p->gdp->avg; + outV[ kOnsPuId ] = p->gdp->ons; + outV[ kFltPuId ] = p->gdp->flt; + outV[ kSupPuId ] = p->gdp->sup; + outV[ kAtkPuId ] = p->gdp->onFl; + outV[ kRlsPuId ] = p->gdp->offFl; + outV[ kSegPuId ] = segFl; + + // write the output file - plot with cmGateDetectPlot.m + if( cmBinMtxFileExecR(p->mfp,outV,kPuCnt) != cmOkRC ) + rc = cmErrMsg(&p->err,kProcFailPuRC,"Matrix file write failed."); + + return rc; +} + +void _cmPuCalcGains( cmPu_t* p ) +{ + unsigned i; + cmReal_t avg = 0; + + if( p->chCnt == 0 ) + return; + + for(i=0; ichCnt; ++i) + avg += p->chArray[i].gateMaxAvg; + + avg /= p->chCnt; + + for(i=0; ichCnt; ++i) + { + cmReal_t d = p->chArray[i].gateMaxAvg==0 ? 1.0 : p->chArray[i].gateMaxAvg; + p->chArray[i].gain = avg / d; + } +} + +cmPuCh_t* _cmPuIncrCh( cmPu_t* p, cmPuCh_t* chp, unsigned* segSmpIdxPtr ) +{ + if( *segSmpIdxPtr != p->chArray[0].begSmpIdx ) + ++chp; + + if( chp >= p->chArray + p->chCnt ) + return NULL; + + if( chp+1 == p->chArray + p->chCnt ) + *segSmpIdxPtr = p->afrp->info.frameCnt; + else + *segSmpIdxPtr = (chp+1)->begSmpIdx; + + return chp; +} + +cmPuRC_t _cmPuCalcRerunGateDetectors( + cmPu_t* p, + const cmChar_t* outMtxFn, + const cmChar_t* outAudFn, + const cmGateDetectParams* gdArgs, + unsigned procSmpCnt, + unsigned wndSmpCnt, + unsigned hopSmpCnt ) +{ + cmPuRC_t rc = kOkPuRC; + cmAudioFileWr* afwp = NULL; + unsigned outChCnt = 1; + unsigned outChIdx = 0; + unsigned bitsPerSmp = 16; + unsigned smpIdx = 0; + cmSample_t* smpV = NULL; + + + // rewind the audio file reader + if( cmAudioFileRdSeek(p->afrp,0) != cmOkRC ) + { + cmErrMsg(&p->err,kProcFailPuRC,"Audio file seek failed."); + goto errLabel; + } + + // reset the shift buffer + if( cmShiftBufInit( p->sbp, procSmpCnt, wndSmpCnt, hopSmpCnt ) != cmOkRC ) + { + cmErrMsg(&p->err,kProcFailPuRC,"Shift buffer reset failed."); + goto errLabel; + } + + // reset the gate detector + if( cmGateDetectInit2( p->gdp, procSmpCnt, gdArgs ) != cmOkRC ) + { + cmErrMsg(&p->err,kProcFailPuRC,"Gate detector reset failed."); + goto errLabel; + } + + // create an new matrix output file + if( cmBinMtxFileInit( p->mfp, outMtxFn ) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Output matrix file '%s' initialization failed.",cmStringNullGuard(outMtxFn)); + goto errLabel; + } + + // create an audio output file + if( (afwp = cmAudioFileWrAlloc(p->ctxp, NULL, procSmpCnt, outAudFn, p->afrp->info.srate, outChCnt, bitsPerSmp )) == NULL ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Output audio file '%s' initialization failed.",cmStringNullGuard(outAudFn)); + goto errLabel; + } + + smpV = cmMemAllocZ(cmSample_t,procSmpCnt); + + cmPuCh_t* chp = p->chArray; + unsigned segSmpIdx = chp->begSmpIdx; + bool segFl = false; + + // for each procSmpCnt samples + for(; cmAudioFileRdRead(p->afrp) != cmEofRC; smpIdx += procSmpCnt ) + { + // apply auto-gain to the audio vector + cmVOS_MultVVS(smpV,p->afrp->outN,p->afrp->outV,chp->gain); + + // is this a segment boundary + if( smpIdx+procSmpCnt >= p->afrp->info.frameCnt || (smpIdx <= segSmpIdx && segSmpIdx < smpIdx + procSmpCnt) ) + { + segFl = true; + + if((chp = _cmPuIncrCh(p,chp, &segSmpIdx )) == NULL ) + break; + } + + // shift the new samples into the shift buffer + while(cmShiftBufExec(p->sbp,smpV,p->afrp->outN)) + { + + // update the gate detector + cmGateDetectExec2(p->gdp,p->sbp->outV,p->sbp->outN); + + if( _cmPuWriteMtxFile(p,segFl) != kOkPuRC ) + goto errLabel; + + segFl =false; + } + + // write the audio output file + if( cmAudioFileWrExec(afwp, outChIdx,smpV,p->afrp->outN ) != cmOkRC ) + { + cmErrMsg(&p->err,kProcFailPuRC,"A write failed to the audio output file '%s'.",outAudFn); + goto errLabel; + } + + } + + errLabel: + + cmMemPtrFree(&smpV); + + if( cmAudioFileWrFree(&afwp) != cmOkRC ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Output audio file '%s' close failed.",cmStringNullGuard(outAudFn)); + goto errLabel; + } + + return rc; +} + +cmPuRC_t cmPuAutoGainCfg( + cmPuH_t h, + const cmChar_t* audioFn, + const cmChar_t* labelFn, + const cmChar_t* outMtx0Fn, + const cmChar_t* outMtx1Fn, + const cmChar_t* outAudFn, + unsigned procSmpCnt, + cmReal_t hopMs, + const cmGateDetectParams* gd0Args, + const cmGateDetectParams* gd1Args ) +{ + cmPuRC_t rc; + cmPu_t* p = _cmPuHandleToPtr(h); + int smpIdx = 0; + int chIdx = 0; + const cmReal_t rmsMax = 1.0; + cmReal_t minRms = rmsMax; + cmReal_t gateMax = 0; + cmReal_t gateSum = 0; + unsigned gateCnt = 0; + cmPuCh_t* chp = NULL; + bool segFl = false; + unsigned segSmpIdx = cmInvalidIdx; + + // create a cmProc context + if((p->ctxp = cmCtxAlloc(NULL, p->err.rpt, cmLHeapNullHandle, cmSymTblNullHandle )) == NULL ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Proc context create failed."); + goto errLabel; + } + + // create a cmProc audio file reader + if((p->afrp = cmAudioFileRdAlloc(p->ctxp, NULL, procSmpCnt, audioFn, chIdx, 0, cmInvalidIdx)) == NULL ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Audio file reader creation failed on '%s'.",cmStringNullGuard(audioFn)); + goto errLabel; + } + + // given the sample rate calculate the hop and window size in samples + unsigned hopSmpCnt = floor(p->afrp->info.srate * hopMs / 1000); + unsigned wndSmpCnt = hopSmpCnt * gd0Args->medCnt; + + // create a shift buffer to maintain the RMS window for the gate detector + if((p->sbp = cmShiftBufAlloc(p->ctxp,NULL,procSmpCnt,wndSmpCnt,hopSmpCnt )) == NULL ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Shift buffer create failed."); + goto errLabel; + } + + // create a gate detector + if((p->gdp = cmGateDetectAlloc2(p->ctxp,NULL,procSmpCnt,gd0Args)) == NULL ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Gate detect create failed."); + goto errLabel; + } + + // create an output file to hold the results of the gate detector + if( (p->mfp = cmBinMtxFileAlloc(p->ctxp,NULL,outMtx0Fn)) == NULL ) + { + rc = cmErrMsg(&p->err,kProcFailPuRC,"Binary matrix file create failed."); + goto errLabel; + } + + // read the label file and create p->chArray + if((rc = _cmPuReadLabelsAndCreateArray(p, labelFn, p->afrp->info.srate)) != kOkPuRC ) + goto errLabel; + + chp = p->chArray; + segSmpIdx = chp->begSmpIdx; + + // for each procSmpCnt samples + for(; cmAudioFileRdRead(p->afrp) != cmEofRC; smpIdx += procSmpCnt ) + { + // if this audio frame marks a segment beginning or + // the end-of-audio-file will occur on the next frame + if( smpIdx+procSmpCnt >= p->afrp->info.frameCnt || (smpIdx <= segSmpIdx && segSmpIdx < smpIdx + procSmpCnt) ) + { + segFl = true; + + // if no ending offset was located then update the gate sum + if( gateMax != 0 ) + { + gateSum += gateMax; + gateCnt += 1; + gateMax = 0; + } + + // calc the avg max RMS value for this segment + chp->gateMaxAvg = gateCnt == 0 ? 0.0 : gateSum / gateCnt; + gateCnt = 0; + gateSum = 0; + gateMax = 0; + + minRms = rmsMax; // force the segment end to be after the segment beginning + + if((chp = _cmPuIncrCh(p,chp, &segSmpIdx )) == NULL ) + break; + } + + // shift the new samples into the shift buffer + while(cmShiftBufExec(p->sbp,p->afrp->outV,p->afrp->outN)) + { + // update the gate detector + cmGateDetectExec2(p->gdp,p->sbp->outV,p->sbp->outN); + + // write the output matrix file + if( _cmPuWriteMtxFile(p,segFl) != kOkPuRC ) + goto errLabel; + + segFl = false; + + // if this frame is an RMS minimum or onset or offset + // then select it as a possible segment end. + // Note that for onsets this will effectively force the end to + // come after the onset because the onset will not be an energy minimum + // relative to subsequent frames. + if( p->gdp->rms < minRms || p->gdp->onFl || p->gdp->offFl ) + { + minRms = p->gdp->rms; + chp->endSmpIdx = smpIdx; + + // count onsets + if( p->gdp->onFl ) + ++chp->onCnt; + + // count offsets + if( p->gdp->offFl ) + { + ++chp->offCnt; + + // update the gate sum and count + gateSum += gateMax; + gateCnt += 1; + gateMax = 0; + } + } + + // track the max RMS value during this gate + if( p->gdp->gateFl && p->gdp->rms > gateMax ) + gateMax = p->gdp->rms; + + } + } + + // calculate the channel gains + if( rc == kOkPuRC ) + _cmPuCalcGains(p); + + rc = _cmPuCalcRerunGateDetectors(p,outMtx1Fn,outAudFn,gd1Args,procSmpCnt,wndSmpCnt,hopSmpCnt); + + p->gd0Args = *gd0Args; + p->gd1Args = *gd1Args; + + errLabel: + if( p->mfp != NULL ) + cmBinMtxFileFree(&p->mfp); + + if( p->gdp != NULL ) + cmGateDetectFree2(&p->gdp); + + if( p->sbp != NULL ) + cmShiftBufFree(&p->sbp); + + if( p->afrp != NULL ) + cmAudioFileRdFree(&p->afrp); + + if( p->ctxp != NULL ) + cmCtxFree(&p->ctxp); + + return rc; +} + +cmPuRC_t cmPuAutoGainExec( cmPuH_t h, const cmChar_t* fileDir, unsigned procSmpCnt ) +{ + cmPu_t* p = _cmPuHandleToPtr(h); + const cmChar_t* inAudFn = cmFsMakeFn(fileDir, p->inAudFn, NULL, NULL ); + const cmChar_t* inLabelFn = cmFsMakeFn(fileDir, p->inLabelFn, NULL, NULL ); + const cmChar_t* outMtx0Fn = cmFsMakeFn(fileDir, p->outMtx0Fn, NULL, NULL ); + const cmChar_t* outMtx1Fn = cmFsMakeFn(fileDir, p->outMtx1Fn, NULL, NULL ); + const cmChar_t* outAudFn = cmFsMakeFn(fileDir, p->outAudFn, NULL, NULL ); + + cmPuRC_t rc = cmPuAutoGainCfg(h,inAudFn,inLabelFn,outMtx0Fn,outMtx1Fn,outAudFn,procSmpCnt,p->hopMs,&p->gd0Args,&p->gd1Args); + + cmFsFreeFn(outAudFn); + cmFsFreeFn(outMtx1Fn); + cmFsFreeFn(outMtx0Fn); + cmFsFreeFn(inLabelFn); + cmFsFreeFn(inAudFn); + + return rc; +} + +cmPuRC_t cmPuAutoGainCfgFromJson( + cmPuH_t h, + const cmChar_t* cfgDir, + cmJsonH_t jsH, + cmJsonNode_t* onp, + unsigned procSmpCnt ) +{ + cmPuRC_t rc; + if((rc = cmPuReadJson(h,jsH,onp)) != kOkPuRC ) + return rc; + + + return cmPuAutoGainExec(h,cfgDir,procSmpCnt); +} + + +void cmPuReport( cmPuH_t h, cmRpt_t* rpt ) +{ + cmPu_t* p = _cmPuHandleToPtr(h); + + unsigned i; + for(i=0; ichCnt; ++i) + { + const cmPuCh_t* chp = p->chArray + i; + cmRptPrintf(rpt,"beg:%i end:%i on:%i off:%i max:%f gain:%f\n", + chp->begSmpIdx, chp->endSmpIdx, chp->onCnt, chp->offCnt, chp->gateMaxAvg, chp->gain ); + } +} + +cmPuRC_t _cmPuJsonGainRead( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label ) +{ + cmPuRC_t rc = kOkPuRC; + cmJsonNode_t* arp; + + // locate the JSON 'gain' array + if(( arp = cmJsonFindValue(jsH,label,onp,kArrayTId)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON array node %s.",cmStringNullGuard(label)); + goto errLabel; + } + + // get the count of elements in the 'gain' array + unsigned arrCnt = cmJsonChildCount(arp); + cmPuCh_t* arr = NULL; + + if( arrCnt > 0 ) + { + arr = cmMemAllocZ(cmPuCh_t,arrCnt); + + unsigned i; + for(i=0; ichCnt ) + arr[i] = p->chArray[i]; + + if( cmJsonRealValue( cmJsonArrayElement(arp,i), &arr[i].gain ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"An error occurred while accessing a JSON 'gain' element."); + goto errLabel; + } + + } + } + + + errLabel: + + if( rc != kOkPuRC ) + cmMemPtrFree(&arr); + + cmMemPtrFree(&p->chArray); + p->chArray = arr; + p->chCnt = arrCnt; + + return rc; +} + +cmPuRC_t _cmPuJsonGdParmsRead( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label, cmGateDetectParams* gdParms ) +{ + cmPuRC_t rc = kOkPuRC; + cmJsonNode_t* gdp; + const char* errLabelPtr; + if(( gdp = cmJsonFindValue(jsH,label,onp,kObjectTId)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON object node %s.",cmStringNullGuard(label)); + goto errLabel; + } + + if( cmJsonMemberValues(gdp, &errLabelPtr, + "medCnt", kIntTId, &gdParms->medCnt, + "avgCnt", kIntTId, &gdParms->avgCnt, + "suprCnt", kIntTId, &gdParms->suprCnt, + "offCnt", kIntTId, &gdParms->offCnt, + "suprCoeff", kRealTId, &gdParms->suprCoeff, + "onThreshDb", kRealTId, &gdParms->onThreshDb, + "offThreshDb", kRealTId, &gdParms->offThreshDb, + NULL ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Gate detect parameter restore failed for '%s'.",cmStringNullGuard(label)); + goto errLabel; + } + + errLabel: + return rc; +} + +cmPuRC_t _cmPuJsonCfgParmsRead(cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp) +{ + cmPuRC_t rc = kOkPuRC; + cmJsonNode_t* gdp = onp; + const char* errLabelPtr; + + + //if(( gdp = cmJsonFindValue(jsH,label,onp,kObjectTId)) == NULL ) + //{ + // rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON object node %s.",cmStringNullGuard(label)); + // goto errLabel; + // } + + if( cmJsonMemberValues(gdp, &errLabelPtr, + "audioFn", kStringTId, &p->inAudFn, + "labelFn", kStringTId, &p->inLabelFn, + "outMtx0Fn", kStringTId, &p->outMtx0Fn, + "outMtx1Fn", kStringTId, &p->outMtx1Fn, + "outAudFn", kStringTId, &p->outAudFn, + "hopMs", kRealTId, &p->hopMs, + NULL ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Autotune cfg parameter read failed."); + goto errLabel; + } + + errLabel: + return rc; + +} + +cmPuRC_t cmPuReadJson( cmPuH_t h, cmJsonH_t jsH, cmJsonNode_t* onp ) +{ + cmPuRC_t rc = kOkPuRC; + cmPu_t* p = _cmPuHandleToPtr(h); + cmJsonNode_t* atp; + + if(( atp = cmJsonFindValue(jsH,"cfg",onp,kObjectTId)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"The JSON 'autotune' object was not found."); + goto errLabel; + } + + if((rc = _cmPuJsonCfgParmsRead(p,jsH,atp)) != kOkPuRC ) + goto errLabel; + + if((rc = _cmPuJsonGdParmsRead(p,jsH,atp,"gdParms0",&p->gd0Args)) != kOkPuRC ) + goto errLabel; + + if((rc = _cmPuJsonGdParmsRead(p,jsH,atp,"gdParms1",&p->gd1Args)) != kOkPuRC ) + goto errLabel; + + if((rc = _cmPuJsonGainRead(p,jsH,atp,"gain")) != kOkPuRC ) + goto errLabel; + + errLabel: + return rc; + +} + +unsigned cmPuChannelCount( cmPuH_t h ) +{ + cmPu_t* p = _cmPuHandleToPtr(h); + return p->chCnt; +} + +const cmPuCh_t* cmPuChannel( cmPuH_t h, unsigned chIdx ) +{ + cmPu_t* p = _cmPuHandleToPtr(h); + assert( chIdx < p->chCnt ); + return p->chArray + chIdx; +} + + + +cmPuRC_t _cmPuJsonSetInt( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* gdp, const cmChar_t* label, int val ) +{ + cmPuRC_t rc = kOkPuRC; + + if( cmJsonReplacePairInt(jsH, gdp, label, kIntTId | kRealTId, val ) != kOkJsRC ) + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Error setting integer JSON field: %s.",cmStringNullGuard(label)); + return rc; +} + +cmPuRC_t _cmPuJsonSetReal( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* gdp, const cmChar_t* label, cmReal_t val ) +{ + cmPuRC_t rc = kOkPuRC; + + if( cmJsonReplacePairReal(jsH, gdp, label, kIntTId | kRealTId, val ) != kOkJsRC ) + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Error setting real JSON field: %s.",cmStringNullGuard(label)); + return rc; +} + +cmPuRC_t _cmPuJsonGdParmsUpdate( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label, const cmGateDetectParams* gdParms ) +{ + cmPuRC_t rc = kOkPuRC; + cmJsonNode_t* gdp; + + if(( gdp = cmJsonFindValue(jsH,label,onp,kObjectTId)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON object node %s.",cmStringNullGuard(label)); + goto errLabel; + } + + _cmPuJsonSetInt( p, jsH, gdp, "medCnt", gdParms->medCnt); + _cmPuJsonSetInt( p, jsH, gdp, "avgCnt", gdParms->avgCnt); + _cmPuJsonSetInt( p, jsH, gdp, "suprCnt", gdParms->suprCnt); + _cmPuJsonSetInt( p, jsH, gdp, "offCnt", gdParms->offCnt); + _cmPuJsonSetReal( p, jsH, gdp, "suprCoeff", gdParms->suprCoeff); + _cmPuJsonSetReal( p, jsH, gdp, "onThreshDb", gdParms->onThreshDb); + _cmPuJsonSetReal( p, jsH, gdp, "offThresDb", gdParms->offThreshDb); + + rc = cmErrLastRC(&p->err); + errLabel: + return rc; + +} + +cmPuRC_t _cmPuJsonGainUpdate( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label ) +{ + cmPuRC_t rc = kOkPuRC; + cmJsonNode_t* arp; + unsigned i; + + // locate the JSON 'gain' array + if(( arp = cmJsonFindValue(jsH,label,onp,kArrayTId)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON array node %s.",cmStringNullGuard(label)); + goto errLabel; + } + + // get the count of elements in the 'gain' array + unsigned arrCnt = cmJsonChildCount(arp); + + // update the existing 'gain' array elmements from p->chArray[] + for(i=0; ichCnt; ++i) + { + if(cmJsonSetReal( jsH, cmJsonArrayElement(arp,i), p->chArray[i].gain ) != kOkPuRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"Set JSON 'gain' array elment failed."); + goto errLabel; + } + } + + // create new elements if the array was not long enough + for(; ichCnt; ++i) + if( cmJsonCreateReal(jsH,arp,p->chArray[i].gain) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON 'gain' element create failed."); + goto errLabel; + } + + // remove elements if the array begain with extra elements. + if( arrCnt > p->chCnt ) + { + if( cmJsonRemoveNode( jsH, cmJsonArrayElement(arp,i), true ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON 'gain' element removal failed."); + goto errLabel; + } + } + + errLabel: + return rc; + +} + +cmPuRC_t cmPuWriteJson( cmPuH_t h, cmJsonH_t jsH, cmJsonNode_t* onp ) +{ + cmPuRC_t rc = kOkPuRC; + cmPu_t* p = _cmPuHandleToPtr(h); + cmJsonNode_t* atp; + + if(( atp = cmJsonFindValue(jsH,"autoTune",onp,kObjectTId)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"The JSON 'autotune' object was not found."); + goto errLabel; + } + + if((rc = _cmPuJsonGdParmsUpdate(p,jsH,atp,"gdParms0",&p->gd0Args)) != kOkPuRC ) + goto errLabel; + + if((rc = _cmPuJsonGdParmsUpdate(p,jsH,atp,"gdParms1",&p->gd1Args)) != kOkPuRC ) + goto errLabel; + + if((rc = _cmPuJsonGainUpdate(p,jsH,atp,"gain")) != kOkPuRC ) + goto errLabel; + + errLabel: + + return rc; +} + +cmPuRC_t cmPuWriteJsonFile( cmPuH_t h, const cmChar_t* jsonFn ) +{ + cmPuRC_t rc = kOkPuRC; + cmJsonH_t jsH = cmJsonNullHandle; + cmPu_t* p = _cmPuHandleToPtr(h); + + // initialize a JSON tree from 'jsonFn'. + if( cmJsonInitializeFromFile(&jsH,jsonFn,&p->ctx) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON file initialization failed on '%s'.",cmStringNullGuard(jsonFn)); + goto errLabel; + } + + // update the 'autoTune' object in the JSON tree + if((rc = cmPuWriteJson(h,jsH,cmJsonRoot(jsH))) != kOkPuRC ) + goto errLabel; + + // write the JSON tree back to 'jsonFn'. + if( cmJsonWrite(jsH,cmJsonRoot(jsH),jsonFn) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON file save failed on '%s'.",cmStringNullGuard(jsonFn)); + goto errLabel; + } + + errLabel: + // release the JSON tree + if( cmJsonFinalize(&jsH) != kOkJsRC ) + rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON file finalization failed on '%s'.",cmStringNullGuard(jsonFn)); + + return rc; +} + +void cmPuTest(cmCtx_t* ctx) +{ + cmPuH_t h = cmPuNullHandle; + cmGateDetectParams gd0Args; + cmGateDetectParams gd1Args; + const cmChar_t* audioFn = "/home/kevin/media/audio/gate_detect/gate_detect0.aif"; + const cmChar_t* labelFn = "/home/kevin/media/audio/gate_detect/gate_detect0_labels.txt"; + const cmChar_t* outMtx0Fn = "/home/kevin/media/audio/gate_detect/gd0.mtx"; + const cmChar_t* outMtx1Fn = "/home/kevin/media/audio/gate_detect/gd1.mtx"; + const cmChar_t* outAudFn = "/home/kevin/media/audio/gate_detect/gd_gain0.aif"; + const cmChar_t* jsonFn = "/home/kevin/src/kc/src/data/rsrc1.txt"; + unsigned procSmpCnt = 64; + cmReal_t hopMs = 10; + + gd0Args.medCnt = 5; + gd0Args.avgCnt = 9; + gd0Args.suprCnt = 6; + gd0Args.offCnt = 3; + gd0Args.suprCoeff = 1.4; + gd0Args.onThreshDb = -53.0; + gd0Args.offThreshDb = -80.0; + + gd1Args = gd0Args; + gd1Args.onThreshDb = -45; + + if( cmPuAlloc(ctx, &h ) != kOkPuRC ) + goto errLabel; + + if( cmPuAutoGainCfg(h,audioFn,labelFn,outMtx0Fn,outMtx1Fn,outAudFn,procSmpCnt,hopMs,&gd0Args,&gd1Args) == kOkPuRC ) + { + cmPuReport(h,&ctx->rpt); + + cmPuWriteJsonFile( h, jsonFn ); + } + errLabel: + cmPuFree(&h); + + +} diff --git a/app/cmPickup.h b/app/cmPickup.h new file mode 100644 index 0000000..d25df06 --- /dev/null +++ b/app/cmPickup.h @@ -0,0 +1,96 @@ +#ifndef cmPickup_h +#define cmPickup_h + +#ifdef __cplusplus +extern "C" { +#endif + + + enum + { + kOkPuRC = cmOkRC, + kProcFailPuRC, + kJsonFailPuRC + }; + + typedef cmRC_t cmPuRC_t; + typedef cmHandle_t cmPuH_t; + + // This record holds information which is maintained on a per-pickup basis + typedef struct + { + unsigned begSmpIdx; // during auto-gain cfg set to the first sample in the audio example file where the group of notes from this pickup begin. + unsigned endSmpIdx; // ... end of the example notes for this pickup + unsigned midiPitch; // midi pitch associated with this pickup + unsigned onCnt; // during auto-gain cfg set to the count of onsets detected for this pickup + unsigned offCnt; // ... offset detected + cmReal_t gateMaxAvg; // avg of the the max gate RMS values for all detected notes + cmReal_t gain; // auto-gain coeff for this pickup + + } cmPuCh_t; + + + extern cmPuH_t cmPuNullHandle; + + cmPuRC_t cmPuAlloc( cmCtx_t* ctx, cmPuH_t* hp ); + cmPuRC_t cmPuFree( cmPuH_t* hp ); + bool cmPuIsValid( cmPuH_t h ); + + // Given a recorded audio file containing a set of notes recorded from each pickup, + // an Audacity label file which marks the beginning of teach set of notes, + // and a set of gate detector parameters attempt to calculate a set of gain + // coefficients to equalize the relative gain of all the pickup channels. + // This algorithm works in two passes: In the first pass the gain coefficient + // is established by finding an average RMS value among the examples and + // then increasing and decreasing the pickups to move the examples closer + // to the average. The gain is then adjusted and a second pass is made to + // examine how well the gate detectors work with the new gain setttings. + cmPuRC_t cmPuAutoGainCfg( + cmPuH_t h, + const cmChar_t* audioFn, // audio file containing a set of examples for each note + const cmChar_t* labelFn, // audicity label file with one marker preceding each set of notes + const cmChar_t* outMtx0Fn, // octave binary matrix file containing the intermediate and final results of the gate detector analysis after the first pass + const cmChar_t* outMtx1Fn, // octave binary matrix file containing the intermediate and final results of the gate detector analysis after the second pass + const cmChar_t* outAudFn, // audioFn rewritten with auto-gain applied + unsigned procSmpCnt, // analysis DSP frame size in samples + cmReal_t hopMs, // analysis window hop size in milliseconds + const cmGateDetectParams* gd0Args, // first pass gate detector args + const cmGateDetectParams* gd1Args ); // second pass gate detector args + + // Calls cmPuReadJson() and then prepends the fileDir to each of the + // file names. All the files are then read and written to this directory. + // and calls cmPuAutoGainCfg(). + cmPuRC_t cmPuAutoGainCfgFromJson( + cmPuH_t h, + const cmChar_t* fileDir, + cmJsonH_t jsH, + cmJsonNode_t* onp, + unsigned procSmpCnt ); + + + cmPuRC_t cmPuAutoGainExec( cmPuH_t h, const cmChar_t* fileDir, unsigned procSmpCnt ); + + // Load the 'parmsCfg', 'gdArgs' and 'gain' array from the JSON + // 'autoTune' object contained in the JSON object 'onp'. + cmPuRC_t cmPuReadJson( cmPuH_t h, cmJsonH_t jsH, cmJsonNode_t* onp ); + + unsigned cmPuChannelCount( cmPuH_t h ); + const cmPuCh_t* cmPuChannel( cmPuH_t h, unsigned chIdx ); + + + // Update the 'autoTune' JSON object contained the JSON object 'onp'. + cmPuRC_t cmPuWriteJson( cmPuH_t h, cmJsonH_t jsH, cmJsonNode_t* onp ); + + // Update the 'autoTune' JSON object in the JSON file 'jsonFn'. + // (Same as cmPuWriteJson() except the JSON tree to update is contained in a file.) + cmPuRC_t cmPuWriteJsonFile( cmPuH_t h, const cmChar_t* jsonFn ); + + void cmPuReport( cmPuH_t h, cmRpt_t* rpt ); + + void cmPuTest(cmCtx_t* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/cmScore.c b/app/cmScore.c new file mode 100644 index 0000000..ab6bff0 --- /dev/null +++ b/app/cmScore.c @@ -0,0 +1,635 @@ +#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 "cmMidi.h" +#include "cmLex.h" +#include "cmCsv.h" +#include "cmMidiFile.h" +#include "cmAudioFile.h" +#include "cmTimeLine.h" +#include "cmScore.h" + +/* +#include "cmComplexTypes.h" +#include "cmLinkedHeap.h" +#include "cmSymTbl.h" +#include "cmProcObj.h" +#include "cmProc.h" +#include "cmProcTemplate.h" +*/ + +#include "cmVectOpsTemplateMain.h" + +cmScH_t cmScNullHandle = cmSTATIC_NULL_HANDLE; + +enum +{ + kLabelCharCnt = 7, + + kInvalidDynScId = 0, + +}; + +enum +{ + kTypeLabelColScIdx = 3, + kDSecsColScIdx = 5, + kPitchColScIdx = 11, + kBarColScIdx = 13, + kSkipColScIdx = 14, + kEvenColScIdx = 15, + kTempoColScIdx = 16, + kDynColScIdx = 17 +}; + +typedef struct +{ + unsigned id; + cmChar_t label[ kLabelCharCnt + 1 ]; +} cmScEvtRef_t; + + +typedef struct +{ + cmErr_t err; + cmScoreEvt_t* array; + unsigned cnt; + cmCsvH_t cH; +} cmSc_t; + +cmScEvtRef_t _cmScEvtRefArray[] = +{ + { kTimeSigEvtScId, "tsg" }, + { kKeySigEvtScId, "ksg" }, + { kTempoEvtScId, "tmp" }, + { kTrackEvtScId, "trk" }, + { kTextEvtScId, "txt" }, + { kEOTrackEvtScId, "eot" }, + { kCopyEvtScId, "cpy"}, + { kBlankEvtScId, "blk"}, + { kBarEvtScId, "bar"}, + { kPgmEvtScId, "pgm" }, + { kCtlEvtScId, "ctl" }, + { kNonEvtScId, "non" }, + { kInvalidEvtScId, "***" } +}; + +cmScEvtRef_t _cmScDynRefArray[] = +{ + { 1, "pppp" }, + { 2, "ppp" }, + { 3, "pp" }, + { 4, "p" }, + { 5, "mp" }, + { 6, "m" }, + { 7, "mf" }, + { 8, "f" }, + { 9, "ff" }, + { 10, "fff" }, + { 11, "ffff"}, + { kInvalidDynScId, "***" }, +}; + +cmSc_t* _cmScHandleToPtr( cmScH_t h ) +{ + cmSc_t* p = (cmSc_t*)h.h; + assert( p != NULL ); + return p; +} + +unsigned _cmScEvtTypeLabelToId( const cmChar_t* label ) +{ + cmScEvtRef_t* r = _cmScEvtRefArray; + for(; r->id != kInvalidEvtScId; ++r ) + if( strcmp(label,r->label) == 0 ) + return r->id; + return kInvalidEvtScId; +} + +const cmChar_t* _cmScEvtTypeIdToLabel( unsigned id ) +{ + cmScEvtRef_t* r = _cmScEvtRefArray; + for(; r->id != kInvalidEvtScId; ++r ) + if( r->id == id ) + return r->label; + return NULL; +} + +unsigned _cmScDynLabelToId( const cmChar_t* label ) +{ + cmScEvtRef_t* r = _cmScDynRefArray; + for(; r->id != kInvalidEvtScId; ++r ) + if( strcmp(label,r->label) == 0 ) + return r->id; + return kInvalidDynScId; +} + +const cmChar_t* _cmScDynIdToLabel( unsigned id ) +{ + cmScEvtRef_t* r = _cmScDynRefArray; + for(; r->id != kInvalidDynScId; ++r ) + if( r->id == id ) + return r->label; + return NULL; +} + +unsigned _cmScLexSciPitchMatcher( const cmChar_t* cp, unsigned cn ) +{ + // first char must be "A-G" + if( strspn(cp,"ABCDEFG") != 1 ) + return 0; + + unsigned i = 1; + + // next char could be accidental + if( cp[i] == '#' || cp[i] == 'b' ) + ++i; // i==2 + + // the 2nd or 3rd char must be a digit + if( isdigit(cp[i]) == false ) + return 0; + + ++i; // i==2 or i==3 + + // the 3rd or 4th char must be a digit or EOS + if( isdigit(cp[i]) == false ) + return i; + + ++i; + + return i; + +} + +cmScRC_t _cmScFinalize( cmSc_t* p ) +{ + cmScRC_t rc = kOkScRC; + + if( cmCsvFinalize(&p->cH) != kOkCsvRC ) + return rc; + + cmMemFree(p->array); + cmMemFree(p); + return rc; +} + +cmScRC_t _cmScParseBar( cmSc_t* p, unsigned rowIdx, int* barNumb ) +{ + if((*barNumb = cmCsvCellInt(p->cH,rowIdx,kBarColScIdx)) == INT_MAX ) + return cmErrMsg(&p->err,kSyntaxErrScRC,"Unable to parse the bar number."); + return kOkScRC; +} + +cmScRC_t _cmScParseNoteOn( cmSc_t* p, unsigned rowIdx, cmScoreEvt_t* s, int barNumb, unsigned barNoteIdx ) +{ + cmScRC_t rc = kOkScRC; + unsigned flags = 0; + unsigned dynVal = kInvalidDynScId; + const cmChar_t* sciPitch; + cmMidiByte_t midiPitch; + const cmChar_t* attr; + double dsecs; + + if((sciPitch = cmCsvCellText(p->cH,rowIdx,kPitchColScIdx)) == NULL ) + return cmErrMsg(&p->err,kSyntaxErrScRC,"Expected a scientific pitch value"); + + if((midiPitch = cmSciPitchToMidi(sciPitch)) == kInvalidMidiPitch) + return cmErrMsg(&p->err,kSyntaxErrScRC,"Unable to convert the scientific pitch '%s' to a MIDI value. "); + + // it is possible that note delta-secs field is empty - so default to 0 + if((dsecs = cmCsvCellDouble(p->cH, rowIdx, kDSecsColScIdx )) == DBL_MAX) // Returns DBL_MAX on error. + dsecs = 0; + + if((attr = cmCsvCellText(p->cH,rowIdx,kSkipColScIdx)) != NULL && *attr == 's' ) + flags += kSkipScFl; + + if((attr = cmCsvCellText(p->cH,rowIdx,kEvenColScIdx)) != NULL && *attr == 'e' ) + flags += kEvenScFl; + + if((attr = cmCsvCellText(p->cH,rowIdx,kTempoColScIdx)) != NULL && *attr == 't' ) + flags += kTempoScFl; + + if((attr = cmCsvCellText(p->cH,rowIdx,kDynColScIdx)) != NULL ) + { + if((dynVal = _cmScDynLabelToId(attr)) == kInvalidDynScId ) + return cmErrMsg(&p->err,kSyntaxErrScRC,"Unknown dynamic label '%s'.",cmStringNullGuard(attr)); + + flags += kDynScFl; + } + + s->type = kNonEvtScId; + s->pitch = midiPitch; + s->flags = flags; + s->dynVal = dynVal; + s->barNumb = barNumb; + s->barNoteIdx = barNoteIdx; + + return rc; +} + +cmScRC_t _cmScParseFile( cmSc_t* p, cmCtx_t* ctx, const cmChar_t* fn ) +{ + cmScRC_t rc = kOkScRC; + unsigned barNoteIdx; + int barNumb; + + if( cmCsvInitialize(&p->cH, ctx ) != kOkCsvRC ) + { + rc = cmErrMsg(&p->err,kCsvFailScRC,"Score file initialization failed."); + goto errLabel; + } + + if( cmCsvLexRegisterMatcher(p->cH, cmCsvLexNextAvailId(p->cH), _cmScLexSciPitchMatcher ) != kOkCsvRC ) + { + rc = cmErrMsg(&p->err,kCsvFailScRC,"CSV token matcher registration failed."); + goto errLabel; + } + + if( cmCsvParseFile(p->cH, fn, 0 ) != kOkCsvRC ) + { + rc = cmErrMsg(&p->err,kCsvFailScRC,"CSV file parsing failed on the file '%s'.",cmStringNullGuard(fn)); + goto errLabel; + } + + p->cnt = cmCsvRowCount(p->cH); + p->array = cmMemAllocZ(cmScoreEvt_t,p->cnt); + + unsigned i,j; + + // skip labels line - start on line 1 + for(i=1,j=0; icnt && rc==kOkScRC; ++i) + { + // get the row 'type' label + const char* typeLabel; + if((typeLabel = cmCsvCellText(p->cH,i,kTypeLabelColScIdx)) == NULL ) + { + rc = cmErrMsg(&p->err,kSyntaxErrScRC,"No type label."); + break; + } + + // convert the row 'type' label to an id + unsigned tid; + if((tid = _cmScEvtTypeLabelToId(typeLabel)) == kInvalidEvtScId) + { + rc = cmErrMsg(&p->err,kSyntaxErrScRC,"Unknown type '%s'.",cmStringNullGuard(typeLabel)); + break; + } + + + switch(tid) + { + case kBarEvtScId: + // parse bar lines + if((rc = _cmScParseBar(p,i,&barNumb)) == kOkScRC ) + barNoteIdx = 0; + break; + + case kNonEvtScId: + // parse note-on events + if((rc = _cmScParseNoteOn(p, i, p->array + j, barNumb, barNoteIdx )) == kOkScRC ) + { + if( cmIsFlag(p->array[j].flags,kSkipScFl) == false ) + ++j; + + ++barNoteIdx; + } + break; + + default: + break; + } + + } + + if( rc == kSyntaxErrScRC ) + { + cmErrMsg(&p->err,rc,"Syntax error on line %i in '%s'.",i+1,cmStringNullGuard(fn)); + goto errLabel; + } + + p->cnt = i; + + errLabel: + + return rc; +} + +cmScRC_t cmScoreInitialize( cmCtx_t* ctx, cmScH_t* hp, const cmChar_t* fn ) +{ + cmScRC_t rc = kOkScRC; + if((rc = cmScoreFinalize(hp)) != kOkScRC ) + return rc; + + cmSc_t* p = cmMemAllocZ(cmSc_t,1); + + cmErrSetup(&p->err,&ctx->rpt,"Score"); + + if((rc = _cmScParseFile(p,ctx,fn)) != kOkScRC ) + goto errLabel; + + hp->h = p; + + errLabel: + if( rc != kOkScRC ) + _cmScFinalize(p); + + return rc; +} + +cmScRC_t cmScoreFinalize( cmScH_t* hp ) +{ + cmScRC_t rc = kOkScRC; + + if( hp == NULL || cmScoreIsValid(*hp) == false ) + return kOkScRC; + + cmSc_t* p = _cmScHandleToPtr(*hp); + + if((rc = _cmScFinalize(p)) != kOkScRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmScoreIsValid( cmScH_t h ) +{ return h.h != NULL; } + +unsigned cmScoreEvtCount( cmScH_t h ) +{ + cmSc_t* p = _cmScHandleToPtr(h); + return p->cnt; +} + +cmScoreEvt_t* cmScoreEvt( cmScH_t h, unsigned idx ) +{ + cmSc_t* p = _cmScHandleToPtr(h); + if( idx >= p->cnt ) + { + cmErrMsg(&p->err,kInvalidIdxScRC,"%i is an invalid index for %i records.",idx,p->cnt); + return NULL; + } + return p->array + idx; +} + +void cmScorePrint( cmScH_t h, cmRpt_t* rpt ) +{ + cmSc_t* p = _cmScHandleToPtr(h); + unsigned i; + for(i=0; i<20 /*p->cnt*/; ++i) + { + cmScoreEvt_t* r = p->array + i; + switch(r->type) + { + case kNonEvtScId: + cmRptPrintf(rpt,"%5i %3i %3i %s 0x%2x %c%c%c %s\n", + i, + r->barNumb, + r->barNoteIdx, + _cmScEvtTypeIdToLabel(r->type), + r->pitch, + cmIsFlag(r->flags,kEvenScFl) ? 'e' : ' ', + cmIsFlag(r->flags,kTempoScFl) ? 't' : ' ', + cmIsFlag(r->flags,kDynScFl) ? 'd' : ' ', + cmIsFlag(r->flags,kDynScFl) ? _cmScDynIdToLabel(r->dynVal) : ""); + break; + + default: + break; + } + } +} + +// Each time line note-on object is decorated (via cmTlObj_t.userDataPtr) with a +// cmScSyncState_t record. +typedef struct +{ + unsigned cnt; // count of candidate sync locations + double dist; // edit distance to the closest sync location + unsigned scEvtIdx; // score record this note-on is assigned to +} cmScSyncState_t; + +void _cmScSyncTimeLineAllocFree( cmTlH_t tlH, bool allocFl ) +{ + cmTlMidiEvt_t* mep = cmTlNextMidiEvtObjPtr(tlH,NULL,cmInvalidId); + + for(; mep != NULL; mep = cmTlNextMidiEvtObjPtr(tlH,&mep->obj,cmInvalidId)) + if( mep->msg->status == kNoteOnMdId ) + { + if( allocFl ) + mep->obj.userDataPtr = cmMemAllocZ(cmScSyncState_t,1); + else + cmMemPtrFree(&mep->obj.userDataPtr); + } +} + +void _cmScPrintSyncState( cmSc_t* p, cmTlH_t tlH ) +{ + unsigned i = 0; + double sr = cmTimeLineSampleRate(tlH); + cmTlMidiEvt_t* mep = cmTlNextMidiEvtObjPtr(tlH,NULL,cmInvalidId); + + for(; mep != NULL; mep = cmTlNextMidiEvtObjPtr(tlH,&mep->obj,cmInvalidId)) + if( mep->msg->status == kNoteOnMdId ) + { + cmScSyncState_t* ssp = (cmScSyncState_t*)mep->obj.userDataPtr; + + cmRptPrintf(p->err.rpt,"%5.3f pit:0x%2x (%3i) bar:%3i bni:%3i cnt:%3i dst:%1.6f ref:%s\n", + (mep->obj.ref->begSmpIdx - mep->obj.begSmpIdx) / (sr*60), + mep->msg->u.chMsgPtr->d0, + mep->msg->u.chMsgPtr->d0, + ssp->cnt ? p->array[ ssp->scEvtIdx ].barNumb : 0, + ssp->cnt ? p->array[ ssp->scEvtIdx ].barNoteIdx : 0, + ssp->cnt, + ssp->dist, + cmStringNullGuard(mep->obj.ref->name)); + + ++i; + if( i>=300) + break; + } +} + +double _cmScWndEditDist( cmSc_t* p, unsigned* mtx, const unsigned* tlWnd, cmScSyncState_t* tlObjWnd[], unsigned wndCnt ) +{ + unsigned scWnd[ wndCnt ]; + unsigned scIdxWnd[ wndCnt ]; + unsigned i; + unsigned wn = 0; + double minDist = DBL_MAX; + + // for each note-on score event + for(i=0; icnt; ++i) + if( p->array[i].type == kNonEvtScId ) + { + // shift the score event window to the the left + memmove(scWnd, scWnd+1, (wndCnt-1)*sizeof(scWnd[0])); + memmove(scIdxWnd,scIdxWnd+1,(wndCnt-1)*sizeof(scIdxWnd[0])); + + // insert new score event data on right + scWnd[wndCnt-1] = p->array[i].pitch; + scIdxWnd[wndCnt-1] = i; + ++wn; + + // if the window is full + if(wn >= wndCnt ) + { + // score the edit distance between the time line window and the edit window + double dist = cmVOU_LevEditDist(wndCnt,mtx,scWnd,wndCnt,tlWnd,wndCnt,wndCnt); + + if( dist < minDist ) + minDist = dist; + + // update the match information in the time line window + unsigned j; + for(j=0; jcnt == 0 || dist < tlObjWnd[j]->dist) ) + { + tlObjWnd[j]->cnt += 1; + tlObjWnd[j]->dist = dist; + tlObjWnd[j]->scEvtIdx = scIdxWnd[j]; + } + } + } + } + + return minDist; +} + +cmScRC_t cmScoreSyncTimeLine( cmScH_t scH, cmTlH_t tlH, unsigned edWndCnt, cmReal_t maxSecs ) +{ + cmSc_t* p = _cmScHandleToPtr(scH); + unsigned* edWndMtx = cmVOU_LevEditDistAllocMtx(edWndCnt); + unsigned maxMicroSecs = floor(maxSecs*1000000); + unsigned edWndData[ edWndCnt ]; + cmScSyncState_t* edWndObj[ edWndCnt ]; + + // alloc a sync state record for each MIDI note-on in the time line + _cmScSyncTimeLineAllocFree(tlH, true ); + + // get the first time line object + cmTlObj_t* rfp = cmTimeLineNextTypeObj(tlH,NULL,cmInvalidId,kMidiFileTlId); + + // interate through the time line in search of MIDI file objects + for(; rfp != NULL; rfp = cmTimeLineNextTypeObj(tlH,rfp,cmInvalidId,kMidiFileTlId)) + { + cmTlMidiFile_t* mfp = cmTimeLineMidiFileObjPtr(tlH,rfp); + unsigned curEdWndCnt = 0; + double prog = 0.1; + unsigned progIdx = 0; + + cmRptPrintf(p->err.rpt,"MIDI File:%s\n", cmMidiFileName( mfp->h )); + + // get first midi event object + cmTlMidiEvt_t* mep = cmTlNextMidiEvtObjPtr(tlH,NULL,cmInvalidId); + + // iterate through the time line in search of MIDI note-on events with belong to mfp + for(; mep != NULL; mep = cmTlNextMidiEvtObjPtr(tlH,&mep->obj,cmInvalidId) ) + { + if( mep->obj.ref == rfp && mep->msg->status == kNoteOnMdId ) + { + // If this notes inter-onset time is greater than maxMicroSecs + // then dispose of the current window and begin refilling it again. + if( mep->msg->dtick > maxMicroSecs ) + curEdWndCnt = 0; + + // shift window one slot to left + unsigned i; + for(i=0; imsg->u.chMsgPtr->d0; // d0=pitch + edWndObj[ edWndCnt-1] = (cmScSyncState_t*)mep->obj.userDataPtr; + + ++curEdWndCnt; + + // if a complete window exists then update the time-line / score match state + if( curEdWndCnt >= edWndCnt ) + _cmScWndEditDist( p, edWndMtx, edWndData, edWndObj, edWndCnt ); + + // print the progress + ++progIdx; + if( progIdx >= prog * mfp->noteOnCnt ) + { + cmRptPrintf(p->err.rpt,"%i ",(unsigned)round(prog*10)); + prog += 0.1; + } + } + } + cmRptPrintf(p->err.rpt,"\n"); + } + + _cmScPrintSyncState(p,tlH ); + + // free sync state records + _cmScSyncTimeLineAllocFree(tlH,false); + + cmMemFree(edWndMtx); + + return kOkScRC; +} + + +cmScRC_t cmScoreSyncTimeLineTest( cmCtx_t* ctx, const cmChar_t* timeLineJsFn, const cmChar_t* scoreCsvFn ) +{ + cmScRC_t rc = kOkScRC; + cmTlH_t tlH = cmTimeLineNullHandle; + cmScH_t scH = cmScNullHandle; + unsigned edWndCnt = 7; + cmReal_t maxSecs = 2.0; + + if((rc = cmTimeLineInitialize(ctx,&tlH,NULL,NULL)) != kOkTlRC ) + return cmErrMsg(&ctx->err,kTimeLineFailScRC,"Time line initialization failed.");; + + if((rc = cmTimeLineReadJson(tlH,timeLineJsFn)) != kOkTlRC ) + { + rc = cmErrMsg(&ctx->err,kTimeLineFailScRC,"Time line parse failed.");; + goto errLabel; + } + + //cmTimeLinePrint(tlH,&ctx->rpt); + + if(1) + { + if((rc = cmScoreInitialize(ctx,&scH,scoreCsvFn)) != kOkScRC ) + goto errLabel; + + + rc = cmScoreSyncTimeLine(scH, tlH, edWndCnt, maxSecs ); + + } + //cmScorePrint(scH, ctx->err.rpt ); + + + + errLabel: + cmScoreFinalize(&scH); + cmTimeLineFinalize(&tlH); + + return rc; + +} + + +void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ) +{ + cmScH_t h = cmScNullHandle; + if( cmScoreInitialize(ctx,&h,fn) != kOkScRC ) + return; + + cmScorePrint(h,&ctx->rpt); + + cmScoreFinalize(&h); +} diff --git a/app/cmScore.h b/app/cmScore.h new file mode 100644 index 0000000..9680a51 --- /dev/null +++ b/app/cmScore.h @@ -0,0 +1,82 @@ +#ifndef cmScore_h +#define cmScore_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkScRC = cmOkRC, + kCsvFailScRC, + kSyntaxErrScRC, + kInvalidIdxScRC, + kTimeLineFailScRC + + }; + + enum + { + kInvalidEvtScId = 0, + kTimeSigEvtScId, + kKeySigEvtScId, + kTempoEvtScId, + kTrackEvtScId, + kTextEvtScId, + kEOTrackEvtScId, + kCopyEvtScId, + kBlankEvtScId, + kBarEvtScId, + kPgmEvtScId, + kCtlEvtScId, + kNonEvtScId + }; + + enum + { + kEvenScFl = 0x01, // This note is marked for evenness measurement + kDynScFl = 0x02, // This note is marked for dynamics measurement + kTempoScFl = 0x03, // This note is marked for tempo measurement + kSkipScFl = 0x04 // this isn't a real event (e.g. tied note) skip over it + }; + + typedef struct + { + unsigned type; // Event type + double dsecs; // + cmMidiByte_t pitch; // MIDI pitch of this note + unsigned flags; // Attribute flags for this event + unsigned dynVal; // Dynamcis value pppp to ffff (1 to 11) for this note. + unsigned barNumb; // bar number of this event + unsigned barNoteIdx; // index of this note in this bar + } cmScoreEvt_t; + + + typedef cmRC_t cmScRC_t; + typedef cmHandle_t cmScH_t; + + extern cmScH_t cmScNullHandle; + + // Initialize a score object from a CSV File generated from a score spreadsheet. + cmScRC_t cmScoreInitialize( cmCtx_t* ctx, cmScH_t* hp, const cmChar_t* fn ); + cmScRC_t cmScoreFinalize( cmScH_t* hp ); + + bool cmScoreIsValid( cmScH_t h ); + + // Access the score data. + unsigned cmScoreEvtCount( cmScH_t h ); + cmScoreEvt_t* cmScoreEvt( cmScH_t h, unsigned idx ); + + void cmScorePrint( cmScH_t h, cmRpt_t* rpt ); + + cmScRC_t cmScoreSyncTimeLine( cmScH_t scH, cmTlH_t tlH, unsigned editDistWndCnt, cmReal_t maxNoteOffsetSecs ); + + cmScRC_t cmScoreSyncTimeLineTest( cmCtx_t* ctx, const cmChar_t* timeLineJsFn, const cmChar_t* scoreCsvFn ); + + void cmScoreTest( cmCtx_t* ctx, const cmChar_t* fn ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/cmTimeLine.c b/app/cmTimeLine.c new file mode 100644 index 0000000..3f36079 --- /dev/null +++ b/app/cmTimeLine.c @@ -0,0 +1,1542 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmLinkedHeap.h" +#include "cmJson.h" +#include "cmAudioFile.h" +#include "cmMidi.h" +#include "cmMidiFile.h" +#include "cmTimeLine.h" + +// id's used to track the type of a serialized object +enum +{ + kMsgTlId, + kObjTlId +}; + + +// +typedef struct _cmTlMsg_str +{ + unsigned typeId; // always set to kMsgTlId + cmTlUiMsgTypeId_t msgId; // + double srate; + unsigned seqCnt; + unsigned seqId; +} _cmTlMsg_t; + + +typedef struct _cmTlObj_str +{ + void* mem; + unsigned memByteCnt; + cmTlObj_t* obj; + struct _cmTlObj_str* prev; + struct _cmTlObj_str* next; +} _cmTlObj_t; + +typedef struct +{ + _cmTlObj_t* first; + _cmTlObj_t* last; +} _cmTlSeq_t; + +typedef struct +{ + cmErr_t err; + cmCtx_t ctx; + cmLHeapH_t lH; + double srate; + unsigned nextSeqId; + cmTlCb_t cbFunc; + void* cbArg; + unsigned nextUId; + char* tmpBuf; + unsigned seqCnt; + _cmTlSeq_t* seq; // seq[seqCnt] +} _cmTl_t; + +typedef struct +{ + char label[8]; + unsigned id; +} _cmTlId_t; + +_cmTlId_t _cmTlIdArray[] = +{ + { "mf", kMidiFileTlId }, + { "me", kMidiEvtTlId }, + { "af", kAudioFileTlId }, + { "ae", kAudioEvtTlId }, + { "mk", kMarkerTlId }, + { "", cmInvalidId } +}; + +cmTlH_t cmTimeLineNullHandle = cmSTATIC_NULL_HANDLE; + +_cmTl_t* _cmTlHandleToPtr( cmTlH_t h ) +{ + _cmTl_t* p = (_cmTl_t*)h.h; + assert( p != NULL ); + return p; +} + +_cmTlId_t* _cmTlIdLabelToRecd( _cmTl_t* p, const cmChar_t* label ) +{ + unsigned i; + if( label != NULL ) + for(i=0; _cmTlIdArray[i].id != cmInvalidId; ++i) + if( strcmp(_cmTlIdArray[i].label,label) == 0 ) + return _cmTlIdArray + i; + return NULL; +} + +_cmTlId_t* _cmTlIdToRecd( _cmTl_t* p, unsigned id ) +{ + unsigned i; + for(i=0; _cmTlIdArray[i].id != cmInvalidId; ++i) + if( _cmTlIdArray[i].id == id ) + return _cmTlIdArray + i; + return NULL; +} + +const cmChar_t* _cmTlIdToLabel( _cmTl_t* p, unsigned id ) +{ + _cmTlId_t* rp; + if((rp = _cmTlIdToRecd(p,id)) != NULL ) + return rp->label; + return ""; +} + +unsigned _cmTlIdLabelToId( _cmTl_t* p, const cmChar_t* label ) +{ + _cmTlId_t* rp; + if((rp = _cmTlIdLabelToRecd(p,label)) != NULL ) + return rp->id; + return cmInvalidId; +} + +// cast a generic object to a midi file object +cmTlMidiFile_t* _cmTlMidiFileObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) +{ + if( op->typeId != kMidiFileTlId ) + { + if( errFl && p != NULL) + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + return NULL; + } + + return (cmTlMidiFile_t*)op; +} + +// cast a generic object to a midi event object +cmTlMidiEvt_t* _cmTlMidiEvtObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) +{ + if( op->typeId != kMidiEvtTlId ) + { + if( errFl && p != NULL ) + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + return NULL; + } + + return (cmTlMidiEvt_t*)op; +} + +// case a generic object to an audio file object +cmTlAudioFile_t* _cmTlAudioFileObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) +{ + if( op->typeId != kAudioFileTlId ) + { + if( errFl && p != NULL) + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + return NULL; + } + + return (cmTlAudioFile_t*)op; +} + +// cast a generic object an audio event object to +cmTlAudioEvt_t* _cmTlAudioEvtObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) +{ + if( op->typeId != kAudioEvtTlId ) + { + if( errFl && p != NULL) + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + return NULL; + } + return (cmTlAudioEvt_t*)op; +} + + +// cast a generic object to a marker object +cmTlMarker_t* _cmTlMarkerObjPtr( _cmTl_t* p, cmTlObj_t* op, bool errFl ) +{ + if( op->typeId != kMarkerTlId ) + { + if( errFl && p != NULL) + cmErrMsg(&p->err,kTypeCvtFailTlRC,"A time line object type promotion failed."); + return NULL; + } + return (cmTlMarker_t*)op; +} + +cmTlMidiFile_t* _cmTimeLineMidiFileObjPtr( _cmTl_t* p, cmTlObj_t* op ) +{ return _cmTlMidiFileObjPtr(p,op,true); } + +cmTlMidiEvt_t* _cmTimeLineMidiEvtObjPtr( _cmTl_t* p, cmTlObj_t* op ) +{ return _cmTlMidiEvtObjPtr(p,op,true); } + +cmTlAudioFile_t* _cmTimeLineAudioFileObjPtr( _cmTl_t* p, cmTlObj_t* op ) +{ return _cmTlAudioFileObjPtr(p,op,true);} + +cmTlAudioEvt_t* _cmTimeLineAudioEvtObjPtr( _cmTl_t* p, cmTlObj_t* op ) +{ return _cmTlAudioEvtObjPtr(p,op,true);} + +cmTlMarker_t* _cmTimeLineMarkerObjPtr( _cmTl_t* p, cmTlObj_t* op ) +{ return _cmTlMarkerObjPtr(p,op,true);} + + +// Locate a record which matches 'name' and (optionally) 'seqId' +_cmTlObj_t* _cmTlFindRecd( _cmTl_t* p, unsigned seqId, const cmChar_t* name ) +{ + if( name == NULL ) + return NULL; + + unsigned i; + + for(i=0; iseqCnt; ++i) + if( seqId==cmInvalidId || seqId == i ) + { + _cmTlObj_t* op = p->seq[i].first; + while(op != NULL) + { + if( strcmp(op->obj->name,name) == 0 ) + return op; + + op = op->next; + } + } + + return NULL; +} + +// Returns true if 'op' is a child of 'ref'. +bool _cmTlIsChild( _cmTlObj_t* ref, _cmTlObj_t* op ) +{ + // if 'op' is not active then it can't be a child + if( op->obj == NULL ) + return false; + + // if 'ref' is NULL then match obj's which do not have a parent + if( ref == NULL ) + return op->obj->ref == NULL; + + // if 'ref' is the parent of 'op'. + return op->obj->ref == ref->obj; +} + +// calc the absolute start time of this object by adding the +// time time offsets of all ancestors. +int _cmTlStartTime( const cmTlObj_t* obj ) +{ + assert( obj != NULL ); + + int t = 0; + + do + { + t += obj->begSmpIdx; + obj=obj->ref; + }while( obj != NULL); + + + return t; +} + + + +// Locate the closest record which is before 'np'. +// When multiple records have the same distance to 'np' then the last one inserted +// is taken as the closest. This way records with equal time values will be +// secondarily sequenced on their order of insertion. +_cmTlObj_t* _cmTlFindRecdBefore( _cmTl_t* p, const _cmTlObj_t* np ) +{ + assert( np->obj!=NULL && np->obj->seqId < p->seqCnt ); + + // calc the absolute time of this object + //int absSmpIdx = _cmTlStartTime(np->obj); + int rsi; + _cmTlObj_t* rp = NULL; + _cmTlObj_t* op = p->seq[np->obj->seqId].first; + + //printf("type:%i %i\n",np->obj->typeId,absSmpIdx); + + // for each object in the list ... + while( op != NULL ) + { + int csi; + + //if( op!=np && op->obj!=NULL && (csi = _cmTlStartTime(op->obj)) <= absSmpIdx ) + if( (op!=np) && (op->obj!=NULL) && ((csi = op->obj->seqSmpIdx) <= np->obj->seqSmpIdx) ) + { + if( rp == NULL || csi >= rsi ) + { + rp = op; + rsi = csi; + } + } + op = op->next; + } + + return rp; +} + + + + +// Mark 'op' and all children of 'op' for deletion. +// Note that this function is recursive. +cmTlRC_t _cmTlDeleteDependentRecds( _cmTl_t* p, _cmTlObj_t* op ) +{ + assert( op->obj!=NULL && op->obj->seqId < p->seqCnt ); + + cmTlRC_t rc = kOkTlRC; + _cmTlObj_t* dp = p->seq[op->obj->seqId].first; + + // mark all recd's that are children of 'op' for deletion + while( dp != NULL ) + { + // if 'dp' is a child of 'op'. + if( _cmTlIsChild(op,dp) ) + if(( rc = _cmTlDeleteDependentRecds(p,dp)) != kOkTlRC ) + return rc; + + dp = dp->next; + } + + // release any resources held by 'op'. + switch(op->obj->typeId) + { + case kMidiFileTlId: + { + cmTlMidiFile_t* mp = _cmTimeLineMidiFileObjPtr(p,op->obj); + cmMidiFileClose(&mp->h); + } + break; + + case kMidiEvtTlId: + break; + + case kAudioFileTlId: + { + cmTlAudioFile_t* ap = _cmTimeLineAudioFileObjPtr(p,op->obj); + cmAudioFileDelete(&ap->h); + } + break; + + case kAudioEvtTlId: + break; + + case kMarkerTlId: + break; + + default: + return cmErrMsg(&p->err,kUnknownRecdTypeTlRC,"An unknown time line object type (%i) was encounterned during object deletion.",op->obj->typeId); + + } + + // mark 'op' as deleted by setting op->obj to NULL + op->obj = NULL; + + return kOkTlRC; +} + + +// Delete 'op' and all of its dependents. +cmTlRC_t _cmTlDeleteRecd( _cmTl_t* p, _cmTlObj_t* op ) +{ + if( op == NULL ) + return kOkTlRC; + + assert( op->obj!=NULL && op->obj->seqId < p->seqCnt ); + cmTlRC_t rc; + + _cmTlSeq_t* s = p->seq + op->obj->seqId; + + // mark this object and any objecs which are dependent on it for deletion + if((rc =_cmTlDeleteDependentRecds(p,op)) != kOkTlRC ) + return rc; + + + // unlink and delete and records marked for deletion + op = s->first; + + while( op ) + { + _cmTlObj_t* tp = op->next; + + // if this object is marked for deletion unlink it from this master list + if( op->obj == NULL ) + { + if( op->next != NULL ) + op->next->prev = op->prev; + else + { + assert( s->last == op ); + s->last = op->prev; + + } + + if( op->prev != NULL ) + op->prev->next = op->next; + else + { + assert( s->first == op ); + s->first = op->next; + } + + // free the record + cmLhFreePtr(p->lH,(void**)&op->mem); + + } + + op = tp; + } + + return rc; +} + +// Insert 'op' after 'bp'. +// If 'bp' is NULL then 'op' is inserted as p->seq[op->seqId].first. +void _cmTlInsertAfter( _cmTl_t* p, _cmTlObj_t* bp, _cmTlObj_t* op ) +{ + assert( op->obj!=NULL && op->obj->seqId < p->seqCnt ); + _cmTlSeq_t* s = p->seq + op->obj->seqId; + + op->prev = bp; + + // if op is being inserted at the beginning of the list + if( bp == NULL ) + { + op->next = s->first; + + if( s->first != NULL ) + s->first->prev = op; + + s->first = op; + + } + else // op is being inserted in the middle or end of the list + { + op->next = bp->next; + + if( bp->next != NULL ) + bp->next->prev = op; + else + { + // insertion at end + assert( bp == s->last ); + s->last = op; + } + + bp->next = op; + } + + if( s->last == NULL ) + s->last = op; + + if( s->first == NULL ) + s->first = op; +} + +// Allocate an object record +cmTlRC_t _cmTlAllocRecd2( + _cmTl_t* p, + const cmChar_t* nameStr, // NULL, empty or unique name + _cmTlObj_t* refPtr, // parent object + int begSmpIdx, // start time + unsigned durSmpCnt, // duration + unsigned typeId, // object type id + unsigned seqId, // owning seq + unsigned recdByteCnt, // byte for type specific data to follow _cmTlObj_t data + _cmTlObj_t** opp ) // return pointer +{ + *opp = NULL; + + if( nameStr == NULL ) + nameStr = ""; + + // get the length of the recd name field + unsigned nameByteCnt = strlen(nameStr)+1; + + // verify that the name was not already used by another recd + if( nameByteCnt>1 && _cmTlFindRecd(p,seqId,nameStr) != NULL ) + return cmErrMsg(&p->err,kDuplNameTlRC,"The object identifier '%s' was already used.",nameStr); + + assert( refPtr==NULL || refPtr->obj !=NULL ); + + if( refPtr != NULL && refPtr->obj->seqId != seqId ) + return cmErrMsg(&p->err,kInvalidSeqIdTlRC,"The sequence id of the reference object (%i) does not match the sequence id (%i) of the new object (label:%s).",refPtr->obj->seqId,seqId,cmStringNullGuard(nameStr)); + + if( seqId >= p->seqCnt ) + { + // assume the sequence id's arrive in increasing order + assert( seqId == p->seqCnt ); + assert( refPtr == NULL ); + p->seqCnt = seqId+1; + p->seq = cmMemResizePZ(_cmTlSeq_t,p->seq,p->seqCnt); + } + + // calc the total size of the recd. memory layout: [name /0 _cmTlObj_t cmTlObj_t ] + unsigned byteCnt = sizeof(unsigned) + sizeof(unsigned) + nameByteCnt + sizeof(_cmTlObj_t) + recdByteCnt; + void* mem = cmLHeapAllocZ( p->lH, byteCnt ); + unsigned* tidPtr = (unsigned*)mem; + unsigned* parentIdPtr = tidPtr + 1; + cmChar_t* name = (cmChar_t*)(parentIdPtr + 1); + _cmTlObj_t* op = (_cmTlObj_t*)(name + nameByteCnt); + cmTlObj_t* tp = (cmTlObj_t*)(op+1); + + // The entire object is contained in mem[] + // Memory Layout: + // kObjTlId parentId name[] \0 _cmTlObj_t cmTlObj_t [recdByteCnt - sizeof(cmTlObj_t)] + + strcpy(name,nameStr); + + // the first element in the mem[] buffer must be kObjTlId - this allows + // mem[] to be used directly as the serialized version of the buffer. + *tidPtr = kObjTlId; + *parentIdPtr = refPtr==NULL ? cmInvalidId : refPtr->obj->uid; + tp->reserved = op; + tp->seqId = seqId; + tp->name = name; + tp->uid = p->nextUId++; + tp->typeId = typeId; + tp->ref = refPtr==NULL ? NULL : refPtr->obj; + tp->begSmpIdx = refPtr==NULL ? 0 : begSmpIdx; + tp->durSmpCnt = durSmpCnt; + tp->seqSmpIdx = refPtr==NULL ? 0 : refPtr->obj->seqSmpIdx + begSmpIdx; + tp->flags = 0; + tp->text = NULL; + + op->obj = tp; + op->mem = mem; + op->memByteCnt = byteCnt; + op->next = NULL; + op->prev = NULL; + + + //if( seqId == 4 ) + // printf("seq:%i id:%i type:%i accum:%i ref:%i offs:%i %f\n",seqId, tp->uid, tp->typeId, tp->seqSmpIdx, refPtr==NULL?-1:refPtr->obj->uid, begSmpIdx, begSmpIdx/(96000.0*60.0) ); + + _cmTlInsertAfter(p, _cmTlFindRecdBefore(p,op), op ); + + + *opp = op; + + return kOkTlRC; +} + +cmTlRC_t _cmTlAllocRecd( _cmTl_t* p, const cmChar_t* nameStr, const cmChar_t* refIdStr, int begSmpIdx, unsigned durSmpCnt, unsigned typeId, unsigned seqId, unsigned recdByteCnt, _cmTlObj_t** opp ) +{ + // locate the obj recd that this recd is part of (the parent recd) + _cmTlObj_t* refPtr = _cmTlFindRecd(p,seqId,refIdStr); + + // if this obj has a parent but it was not found + if( refPtr == NULL && refIdStr!=NULL && strlen(refIdStr)>0 ) + return cmErrMsg(&p->err,kRefNotFoundTlRC,"Reference identifier '%s' not found for object '%s'.",refIdStr,cmStringNullGuard(nameStr)); + + return _cmTlAllocRecd2(p,nameStr,refPtr,begSmpIdx,durSmpCnt,typeId,seqId,recdByteCnt,opp); +} + +void _cmTlNotifyListener( _cmTl_t* p, cmTlUiMsgTypeId_t msgTypeId, _cmTlObj_t* op, unsigned seqId ) +{ + if( p->cbFunc == NULL ) + return; + + switch( msgTypeId ) + { + case kInitMsgTlId: + case kFinalMsgTlId: + case kDoneMsgTlId: + { + _cmTlMsg_t m; + m.typeId = kMsgTlId; + m.msgId = msgTypeId; + m.srate = p->srate; + m.seqCnt = p->seqCnt; + m.seqId = seqId; + p->cbFunc( p->cbArg, &m, sizeof(m) ); + } + break; + + case kInsertMsgTlId: + if( op != NULL ) + p->cbFunc( p->cbArg, op->mem, op->memByteCnt ); + break; + + default: + { assert(0); } + } +} + +cmTlRC_t _cmTlAllocAudioFileRecd( _cmTl_t* p, const cmChar_t* nameStr, const cmChar_t* refIdStr, int begSmpIdx, unsigned seqId, const cmChar_t* fn ) +{ + cmAudioFileH_t afH = cmNullAudioFileH; + cmAudioFileInfo_t info; + cmRC_t afRC = cmOkRC; + cmTlRC_t rc; + _cmTlObj_t* op = NULL; + unsigned recdByteCnt = sizeof(cmTlAudioFile_t) + strlen(fn) + 1; + + if( cmAudioFileIsValid( afH = cmAudioFileNewOpen(fn, &info, &afRC, p->err.rpt )) == false ) + return cmErrMsg(&p->err,kAudioFileFailTlRC,"The time line audio file '%s' could not be opened.",cmStringNullGuard(fn)); + + if((rc = _cmTlAllocRecd(p,nameStr,refIdStr,begSmpIdx,info.frameCnt,kAudioFileTlId,seqId,recdByteCnt,&op)) != kOkTlRC ) + goto errLabel; + + assert(op != NULL && fn != NULL ); + + cmTlAudioFile_t* ap = _cmTimeLineAudioFileObjPtr(p,op->obj); + char* cp = (char*)ap; + + assert(ap != NULL ); + + ap->h = afH; + ap->info = info; + ap->fn = cp + sizeof(cmTlAudioFile_t); + strcpy(ap->fn,fn); // copy the file name into the extra memory + + assert( ap->fn + strlen(fn) + 1 == cp + recdByteCnt ); + + op->obj->text = ap->fn; + + // notify listeners that an new object was created by sending a kObjTlId msg + // notifiy any listeners that a midi file object was created + //_cmTlNotifyListener(p, kInsertMsgTlId, op ); + + errLabel: + if( rc != kOkTlRC ) + cmAudioFileDelete(&afH); + + return rc; +} + + +cmTlRC_t _cmTlProcMidiFile( _cmTl_t* p, _cmTlObj_t* op, cmMidiFileH_t mfH ) + { + cmTlRC_t rc = kOkTlRC; + cmTlMidiFile_t* mfp = _cmTimeLineMidiFileObjPtr(p,op->obj); + unsigned mn = cmMidiFileMsgCount(mfH); + const cmMidiTrackMsg_t** mapp = cmMidiFileMsgArray(mfH); + unsigned mi = 0; + double accum = 0; + _cmTlObj_t* refOp = op; + bool fl = false; + unsigned dtick = 0; + mfp->noteOnCnt = 0; + + // for each midi message + for(; midtick; + + if( fl ) + { + dtick = 0; + fl = mp->dtick == 0; + } + + + accum += dtick * p->srate / 1000000; + + //int begSmpIdx = floor(accum_micros * p->srate / 1000000); + int begSmpIdx = floor( dtick * p->srate / 1000000 ); + int durSmpCnt = 0; + unsigned midiTrkMsgByteCnt = cmMidiFilePackTrackMsgBufByteCount( mp ); + unsigned recdByteCnt = sizeof(cmTlMidiEvt_t) + midiTrkMsgByteCnt; + + //if( mfp->obj.seqId==4 && mi<=25 ) + // printf("%s: bsi:%9i acc:%f smp acc:%f min %s\n", mp->status == kNoteOnMdId?"non":" ", begSmpIdx, accum, accum / (p->srate * 60),cmStringNullGuard(mfp->obj.name)); + + // count the note-on messages + if( mp->status == kNoteOnMdId ) + { + durSmpCnt = floor(mp->u.chMsgPtr->durTicks * p->srate / 1000000 ); + ++mfp->noteOnCnt; + } + + // allocate the generic time-line object record + if((rc = _cmTlAllocRecd2(p, NULL, refOp, begSmpIdx, durSmpCnt, kMidiEvtTlId, mfp->obj.seqId, recdByteCnt, &meop)) != kOkTlRC ) + goto errLabel; + + assert( meop != NULL ); + + cmTlMidiEvt_t* mep = _cmTimeLineMidiEvtObjPtr(p,meop->obj); + char* cp = (char*)mep; + + assert( mep != NULL ); + + // Set the cmTlMidiEvt_t.msg cmMidiTrkMsg_t pointer to point to the + // extra memory allocated just past the cmTlMidiEvt_t recd. + mep->msg = (cmMidiTrackMsg_t*)(cp + sizeof(cmTlMidiEvt_t)); + mep->midiFileObjId = mfp->obj.uid; + + // Do not write MIDI objects that are part of a MIDI file. They will be automatically + // loaded when the time line is loaded and therefore do not need to be save + // explicitely in the time line data. + meop->obj->flags = cmSetFlag(meop->obj->flags,kNoWriteTlFl); + + // Pack the cmMidiTrackMsg_t record into the extra memory + cmMidiFilePackTrackMsg( mp, (char*)mep->msg, midiTrkMsgByteCnt ); + + // verify that the memory allocation was calculated correctly + assert( cp + recdByteCnt == ((char*)mep->msg) + midiTrkMsgByteCnt); + + // notify any listeners that a new midi event was created by sending a kObjTlId msg. + //_cmTlNotifyListener(p, kInsertMsgTlId, meop ); + + // this midi event is the ref. for the next midi evt + refOp = meop; + } + + errLabel: + return rc; +} + +cmTlRC_t _cmTlAllocMidiFileRecd( _cmTl_t* p, const cmChar_t* nameStr, const cmChar_t* refIdStr, int begSmpIdx, unsigned seqId, const cmChar_t* fn ) +{ + cmMidiFileH_t mfH = cmMidiFileNullHandle; + cmTlRC_t rc = kOkTlRC; + _cmTlObj_t* op = NULL; + + // open the midi file + if( cmMidiFileOpen(fn, &mfH, &p->ctx ) != kOkMfRC ) + return cmErrMsg(&p->err,kMidiFileFailTlRC,"The time line midi file '%s' could not be opened.",cmStringNullGuard(fn)); + + // force the first msg to occurr one quarter note into the file + cmMidiFileSetDelay(mfH, cmMidiFileTicksPerQN(mfH) ); + + unsigned durSmpCnt = floor(cmMidiFileDurSecs(mfH)*p->srate); + + // convert the midi file from ticks to microseconds + cmMidiFileTickToMicros(mfH); + + // assign note durations to all note-on msg's + cmMidiFileCalcNoteDurations(mfH); + + unsigned recdByteCnt = sizeof(cmTlMidiFile_t) + strlen(fn) + 1; + + // allocate the midi file time line object + if((rc = _cmTlAllocRecd(p,nameStr,refIdStr,begSmpIdx,durSmpCnt,kMidiFileTlId,seqId,recdByteCnt,&op)) != kOkTlRC ) + goto errLabel; + + assert( op != NULL && fn != NULL ); + + cmTlMidiFile_t* mp = _cmTimeLineMidiFileObjPtr(p,op->obj); + char* cp = (char*)mp; + + assert(mp != NULL ); + + mp->h = mfH; + mp->fn = cp + sizeof(cmTlMidiFile_t); + strcpy(mp->fn,fn); // copy the filename into the extra memory + + assert( mp->fn + strlen(mp->fn) + 1 == cp + recdByteCnt ); + + op->obj->text = mp->fn; + + // notifiy any listeners that a midi file object was created + //_cmTlNotifyListener(p, kInsertMsgTlId, op ); + + // insert the events in the midi file as individual time line objects + if((rc = _cmTlProcMidiFile(p, op, mfH)) != kOkTlRC ) + goto errLabel; + + + errLabel: + if( rc != kOkTlRC ) + { + cmMidiFileClose(&mfH); + + _cmTlDeleteRecd(p, op); + + } + + return rc; +} + +cmTlRC_t _cmTlAllocMarkerRecd( _cmTl_t* p, const cmChar_t* nameStr, const cmChar_t* refIdStr, int begSmpIdx, unsigned durSmpCnt, unsigned seqId, const cmChar_t* text ) +{ + cmTlRC_t rc = kOkTlRC; + _cmTlObj_t* op = NULL; + const cmChar_t* textStr = text==NULL ? "" : text; + + // add memory at the end of the the cmTlMarker_t record to hold the text string. + unsigned recdByteCnt = sizeof(cmTlMarker_t) + strlen(textStr) + 1; + + if((rc = _cmTlAllocRecd(p,nameStr,refIdStr,begSmpIdx,durSmpCnt,kMarkerTlId,seqId,recdByteCnt,&op)) != kOkTlRC ) + goto errLabel; + + assert(op != NULL); + + cmTlMarker_t* mp = _cmTimeLineMarkerObjPtr(p,op->obj); + + assert(mp != NULL ); + + // copy the marker text string into the memory just past the cmTlMarker_t recd. + cmChar_t* tp = (cmChar_t*)(mp + 1); + strcpy(tp,textStr); + + mp->text = tp; + op->obj->text = tp; + + // notify listeners + //_cmTlNotifyListener(p, kInsertMsgTlId, op ); + + errLabel: + if( op != NULL && rc != kOkTlRC ) + _cmTlDeleteRecd(p,op); + + return rc; +} + +cmTlRC_t _cmTlAllocAudioEvtRecd( _cmTl_t* p, const cmChar_t* nameStr, const cmChar_t* refIdStr, int begSmpIdx, unsigned durSmpCnt, unsigned seqId, const cmChar_t* text ) +{ + cmTlRC_t rc = kOkTlRC; + _cmTlObj_t* op = NULL; + const cmChar_t* textStr = text==NULL ? "" : text; + + unsigned recdByteCnt = sizeof(cmTlAudioEvt_t) + strlen(textStr) + 1; + + if((rc = _cmTlAllocRecd(p,nameStr,refIdStr,begSmpIdx,durSmpCnt,kAudioEvtTlId,seqId,recdByteCnt,&op)) != kOkTlRC ) + goto errLabel; + + assert(op != NULL); + + cmTlAudioEvt_t* mp = _cmTimeLineAudioEvtObjPtr(p,op->obj); + + assert(mp != NULL ); + + // copy the marker text string into the memory just past the cmTlAudioEvt_t recd. + cmChar_t* tp = (cmChar_t*)(mp + 1); + strcpy(tp,textStr); + + mp->text = tp; + op->obj->text = tp; + + // notify listeners + //_cmTlNotifyListener(p, kInsertMsgTlId, op ); + + errLabel: + if( op != NULL && rc != kOkTlRC ) + _cmTlDeleteRecd(p,op); + + return rc; +} + +cmTlRC_t _cmTlAllocRecdFromJson(_cmTl_t* p,const cmChar_t* nameStr, const cmChar_t* typeIdStr,const cmChar_t* refIdStr, int begSmpIdx, unsigned durSmpCnt, unsigned seqId, const cmChar_t* textStr) +{ + cmTlRC_t rc = kOkTlRC; + unsigned typeId = _cmTlIdLabelToId(p,typeIdStr); + + switch( typeId ) + { + case kAudioFileTlId: rc = _cmTlAllocAudioFileRecd(p,nameStr,refIdStr,begSmpIdx, seqId,textStr); break; + case kMidiFileTlId: rc = _cmTlAllocMidiFileRecd( p,nameStr,refIdStr,begSmpIdx, seqId,textStr); break; + case kMarkerTlId: rc = _cmTlAllocMarkerRecd( p,nameStr,refIdStr,begSmpIdx,durSmpCnt,seqId,textStr); break; + case kAudioEvtTlId: rc = _cmTlAllocAudioEvtRecd( p,nameStr,refIdStr,begSmpIdx,durSmpCnt,seqId,textStr); break; + default: + rc = cmErrMsg(&p->err,kParseFailTlRC,"'%s' is not a valid 'objArray' record type.",cmStringNullGuard(typeIdStr)); + } + + return rc; +} + +cmTlRC_t _cmTimeLineFinalize( _cmTl_t* p ) +{ + cmTlRC_t rc = kOkTlRC; + unsigned i; + + for(i=0; iseqCnt; ++i) + while( p->seq[i].first != NULL ) + { + if((rc = _cmTlDeleteRecd(p,p->seq[i].first)) != kOkTlRC ) + goto errLabel; + } + + cmLHeapDestroy(&p->lH); + + //_cmTlNotifyListener(p, kFinalMsgTlId, NULL ); + + cmMemPtrFree(&p->tmpBuf); + + cmMemPtrFree(&p); + + + return kOkTlRC; + + errLabel: + return cmErrMsg(&p->err,kFinalizeFailTlRC,"Finalize failed."); +} + +cmTlRC_t cmTimeLineInitialize( cmCtx_t* ctx, cmTlH_t* hp, cmTlCb_t cbFunc, void* cbArg ) +{ + cmTlRC_t rc; + + if((rc = cmTimeLineFinalize(hp)) != kOkTlRC ) + return rc; + + _cmTl_t* p = cmMemAllocZ( _cmTl_t, 1 ); + + cmErrSetup(&p->err,&ctx->rpt,"Time Line"); + p->ctx = *ctx; + p->cbFunc = cbFunc; + p->cbArg = cbArg; + + if(cmLHeapIsValid( p->lH = cmLHeapCreate( 8192, ctx )) == false ) + { + rc = cmErrMsg(&p->err,kLHeapFailTlRC,"The linked heap allocation failed."); + goto errLabel; + } + + hp->h = p; + + return rc; + + errLabel: + _cmTimeLineFinalize(p); + return rc; +} + +cmTlRC_t cmTimeLineInitializeFromFile( cmCtx_t* ctx, cmTlH_t* hp, cmTlCb_t cbFunc, void* cbArg, const cmChar_t* fn ) +{ + cmTlRC_t rc; + if((rc = cmTimeLineInitialize(ctx,hp,cbFunc,cbArg)) != kOkTlRC ) + return rc; + + //_cmTl_t* p = _cmTlHandleToPtr(*hp); + //_cmTlNotifyListener(p, kInitMsgTlId, NULL ); + + return cmTimeLineReadJson(*hp,fn); +} + +cmTlRC_t cmTimeLineFinalize( cmTlH_t* hp ) +{ + cmTlRC_t rc; + if( hp == NULL || cmTimeLineIsValid(*hp) == false ) + return kOkTlRC; + + _cmTl_t* p = _cmTlHandleToPtr(*hp); + + + if((rc = _cmTimeLineFinalize(p)) != kOkTlRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmTimeLineIsValid( cmTlH_t h ) +{ return h.h != NULL; } + +double cmTimeLineSampleRate( cmTlH_t h ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return p->srate; +} + +cmTlObj_t* cmTimeLineNextObj( cmTlH_t h, cmTlObj_t* tp, unsigned seqId ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + _cmTlObj_t* op; + + assert( seqId < p->seqCnt ); + if( seqId >= p->seqCnt ) + return NULL; + + // if tp is NULL then start at the begin of the obj list ... + if( tp == NULL ) + op = p->seq[seqId].first; + else + { + // ... otherwise advance to the obj after tp + op = (_cmTlObj_t*)tp->reserved; + assert( op != NULL ); + op = op->next; + } + + // if the list is empty + if( op == NULL ) + return NULL; + + // return the next object which matches seqId or + // the next object if seqId == cmInvalidId + for(; op != NULL; op = op->next) + if( (seqId == cmInvalidId) || (op->obj->seqId == seqId) ) + return op->obj; + + return NULL; +} + +cmTlObj_t* cmTimeLineNextTypeObj( cmTlH_t h, cmTlObj_t* p, unsigned seqId, unsigned typeId ) +{ + cmTlObj_t* tp = p; + while( (tp = cmTimeLineNextObj(h,tp,seqId)) != NULL ) + if( typeId == cmInvalidId || tp->typeId == typeId ) + return tp; + + return NULL; +} + +cmTlMidiFile_t* cmTlNextMidiFileObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ) +{ + if((op = cmTimeLineNextTypeObj(h, op, seqId, kMidiFileTlId )) == NULL ) + return NULL; + return cmTimeLineMidiFileObjPtr(h,op); +} + +cmTlAudioFile_t* cmTlNextAudioFileObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ) +{ + if((op = cmTimeLineNextTypeObj(h, op, seqId, kAudioFileTlId )) == NULL ) + return NULL; + return cmTimeLineAudioFileObjPtr(h,op); +} + +cmTlMidiEvt_t* cmTlNextMidiEvtObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ) +{ + if((op = cmTimeLineNextTypeObj(h, op, seqId, kMidiEvtTlId )) == NULL ) + return NULL; + return cmTimeLineMidiEvtObjPtr(h,op); +} + +cmTlAudioEvt_t* cmTlNextAudioEvtObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ) +{ + if((op = cmTimeLineNextTypeObj(h, op, seqId, kAudioEvtTlId )) == NULL ) + return NULL; + return cmTimeLineAudioEvtObjPtr(h,op); +} + +cmTlMarker_t* cmTlNextMarkerObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ) +{ + if((op = cmTimeLineNextTypeObj(h, op, seqId, kMarkerTlId )) == NULL ) + return NULL; + return cmTimeLineMarkerObjPtr(h,op); +} + + +cmTlMidiFile_t* cmTimeLineMidiFileObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTimeLineMidiFileObjPtr(p,op); +} +cmTlAudioFile_t* cmTimeLineAudioFileObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTimeLineAudioFileObjPtr(p,op); +} +cmTlMidiEvt_t* cmTimeLineMidiEvtObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTimeLineMidiEvtObjPtr(p,op); +} +cmTlAudioEvt_t* cmTimeLineAudioEvtObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTimeLineAudioEvtObjPtr(p,op); +} +cmTlMarker_t* cmTimeLineMarkerObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTimeLineMarkerObjPtr(p,op); +} + +cmTlMidiFile_t* cmTlMidiFileObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTlMidiFileObjPtr(p,op,false); +} +cmTlAudioFile_t* cmTlAudioFileObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTlAudioFileObjPtr(p,op,false); +} +cmTlMidiEvt_t* cmTlMidiEvtObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTlMidiEvtObjPtr(p,op,false); +} +cmTlAudioEvt_t* cmTlAudioEvtObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTlAudioEvtObjPtr(p,op,false); +} +cmTlMarker_t* cmTlMarkerObjPtr( cmTlH_t h, cmTlObj_t* op ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return _cmTlMarkerObjPtr(p,op,false); +} + +cmTlRC_t cmTimeLineInsert( cmTlH_t h, const cmChar_t* nameStr, unsigned typeId, + const cmChar_t* fn, int begSmpIdx, unsigned durSmpCnt, const cmChar_t* refObjNameStr, unsigned seqId ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + + return _cmTlAllocRecdFromJson(p, nameStr, _cmTlIdToLabel(p,typeId), refObjNameStr, begSmpIdx, durSmpCnt, seqId, fn); +} + +cmTlObj_t* _cmTimeLineFindFile( _cmTl_t* p, const cmChar_t* fn, unsigned typeId ) +{ + unsigned i; + for(i=0; iseqCnt; ++i) + { + _cmTlObj_t* op = p->seq[i].first; + for(; op != NULL; op=op->next ) + if( op->obj->typeId == typeId ) + { + const cmChar_t* objFn = NULL; + + switch( typeId ) + { + case kAudioFileTlId: + objFn = ((cmTlAudioFile_t*)(op->obj))->fn; + break; + + case kMidiFileTlId: + objFn = ((cmTlMidiFile_t*)(op->obj))->fn; + break; + + default: + { assert(0); } + } + + if( strcmp(objFn,fn) == 0 ) + return op->obj; + + } + } + + return NULL; +} + +cmTlAudioFile_t* cmTimeLineFindAudioFile( cmTlH_t h, const cmChar_t* fn ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + cmTlObj_t* op; + if((op = _cmTimeLineFindFile(p,fn,kAudioFileTlId)) != NULL ) + return _cmTlAudioFileObjPtr(p,op,true); + return NULL; +} + +cmTlMidiFile_t* cmTimeLineFindMidiFile( cmTlH_t h, const cmChar_t* fn ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + cmTlObj_t* op; + if((op = _cmTimeLineFindFile(p,fn,kMidiFileTlId)) != NULL ) + return _cmTlMidiFileObjPtr(p,op,true); + return NULL; +} + + +cmTlRC_t _cmTlParseErr( cmErr_t* err, const cmChar_t* errLabelPtr, unsigned idx, const cmChar_t* fn ) +{ + cmTlRC_t rc; + + if( errLabelPtr != NULL ) + rc = cmErrMsg(err,kParseFailTlRC,"The required time line configuration field %s was not found in the record at index %i in '%s'.",cmStringNullGuard(errLabelPtr),idx,cmStringNullGuard(fn)); + else + rc = cmErrMsg(err,kParseFailTlRC,"The time_line configuration parse failed on the record at index %i in '%s'.",idx,cmStringNullGuard(fn)); + + return rc; +} + +cmTlRC_t cmTimeLineReadJson( cmTlH_t h, const cmChar_t* ifn ) +{ + cmTlRC_t rc = kOkTlRC; + cmJsonH_t jsH = cmJsonNullHandle; + cmJsonNode_t* jnp; + const cmChar_t* errLabelPtr; + int i; + + _cmTl_t* p = _cmTlHandleToPtr(h); + + // open the json file + if( cmJsonInitializeFromFile(&jsH, ifn, &p->ctx ) != kOkJsRC ) + return cmErrMsg(&p->err,kJsonFailTlRC,"JSON file initialization failed."); + + if((jnp = cmJsonFindValue(jsH,"time_line",cmJsonRoot(jsH),kObjectTId)) == NULL) + { + rc = cmErrMsg(&p->err,kParseFailTlRC,"The JSON 'time_line' object was not found in '%s'.",cmStringNullGuard(ifn)); + goto errLabel; + } + + if( cmJsonMemberValues(jnp,&errLabelPtr, + "srate",kRealTId,&p->srate, + "objArray",kArrayTId,&jnp, + NULL) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kParseFailTlRC,"The JSON 'time_line' object header parse failed in '%s'.",cmStringNullGuard(ifn)); + goto errLabel; + } + + for(i=0; ierr, errLabelPtr, i, ifn ); + goto errLabel; + } + + + if((rc = _cmTlAllocRecdFromJson(p,nameStr,typeIdStr,refIdStr,begSmpIdx,durSmpCnt,seqId,textStr)) != kOkTlRC ) + goto errLabel; + + } + + + errLabel: + if( rc != kOkTlRC ) + _cmTimeLineFinalize(p); + + cmJsonFinalize(&jsH); + return rc; +} + +unsigned cmTimeLineSeqCount( cmTlH_t h ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + return p->seqCnt; +} + +cmTlRC_t cmTimeLineSeqNotify( cmTlH_t h, unsigned seqId ) +{ + cmTlRC_t rc = kOkTlRC; + _cmTl_t* p = _cmTlHandleToPtr(h); + + assert( seqId < p->seqCnt ); + + _cmTlNotifyListener(p,kInitMsgTlId,NULL,seqId); + + _cmTlObj_t* op = p->seq[seqId].first; + for(; op!=NULL; op=op->next) + if( op->obj->seqId == seqId ) + _cmTlNotifyListener(p, kInsertMsgTlId, op, cmInvalidId ); + + _cmTlNotifyListener(p,kDoneMsgTlId,NULL,seqId); + + return rc; +} + +void _cmTimeLinePrintObj(_cmTl_t* p, _cmTlObj_t* op, cmRpt_t* rpt ) +{ + + cmRptPrintf(rpt,"%2i %5i %9i %9i %s %10s ",op->obj->seqId, op->obj->uid, op->obj->seqSmpIdx, op->obj->begSmpIdx, _cmTlIdToLabel(p,op->obj->typeId),op->obj->name); + switch(op->obj->typeId ) + { + case kMidiFileTlId: + { + cmTlMidiFile_t* rp = _cmTimeLineMidiFileObjPtr(p,op->obj); + cmRptPrintf(rpt,"%s ", cmMidiFileName(rp->h)); + } + break; + + case kMidiEvtTlId: + { + cmTlMidiEvt_t* rp = _cmTimeLineMidiEvtObjPtr(p,op->obj); + if( op->obj->ref != NULL ) + cmRptPrintf(rpt,"%s ",op->obj->ref->name); + cmRptPrintf(rpt,"%s ",cmMidiStatusToLabel(rp->msg->status)); + } + break; + + case kAudioFileTlId: + { + cmTlAudioFile_t* rp = _cmTimeLineAudioFileObjPtr(p,op->obj); + cmRptPrintf(rpt,"%s ", cmAudioFileName(rp->h)); + } + break; + + case kAudioEvtTlId: + { + cmTlAudioEvt_t* rp = _cmTimeLineAudioEvtObjPtr(p,op->obj); + cmRptPrintf(rpt,"%s",rp->text); + } + break; + + case kMarkerTlId: + { + cmTlMarker_t* rp = _cmTimeLineMarkerObjPtr(p,op->obj); + cmRptPrintf(rpt,"%s ", rp->text ); + } + break; + } + cmRptPrint(rpt,"\n"); +} + +cmTlRC_t _cmTimeLineWriteRecd( _cmTl_t* p, cmJsonH_t jsH, cmJsonNode_t* np, cmTlObj_t* obj ) +{ + cmTlRC_t rc = kOkTlRC; + + // if this recd is writable and has not been previously written + if( cmIsNotFlag(obj->flags,kNoWriteTlFl) && cmIsNotFlag(obj->flags,kReservedTlFl) ) + { + const cmChar_t* refStr = NULL; + + if( obj->ref != NULL ) + { + refStr = obj->ref->name; + + // if the ref obj was not previously written then write it (recursively) first + if( cmIsNotFlag(obj->ref->flags, kReservedTlFl) ) + if((rc = _cmTimeLineWriteRecd(p,jsH,np,obj->ref)) != kOkTlRC ) + return rc; + } + + // create the time-line recd + if( cmJsonInsertPairs( jsH, cmJsonCreateObject(jsH,np), + "label", kStringTId, obj->name, + "type", kStringTId, _cmTlIdToLabel(p, obj->typeId ), + "ref", kStringTId, refStr==NULL ? "" : refStr, + "offset", kIntTId, obj->begSmpIdx, + "smpCnt", kIntTId, obj->durSmpCnt, + "trackId", kIntTId, obj->seqId, + "textStr", kStringTId, obj->text==NULL ? "" : obj->text, + NULL ) != kOkJsRC ) + { + return cmErrMsg(&p->err,kJsonFailTlRC,"A time line insertion failed on label:%s type:%s text:%s.",cmStringNullGuard(obj->name),cmStringNullGuard(_cmTlIdToLabel(p, obj->typeId )), cmStringNullGuard(obj->text)); + } + + obj->flags = cmSetFlag(obj->flags, kReservedTlFl); + } + + return rc; +} + +cmTlRC_t cmTimeLineWrite( cmTlH_t h, const cmChar_t* fn ) +{ + cmTlRC_t rc = kOkTlRC; + _cmTl_t* p = _cmTlHandleToPtr(h); + cmJsonH_t jsH = cmJsonNullHandle; + cmJsonNode_t* np; + + // initialize a JSON tree + if( cmJsonInitialize(&jsH,&p->ctx) != kOkJsRC ) + return cmErrMsg(&p->err,kJsonFailTlRC,"JSON object initialization failed."); + + // create the root object + if((np = cmJsonCreateObject(jsH,NULL)) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailTlRC,"Time line root object create failed while creating the time line file '%s.",cmStringNullGuard(fn)); + goto errLabel; + } + + // create the time-line object + if((np = cmJsonInsertPairObject(jsH, np, "time_line" )) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailTlRC,"'time_line' object created failed while creating the time line file '%s.",cmStringNullGuard(fn)); + goto errLabel; + } + + // write the sample rate + if( cmJsonInsertPairReal(jsH, np, "srate", p->srate ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailTlRC,"'sample rate' output failed while creating the time line file '%s.",cmStringNullGuard(fn)); + goto errLabel; + } + + // create the time-line object array + if((np = cmJsonInsertPairArray(jsH, np, "objArray")) == NULL ) + { + rc = cmErrMsg(&p->err,kJsonFailTlRC,"'objArray' output failed while creating the time line file '%s.",cmStringNullGuard(fn)); + goto errLabel; + } + + + unsigned i; + // write each time-line object + for(i=0; iseqCnt; ++i) + { + _cmTlObj_t* op = p->seq[i].first; + for(; op != NULL; op=op->next ) + if((rc = _cmTimeLineWriteRecd(p,jsH,np,op->obj)) != kOkTlRC ) + goto errLabel; + } + + if( cmJsonErrorCode(jsH) == kOkJsRC ) + if( cmJsonWrite(jsH,NULL,fn) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailTlRC,"JSON write failed."); + goto errLabel; + } + + + errLabel: + + if( cmJsonFinalize(&jsH) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailTlRC,"JSON finalize failed."); + goto errLabel; + } + + return rc; +} + +cmTlRC_t cmTimeLinePrint( cmTlH_t h, cmRpt_t* rpt ) +{ + _cmTl_t* p = _cmTlHandleToPtr(h); + unsigned i; + for(i=0; iseqCnt; ++i) + { + _cmTlObj_t* op = p->seq[i].first; + while(op != NULL) + { + _cmTimeLinePrintObj(p,op,rpt); + op = op->next; + } + } + return kOkTlRC; +} + +cmTlRC_t cmTimeLinePrintFn( cmCtx_t* ctx, const cmChar_t* fn, cmRpt_t* rpt ) +{ + cmTlRC_t rc; + cmTlH_t h = cmTimeLineNullHandle; + + if((rc = cmTimeLineInitializeFromFile(ctx,&h,NULL,NULL,fn)) != kOkTlRC ) + return rc; + + cmTimeLinePrint(h,rpt); + + return cmTimeLineFinalize(&h); +} + + +cmTlRC_t cmTimeLineTest( cmCtx_t* ctx, const cmChar_t* jsFn ) +{ + cmTlRC_t rc = kOkTlRC; + cmTlH_t tlH = cmTimeLineNullHandle; + + if((rc = cmTimeLineInitialize(ctx,&tlH,NULL,NULL)) != kOkTlRC ) + return rc; + + if((rc = cmTimeLineReadJson(tlH,jsFn)) != kOkTlRC ) + goto errLabel; + + if((rc = cmTimeLineInsert(tlH,"Mark",kMarkerTlId,"My Marker",10,0,NULL,0)) != kOkTlRC ) + goto errLabel; + + cmTimeLinePrint(tlH, &ctx->rpt ); + + errLabel: + cmTimeLineFinalize(&tlH); + + return rc; +} + +cmTlRC_t _cmTimeLineDecodeObj( const void* msg, unsigned msgByteCnt, cmTlUiMsg_t* r ) +{ + cmTlRC_t rc = kOkTlRC; + unsigned* typeIdPtr = (unsigned*)msg; + unsigned* parentIdPtr = typeIdPtr + 1; + const char* text = (const char*)(parentIdPtr + 1); + _cmTlObj_t* op = (_cmTlObj_t*)(text + strlen(text) + 1); + cmTlObj_t* tp = (cmTlObj_t*)(op + 1 ); + + r->msgId = kInsertMsgTlId; + r->objId = tp->uid; + r->parentObjId = *parentIdPtr; + r->seqId = tp->seqId; + r->typeId = tp->typeId; + r->begSmpIdx = tp->begSmpIdx; + r->durSmpCnt = tp->durSmpCnt; + r->label = strlen(text)==0 ? NULL : text; + r->srate = 0; + r->midiTrkMsg = NULL; + r->textStr = NULL; + + switch( tp->typeId ) + { + case kMidiFileTlId: + r->textStr = _cmTimeLineMidiFileObjPtr(NULL,tp)->fn; + break; + + case kMidiEvtTlId: + { + cmTlMidiEvt_t* mep; + if((mep = _cmTimeLineMidiEvtObjPtr(NULL,tp)) != NULL ) + { + r->midiFileObjId = mep->midiFileObjId; + r->midiTrkMsg = mep->msg; + } + } + break; + + case kAudioFileTlId: + r->textStr = _cmTimeLineAudioFileObjPtr(NULL,tp)->fn; + break; + + case kAudioEvtTlId: + break; + + case kMarkerTlId: + r->textStr = _cmTimeLineMarkerObjPtr(NULL,tp)->text; + break; + + default: + { assert(0); } + } + + return rc; +} + +cmTlRC_t cmTimeLineDecode( const void* msg, unsigned msgByteCnt, cmTlUiMsg_t* r ) +{ + cmTlRC_t rc = kOkTlRC; + _cmTlMsg_t* m = (_cmTlMsg_t*)msg; + + switch( m->typeId ) + { + case kMsgTlId: + r->msgId = m->msgId; + r->srate = m->srate; + r->seqCnt= m->seqCnt; + r->seqId = m->seqId; + break; + + case kObjTlId: + r->msgId = kInsertMsgTlId; + rc = _cmTimeLineDecodeObj(msg,msgByteCnt,r); + break; + + default: + assert(0); + } + + return rc; + +} diff --git a/app/cmTimeLine.h b/app/cmTimeLine.h new file mode 100644 index 0000000..ea640bb --- /dev/null +++ b/app/cmTimeLine.h @@ -0,0 +1,217 @@ +#ifndef cmTimeLine_h +#define cmTimeLine_h + +#ifdef __cplusplus +extern "C" { +#endif + + + typedef cmHandle_t cmTlH_t; + + typedef cmRC_t cmTlRC_t; + + enum + { + kOkTlRC = cmOkRC, + kLHeapFailTlRC, + kParseFailTlRC, + kJsonFailTlRC, + kDuplNameTlRC, + kRefNotFoundTlRC, + kAudioFileFailTlRC, + kMidiFileFailTlRC, + kTypeCvtFailTlRC, + kUnknownRecdTypeTlRC, + kFinalizeFailTlRC, + kInvalidSeqIdTlRC + }; + + typedef enum + { + kMidiFileTlId = 0x01, + kMidiEvtTlId = 0x02, + kAudioFileTlId = 0x03, + kAudioEvtTlId = 0x04, + kMarkerTlId = 0x05 + } cmTlObjTypeId_t; + + enum + { + kReservedTlFl = 0x01, + kNoWriteTlFl = 0x02 // do not write this object in cmTimeLineWrite() + }; + + typedef void (*cmTlCb_t)( void* arg, const void* data, unsigned byteCnt ); + + typedef struct cmTlObj_str + { + void* reserved; // pt's to _cmTlObj_t + unsigned seqId; // sequence this object is assigned to + const cmChar_t* name; // text name of this object + unsigned uid; // generated unique id for this object + cmTlObjTypeId_t typeId; // type of the object + struct cmTlObj_str* ref; // time reference object + int begSmpIdx; // start time of this object as an offset from the start time of the reference object + unsigned durSmpCnt; // duration of this object + int seqSmpIdx; // absolute start time of this object within the sequence + const cmChar_t* text; // points to text assoc'd with this node (file name for audio/midi file, marker text) + unsigned flags; // see kXXXTlFl + void* userDataPtr; // user customizable data pointer + } cmTlObj_t; + + typedef struct + { + cmTlObj_t obj; + cmMidiFileH_t h; + unsigned noteOnCnt; + cmChar_t* fn; + } cmTlMidiFile_t; + + typedef struct + { + cmTlObj_t obj; + unsigned midiFileObjId; + const cmMidiTrackMsg_t* msg; // w/ dticks converted to microsecs + } cmTlMidiEvt_t; + + + typedef struct + { + cmTlObj_t obj; + cmAudioFileH_t h; + cmAudioFileInfo_t info; + cmChar_t* fn; + } cmTlAudioFile_t; + + typedef struct + { + cmTlObj_t obj; + cmAudioFileH_t h; + unsigned smpIdx; + unsigned smpCnt; + cmChar_t* text; + } cmTlAudioEvt_t; + + typedef struct + { + cmTlObj_t obj; + const cmChar_t* text; + } cmTlMarker_t; + + extern cmTlH_t cmTimeLineNullHandle; + + // + cmTlRC_t cmTimeLineInitialize( cmCtx_t* ctx, cmTlH_t* hp, cmTlCb_t cbFunc, void* cbArg ); + cmTlRC_t cmTimeLineInitializeFromFile( cmCtx_t* ctx, cmTlH_t* hp, cmTlCb_t cbFunc, void* cbArg, const cmChar_t* fn ); + + cmTlRC_t cmTimeLineFinalize( cmTlH_t* hp ); + + bool cmTimeLineIsValid( cmTlH_t h ); + double cmTimeLineSampleRate( cmTlH_t h ); + + // Return the object following 'p' assigned to 'seqId'. + // If 'p' is NULL then return the first object assigned to seqId. + // If 'seqId' is set to cmInvalidId then return the next object on any seq. + // If no objects follow 'p' on the specified sequence then return NULL. + cmTlObj_t* cmTimeLineNextObj( cmTlH_t h, cmTlObj_t* p, unsigned seqId ); + + // Same as cmTimeLineNextObj() but returns next object whose type matches 'typeId'. + cmTlObj_t* cmTimeLineNextTypeObj( cmTlH_t h, cmTlObj_t* p, unsigned seqId, unsigned typeId ); + + cmTlMidiFile_t* cmTlNextMidiFileObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ); + cmTlAudioFile_t* cmTlNextAudioFileObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ); + cmTlMidiEvt_t* cmTlNextMidiEvtObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ); + cmTlAudioEvt_t* cmTlNextAudioEvtObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ); + cmTlMarker_t* cmTlNextMarkerObjPtr( cmTlH_t h, cmTlObj_t* op, unsigned seqId ); + + + // Cast a genereic cmTlObj_t pointer to a specificy type. + cmTlMidiFile_t* cmTimeLineMidiFileObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlAudioFile_t* cmTimeLineAudioFileObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlMidiEvt_t* cmTimeLineMidiEvtObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlAudioEvt_t* cmTimeLineAudioEvtObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlMarker_t* cmTimeLineMarkerObjPtr( cmTlH_t h, cmTlObj_t* op ); + + // Same as cmTimeLineXXXObjPtr() but does not generate an error when + // 'op' does not point to the correct type. These function quietly + // return NULL if the requested type does not match. + cmTlMidiFile_t* cmTlMidiFileObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlAudioFile_t* cmTlAudioFileObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlMidiEvt_t* cmTlMidiEvtObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlAudioEvt_t* cmTlAudioEvtObjPtr( cmTlH_t h, cmTlObj_t* op ); + cmTlMarker_t* cmTlMarkerObjPtr( cmTlH_t h, cmTlObj_t* op ); + + cmTlAudioFile_t* cmTimeLineFindAudioFile( cmTlH_t h, const cmChar_t* fn ); + cmTlMidiFile_t* cmTimeLineFindMidiFile( cmTlH_t h, const cmChar_t* fn ); + + // 'typeId' = kAudioFileTlId, kMidiFileTId, kMarkerTlId. + // 'nameStr' and 'refObjNameStr' may be NULL. + cmTlRC_t cmTimeLineInsert( + cmTlH_t h, + const cmChar_t* nameStr, + unsigned typeId, + const cmChar_t* fn_or_markerStr, + int begSmpIdx, + unsigned durSmpCnt, + const cmChar_t* refObjNameStr, + unsigned seqId ); + + // See src/data/tl0.json for an example JSON file. + cmTlRC_t cmTimeLineReadJson( cmTlH_t h, const cmChar_t* ifn ); + + // Return a count of sequences contained within this timeline. + unsigned cmTimeLineSeqCount( cmTlH_t h ); + + // Make notifications for all records belonging to the sequence. + cmTlRC_t cmTimeLineSeqNotify( cmTlH_t h, unsigned seqId ); + + cmTlRC_t cmTimeLineWrite( cmTlH_t h, const cmChar_t* fn ); + + cmTlRC_t cmTimeLinePrint( cmTlH_t h, cmRpt_t* rpt ); + cmTlRC_t cmTimeLinePrintFn( cmCtx_t* ctx, const cmChar_t* fn, cmRpt_t* rpt ); + + cmTlRC_t cmTimeLineTest( cmCtx_t* ctx, const cmChar_t* jsFn ); + + // The time-line notifies listeners of initialization and finalization + // events via calling a cmTlCbFunc_t function. The argument to this + // function is a serialized cmTlUiMsg_t. The recipient of the callback + // can extract information from this message using cmTimeLineDecode() + // to form a cmTlUiMsg_t record. Note that all pointers internal to the + // cmTlUiMsg_t point into the message buffer itself. + + // id's used to indicate the type of a serialized object + typedef enum + { + kInvalidMsgTlId, + kInitMsgTlId, // A new time-line object is begin intialized. + kFinalMsgTlId, // A time-line object is being finalized. + kDoneMsgTlId, // All the objects assoc'd with a time line seq-notify have been sent. + kInsertMsgTlId, // A time-line object was inserted. + } cmTlUiMsgTypeId_t; + + typedef struct + { + cmTlUiMsgTypeId_t msgId; // See cmTlUiMsgTypeId_t. + unsigned objId; // Set to cmTlObj_t.uid + unsigned parentObjId; // cmTlObj_t.uid of the object this object's begSmpIdx is set relative to. + unsigned seqId; // + cmTlObjTypeId_t typeId; // + int begSmpIdx; // Time relative to parent. + unsigned durSmpCnt; // Duration of the object. + const char* label; // Object label (points to memory inside the serialized msg.) + double srate; // Only valid with kInitMsgTlId. + unsigned seqCnt; // Only valid with kInitMsgTlId. + const cmMidiTrackMsg_t* midiTrkMsg; // Only valid for typeId == kMidiEvtTlId. Internal pointers refer to memory inside the serialzed msg. buffer. + unsigned midiFileObjId; // Only valid for typeId == kMidiEvtTlId + const char* textStr; // filename for kAudioFileTlId and kMidiFileTlId, marker text for kMarkerTlId + } cmTlUiMsg_t; + + // Decode a serialized cmTlObj_t as passed to the cmTlCb_t listener + // callback function. + cmTlRC_t cmTimeLineDecode( const void* msg, unsigned msgByteCnt, cmTlUiMsg_t* uiMsg ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmApBuf.c b/cmApBuf.c new file mode 100644 index 0000000..cd4c449 --- /dev/null +++ b/cmApBuf.c @@ -0,0 +1,910 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmAudioPort.h" +#include "cmApBuf.h" +#include "cmThread.h" + +/* + This API is in general called by two types of threads: + audio devices threads and the client thread. There + may be multiple devie threads however there is only + one client thread. + + The audio device threads only call cmApBufUpdate(). + cmApBufUpdate() is never called by any other threads. + A call from the audio update threads targets specific channels + (cmApCh records). The variables within each channels that + it modifies are confined to: + on input channels: increments ii and increments fn (data is entering the ch. buffers) + on output channels: increments oi and decrements fn (data is leaving the ch. buffers) + + The client picks up incoming audio and provides outgoing audio via + cmApBufGet(). It then informs the cmApBuf() that it has completed + the audio data transfer by calling cmApBufAdvance(). + + cmApBufAdvance() modifies the following internal variables: + on input channels: increments oi and decrements fn (data has left the ch buffer) + on output channels: increments ii and increments fn (data has enterned the ch. buffer) + + Based on the above scenario the channel ii and oi variables are always thread-safe + because they are only changed by a single thread. + + ii oi fn + ------ ----- ---- + input ch: audio client both + output ch: client audio both + + The fn variable however is not thread-safe and therefore care must be taken as + to how it is read and updated. + + + + */ + +enum { kInApIdx=0, kOutApIdx=1, kIoApCnt=2 }; + +typedef struct +{ + unsigned fl; // kChApFl|kToneApFl|kMeterApFl ... + cmApSample_t* b; // b[n] + unsigned ii; // next in + unsigned oi; // next out + unsigned fn; // full cnt - count of samples currently in the buffer - incr'd by incoming, decr'd by outgoing + unsigned phs; // tone phase + double hz; // tone frequency + double gain; // channel gain + cmApSample_t* m; // m[mn] meter sample sum + unsigned mn; // length of m[] + unsigned mi; // next ele of m[] to rcv sum +} cmApCh; + +typedef struct +{ + unsigned chCnt; + cmApCh* chArray; + + unsigned n; // length of b[] (multiple of dspFrameCnt) bufCnt*framesPerCycle + double srate; // device sample rate; + + unsigned faultCnt; + unsigned framesPerCycle; + unsigned dspFrameCnt; + +} cmApIO; + +typedef struct +{ + // ioArray[] always contains 2 elements - one for input the other for output. + cmApIO ioArray[kIoApCnt]; +} cmApDev; + +typedef struct +{ + cmApDev* devArray; + unsigned devCnt; + unsigned meterMs; + + cmApSample_t* zeroBuf; // buffer of zeros + unsigned zeroBufCnt; // max of all dspFrameCnt for all devices. +} cmApBuf; + +cmApBuf _cmApBuf; + + +cmApSample_t _cmApMeterValue( const cmApCh* cp ) +{ + double sum = 0; + unsigned i; + for(i=0; imn; ++i) + sum += cp->m[i]; + + return (cmApSample_t)sqrt(sum); +} + +void _cmApSine( cmApCh* cp, cmApSample_t* b0, unsigned n0, cmApSample_t* b1, unsigned n1, unsigned stride, float srate ) +{ + unsigned i; + + for(i=0; iphs) + b0[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate )); + + for(i=0; iphs) + b1[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate )); +} + +cmApSample_t _cmApMeter( const cmApSample_t* b, unsigned bn, unsigned stride ) +{ + const cmApSample_t* ep = b + bn; + cmApSample_t sum = 0; + + for(; bb ); + cmMemPtrFree( &chPtr->m ); +} + +// n=buf sample cnt mn=meter buf smp cnt +void _cmApChInitialize( cmApCh* chPtr, unsigned n, unsigned mn ) +{ + _cmApChFinalize(chPtr); + + chPtr->b = n==0 ? NULL : cmMemAllocZ( cmApSample_t, n ); + chPtr->ii = 0; + chPtr->oi = 0; + chPtr->fn = 0; + chPtr->fl = (n!=0 ? kChApFl : 0); + chPtr->hz = 1000; + chPtr->gain = 1.0; + chPtr->mn = mn; + chPtr->m = cmMemAllocZ(cmApSample_t,mn); + chPtr->mi = 0; +} + +void _cmApIoFinalize( cmApIO* ioPtr ) +{ + unsigned i; + for(i=0; ichCnt; ++i) + _cmApChFinalize( ioPtr->chArray + i ); + + cmMemPtrFree(&ioPtr->chArray); + ioPtr->chCnt = 0; + ioPtr->n = 0; +} + +void _cmApIoInitialize( cmApIO* ioPtr, double srate, unsigned framesPerCycle, unsigned chCnt, unsigned n, unsigned meterBufN, unsigned dspFrameCnt ) +{ + unsigned i; + + _cmApIoFinalize(ioPtr); + + n += (n % dspFrameCnt); // force buffer size to be a multiple of dspFrameCnt + + ioPtr->chArray = chCnt==0 ? NULL : cmMemAllocZ( cmApCh, chCnt ); + ioPtr->chCnt = chCnt; + ioPtr->n = n; + ioPtr->faultCnt = 0; + ioPtr->framesPerCycle = framesPerCycle; + ioPtr->srate = srate; + ioPtr->dspFrameCnt = dspFrameCnt; + + for(i=0; ichArray + i, n, meterBufN ); + +} + +void _cmApDevFinalize( cmApDev* dp ) +{ + unsigned i; + for(i=0; iioArray+i); +} + +void _cmApDevInitialize( cmApDev* dp, double srate, unsigned iFpC, unsigned iChCnt, unsigned iBufN, unsigned oFpC, unsigned oChCnt, unsigned oBufN, unsigned meterBufN, unsigned dspFrameCnt ) +{ + unsigned i; + + _cmApDevFinalize(dp); + + for(i=0; iioArray+i, srate, fpc, chCnt, bufN, meterBufN, dspFrameCnt ); + + } + +} + +cmAbRC_t cmApBufInitialize( unsigned devCnt, unsigned meterMs ) +{ + cmAbRC_t rc; + + if((rc = cmApBufFinalize()) != kOkAbRC ) + return rc; + + _cmApBuf.devArray = cmMemAllocZ( cmApDev, devCnt ); + _cmApBuf.devCnt = devCnt; + _cmApBuf.meterMs = meterMs; + return kOkAbRC; +} + +cmAbRC_t cmApBufFinalize() +{ + unsigned i; + for(i=0; i<_cmApBuf.devCnt; ++i) + _cmApDevFinalize(_cmApBuf.devArray + i); + + cmMemPtrFree( &_cmApBuf.devArray ); + cmMemPtrFree( &_cmApBuf.zeroBuf ); + + _cmApBuf.devCnt = 0; + + return kOkAbRC; +} + +cmAbRC_t cmApBufSetup( + unsigned devIdx, + double srate, + unsigned dspFrameCnt, + unsigned bufCnt, + unsigned inChCnt, + unsigned inFramesPerCycle, + unsigned outChCnt, + unsigned outFramesPerCycle) +{ + cmApDev* devPtr = _cmApBuf.devArray + devIdx; + unsigned iBufN = bufCnt * inFramesPerCycle; + unsigned oBufN = bufCnt * outFramesPerCycle; + unsigned meterBufN = cmMax(1,floor(srate * _cmApBuf.meterMs / (1000.0 * outFramesPerCycle))); + + _cmApDevInitialize( devPtr, srate, inFramesPerCycle, inChCnt, iBufN, outFramesPerCycle, outChCnt, oBufN, meterBufN, dspFrameCnt ); + + if( inFramesPerCycle > _cmApBuf.zeroBufCnt || outFramesPerCycle > _cmApBuf.zeroBufCnt ) + { + _cmApBuf.zeroBufCnt = cmMax(inFramesPerCycle,outFramesPerCycle); + _cmApBuf.zeroBuf = cmMemResizeZ(cmApSample_t,_cmApBuf.zeroBuf,_cmApBuf.zeroBufCnt); + } + + return kOkAbRC; +} + +cmAbRC_t cmApBufPrimeOutput( unsigned devIdx, unsigned audioCycleCnt ) +{ + cmApIO* iop = _cmApBuf.devArray[devIdx].ioArray + kOutApIdx; + unsigned i; + + for(i=0; ichCnt; ++i) + { + cmApCh* cp = iop->chArray + i; + unsigned bn = iop->n * sizeof(cmApSample_t); + memset(cp->b,0,bn); + cp->oi = 0; + cp->ii = iop->framesPerCycle * audioCycleCnt; + cp->fn = iop->framesPerCycle * audioCycleCnt; + } + + return kOkAbRC; +} + +cmAbRC_t cmApBufUpdate( + cmApAudioPacket_t* inPktArray, + unsigned inPktCnt, + cmApAudioPacket_t* outPktArray, + unsigned outPktCnt ) +{ + unsigned i,j; + + // copy samples from the packet to the buffer + if( inPktArray != NULL ) + { + for(i=0; idevIdx].ioArray + kInApIdx; // dest io recd + + // for each source packet channel and enabled dest channel + for(j=0; jchCnt; ++j) + { + cmApCh* cp = ip->chArray + pp->begChIdx +j; // dest ch + unsigned n0 = ip->n - cp->ii; // first dest segment + unsigned n1 = 0; // second dest segment + + assert(pp->begChIdx + j < ip->chCnt ); + + // if the incoming samples would overflow the buffer then ignore them + if( cp->fn + pp->audioFramesCnt > ip->n ) + { + ++ip->faultCnt; // record input overflow + continue; + } + + // if the incoming samples would go off the end of the buffer then + // copy in the samples in two segments (one at the end and another at begin of dest channel) + if( n0 < pp->audioFramesCnt ) + n1 = pp->audioFramesCnt-n0; + else + n0 = pp->audioFramesCnt; + + bool enaFl = cmIsFlag(cp->fl,kChApFl) && cmIsFlag(cp->fl,kMuteApFl)==false; + const cmApSample_t* sp = enaFl ? ((cmApSample_t*)pp->audioBytesPtr) + j : _cmApBuf.zeroBuf; + unsigned ssn = enaFl ? pp->chCnt : 1; // stride (packet samples are interleaved) + cmApSample_t* dp = cp->b + cp->ii; + const cmApSample_t* ep = dp + n0; + + + // update the meter + if( cmIsFlag(cp->fl,kMeterApFl) ) + { + cp->m[cp->mi] = _cmApMeter(sp,pp->audioFramesCnt,pp->chCnt); + cp->mi = (cp->mi + 1) % cp->mn; + } + + // if the test tone is enabled on this input channel + if( enaFl && cmIsFlag(cp->fl,kToneApFl) ) + { + _cmApSine(cp, dp, n0, cp->b, n1, 1, ip->srate ); + } + else // otherwise copy samples from the packet to the buffer + { + // copy the first segment + for(; dp < ep; sp += ssn ) + *dp++ = cp->gain * *sp; + + // if there is a second segment + if( n1 > 0 ) + { + // copy the second segment + dp = cp->b; + ep = dp + n1; + for(; dpgain * *sp; + } + } + + + // advance the input channel buffer + cp->ii = n1>0 ? n1 : cp->ii + n0; + //cp->fn += pp->audioFramesCnt; + cmThUIntIncr(&cp->fn,pp->audioFramesCnt); + } + } + } + + // copy samples from the buffer to the packet + if( outPktArray != NULL ) + { + for(i=0; idevIdx].ioArray + kOutApIdx; // dest io recd + + + // for each dest packet channel and enabled source channel + for(j=0; jchCnt; ++j) + { + cmApCh* cp = op->chArray + pp->begChIdx + j; // dest ch + unsigned n0 = op->n - cp->oi; // first src segment + unsigned n1 = 0; // second src segment + volatile unsigned fn = cp->fn; // store fn because it may be changed by the client thread + + // if the outgoing samples will underflow the buffer + if( pp->audioFramesCnt > fn ) + { + ++op->faultCnt; // record an output underflow + + // if the buffer is empty - zero the packet and return + if( fn == 0 ) + { + memset( pp->audioBytesPtr, 0, pp->audioFramesCnt*sizeof(cmApSample_t)); + continue; + } + + // ... otherwise decrease the count of returned samples + pp->audioFramesCnt = fn; + + } + + // if the outgong segments would go off the end of the buffer then + // arrange to wrap to the begining of the buffer + if( n0 < pp->audioFramesCnt ) + n1 = pp->audioFramesCnt-n0; + else + n0 = pp->audioFramesCnt; + + cmApSample_t* dp = ((cmApSample_t*)pp->audioBytesPtr) + j; + bool enaFl = cmIsFlag(cp->fl,kChApFl) && cmIsFlag(cp->fl,kMuteApFl)==false; + + // if the tone is enabled on this channel + if( enaFl && cmIsFlag(cp->fl,kToneApFl) ) + { + _cmApSine(cp, dp, n0, dp + n0*pp->chCnt, n1, pp->chCnt, op->srate ); + } + else // otherwise copy samples from the output buffer to the packet + { + const cmApSample_t* sp = enaFl ? cp->b + cp->oi : _cmApBuf.zeroBuf; + const cmApSample_t* ep = sp + n0; + + // copy the first segment + for(; sp < ep; dp += pp->chCnt ) + *dp = cp->gain * *sp++; + + // if there is a second segment + if( n1 > 0 ) + { + // copy the second segment + sp = enaFl ? cp->b : _cmApBuf.zeroBuf; + ep = sp + n1; + for(; spchCnt ) + *dp = cp->gain * *sp++; + + } + } + + // update the meter + if( cmIsFlag(cp->fl,kMeterApFl) ) + { + cp->m[cp->mi] = _cmApMeter(((cmApSample_t*)pp->audioBytesPtr)+j,pp->audioFramesCnt,pp->chCnt); + cp->mi = (cp->mi + 1) % cp->mn; + } + + // advance the output channel buffer + cp->oi = n1>0 ? n1 : cp->oi + n0; + //cp->fn -= pp->audioFramesCnt; + cmThUIntDecr(&cp->fn,pp->audioFramesCnt); + } + } + } + return kOkAbRC; +} + +unsigned cmApBufMeterMs() +{ return _cmApBuf.meterMs; } + +unsigned cmApBufChannelCount( unsigned devIdx, unsigned flags ) +{ + if( devIdx == cmInvalidIdx ) + return 0; + + unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; + return _cmApBuf.devArray[devIdx].ioArray[ idx ].chCnt; +} + +void cmApBufSetFlag( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ + if( devIdx == cmInvalidIdx ) + return; + + unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; + bool enableFl = flags & kEnableApFl ? true : false; + unsigned i = chIdx != -1 ? chIdx : 0; + unsigned n = chIdx != -1 ? chIdx+1 : _cmApBuf.devArray[devIdx].ioArray[idx].chCnt; + + for(; ifl = cmEnaFlag(cp->fl, flags & (kChApFl|kToneApFl|kMeterApFl|kMuteApFl|kPassApFl), enableFl ); + } + +} + +bool cmApBufIsFlag( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ + if( devIdx == cmInvalidIdx ) + return false; + + unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; + return cmIsFlag(_cmApBuf.devArray[devIdx].ioArray[idx].chArray[chIdx].fl,flags); +} + + +void cmApBufEnableChannel( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ cmApBufSetFlag(devIdx,chIdx,flags | kChApFl); } + +bool cmApBufIsChannelEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return cmApBufIsFlag(devIdx, chIdx, flags | kChApFl); } + +void cmApBufEnableTone( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ cmApBufSetFlag(devIdx,chIdx,flags | kToneApFl); } + +bool cmApBufIsToneEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return cmApBufIsFlag(devIdx,chIdx,flags | kToneApFl); } + +void cmApBufEnableMute( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ cmApBufSetFlag(devIdx,chIdx,flags | kMuteApFl); } + +bool cmApBufIsMuteEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return cmApBufIsFlag(devIdx,chIdx,flags | kMuteApFl); } + +void cmApBufEnablePass( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ cmApBufSetFlag(devIdx,chIdx,flags | kPassApFl); } + +bool cmApBufIsPassEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return cmApBufIsFlag(devIdx,chIdx,flags | kPassApFl); } + +void cmApBufEnableMeter( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ cmApBufSetFlag(devIdx,chIdx,flags | kMeterApFl); } + +bool cmApBufIsMeterEnabled(unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return cmApBufIsFlag(devIdx,chIdx,flags | kMeterApFl); } + +cmApSample_t cmApBufMeter(unsigned devIdx, unsigned chIdx, unsigned flags ) +{ + if( devIdx == cmInvalidIdx ) + return 0; + + unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; + const cmApCh* cp = _cmApBuf.devArray[devIdx].ioArray[idx].chArray + chIdx; + return _cmApMeterValue(cp); +} + +void cmApBufSetGain( unsigned devIdx, unsigned chIdx, unsigned flags, double gain ) +{ + if( devIdx == cmInvalidIdx ) + return; + + unsigned idx = flags & kInApFl ? kInApIdx : kOutApIdx; + unsigned i = chIdx != -1 ? chIdx : 0; + unsigned n = i + (chIdx != -1 ? 1 : _cmApBuf.devArray[devIdx].ioArray[idx].chCnt); + + for(; ichCnt, meterCnt ); + unsigned i; + + if( faultCntPtr != NULL ) + *faultCntPtr = iop->faultCnt; + + for(i=0; ichArray + i); + + return chCnt; +} + + +bool cmApBufIsDeviceReady( unsigned devIdx, unsigned flags ) +{ + //bool iFl = true; + //bool oFl = true; + unsigned i = 0; + + if( devIdx == cmInvalidIdx ) + return false; + + if( flags & kInApFl ) + { + const cmApIO* ioPtr = _cmApBuf.devArray[devIdx].ioArray + kInApIdx; + for(i=0; ichCnt; ++i) + if( ioPtr->chArray[i].fn < ioPtr->dspFrameCnt ) + return false; + + //iFl = ioPtr->fn > ioPtr->dspFrameCnt; + } + + if( flags & kOutApFl ) + { + const cmApIO* ioPtr = _cmApBuf.devArray[devIdx].ioArray + kOutApIdx; + + for(i=0; ichCnt; ++i) + if( (ioPtr->n - ioPtr->chArray[i].fn) < ioPtr->dspFrameCnt ) + return false; + + + //oFl = (ioPtr->n - ioPtr->fn) > ioPtr->dspFrameCnt; + } + + return true; + //return iFl & oFl; +} + + +// Note that his function returns audio samples but does NOT +// change any internal states. +void cmApBufGet( unsigned devIdx, unsigned flags, cmApSample_t* bufArray[], unsigned bufChCnt ) +{ + unsigned i; + if( devIdx == cmInvalidIdx ) + { + for(i=0; ichCnt ? bufChCnt : ioPtr->chCnt; + //unsigned offs = flags & kInApFl ? ioPtr->oi : ioPtr->ii; + cmApCh* cp = ioPtr->chArray; + + for(i=0; ioi : cp->ii; + bufArray[i] = cmIsFlag(cp->fl,kChApFl) ? cp->b + offs : NULL; + } + +} + +void cmApBufGetIO( unsigned iDevIdx, cmApSample_t* iBufArray[], unsigned iBufChCnt, unsigned oDevIdx, cmApSample_t* oBufArray[], unsigned oBufChCnt ) +{ + cmApBufGet( iDevIdx, kInApFl, iBufArray, iBufChCnt ); + cmApBufGet( oDevIdx, kOutApFl,oBufArray, oBufChCnt ); + + unsigned i = 0; + + if( iDevIdx != cmInvalidIdx && oDevIdx != cmInvalidIdx ) + { + const cmApIO* ip = _cmApBuf.devArray[iDevIdx].ioArray + kInApIdx; + const cmApIO* op = _cmApBuf.devArray[oDevIdx].ioArray + kOutApIdx; + unsigned minChCnt = cmMin(iBufChCnt,oBufChCnt); + unsigned frmCnt = cmMin(ip->dspFrameCnt,op->dspFrameCnt); + unsigned byteCnt = frmCnt * sizeof(cmApSample_t); + + for(i=0; ichArray + i; + cmApCh* icp = ip->chArray + i; + + if( oBufArray[i] != NULL ) + { + // if either the input or output channel is marked for pass-through + if( cmAllFlags(ocp->fl,kPassApFl) || cmAllFlags(icp->fl,kPassApFl) ) + { + memcpy( oBufArray[i], iBufArray[i], byteCnt ); + + // set the output buffer to NULL to prevent it being over written by the client + oBufArray[i] = NULL; + } + else + { + // zero the output buffer + memset(oBufArray[i],0,byteCnt); + } + } + } + } + + if( oDevIdx != cmInvalidIdx ) + { + const cmApIO* op = _cmApBuf.devArray[oDevIdx].ioArray + kOutApIdx; + unsigned byteCnt = op->dspFrameCnt * sizeof(cmApSample_t); + + for(; ichCnt; ++i) + { + cmApCh* cp = ioPtr->chArray + i; + cp->oi = (cp->oi + ioPtr->dspFrameCnt) % ioPtr->n; + //cp->fn -= ioPtr->dspFrameCnt; + cmThUIntDecr(&cp->fn,ioPtr->dspFrameCnt); + } + + //ioPtr->oi = (ioPtr->oi + ioPtr->dspFrameCnt) % ioPtr->n; + //ioPtr->fn -= ioPtr->dspFrameCnt; + } + + if( flags & kOutApFl ) + { + cmApIO* ioPtr = _cmApBuf.devArray[devIdx].ioArray + kOutApIdx; + for(i=0; ichCnt; ++i) + { + cmApCh* cp = ioPtr->chArray + i; + cp->ii = (cp->ii + ioPtr->dspFrameCnt) % ioPtr->n; + //cp->fn += ioPtr->dspFrameCnt; + cmThUIntIncr(&cp->fn,ioPtr->dspFrameCnt); + } + + + //ioPtr->ii = (ioPtr->ii + ioPtr->dspFrameCnt) % ioPtr->n; + //ioPtr->fn += ioPtr->dspFrameCnt; + } +} + + +void cmApBufInputToOutput( unsigned iDevIdx, unsigned oDevIdx ) +{ + if( iDevIdx == cmInvalidIdx || oDevIdx == cmInvalidIdx ) + return; + + unsigned iChCnt = cmApBufChannelCount( iDevIdx, kInApFl ); + unsigned oChCnt = cmApBufChannelCount( oDevIdx, kOutApFl ); + unsigned chCnt = iChCnt < oChCnt ? iChCnt : oChCnt; + + unsigned i; + + cmApSample_t* iBufPtrArray[ iChCnt ]; + cmApSample_t* oBufPtrArray[ oChCnt ]; + + + while( cmApBufIsDeviceReady( iDevIdx, kInApFl ) && cmApBufIsDeviceReady( oDevIdx, kOutApFl ) ) + { + cmApBufGet( iDevIdx, kInApFl, iBufPtrArray, iChCnt ); + cmApBufGet( oDevIdx, kOutApFl, oBufPtrArray, oChCnt ); + + // Warning: buffer pointers to disabled channels will be set to NULL + + for(i=0; idspFrameCnt == op->dspFrameCnt ); + + unsigned byteCnt = ip->dspFrameCnt * sizeof(cmApSample_t); + + if( oBufPtrArray[i] != NULL ) + { + // the input channel is not disabled + if( iBufPtrArray[i]!=NULL ) + memcpy(oBufPtrArray[i],iBufPtrArray[i],byteCnt); + else + // the input channel is disabled but the output is not - so fill the output with zeros + memset(oBufPtrArray[i],0,byteCnt); + } + } + + cmApBufAdvance( iDevIdx, kInApFl ); + cmApBufAdvance( oDevIdx, kOutApFl ); + } + +} + +void cmApBufReport( cmRpt_t* rpt ) +{ + unsigned i,j,k; + for(i=0; i<_cmApBuf.devCnt; ++i) + { + cmRptPrintf(rpt,"%i ",i); + + for(j=0; jchCnt; ++k) + { + cmApCh* cp = ip->chArray + i; + ii += cp->ii; + oi += cp->oi; + fn += cp->fn; + } + + cmRptPrintf(rpt,"%s - i:%7i o:%7i f:%7i n:%7i err %s:%7i ", + j==0?"IN":"OUT", + ii,oi,fn,ip->n, (j==0?"over":"under"), ip->faultCnt); + + } + + cmRptPrintf(rpt,"\n"); + } +} + +/// [cmApBufExample] + +void cmApBufTest( cmRpt_t* rpt ) +{ + unsigned devIdx = 0; + unsigned devCnt = 1 ; + unsigned dspFrameCnt = 10; + unsigned cycleCnt = 3; + unsigned framesPerCycle = 25; + unsigned inChCnt = 2; + unsigned outChCnt = inChCnt; + unsigned sigN = cycleCnt*framesPerCycle*inChCnt; + double srate = 44100.0; + unsigned meterMs = 50; + + unsigned bufChCnt= inChCnt; + cmApSample_t* inBufArray[ bufChCnt ]; + cmApSample_t* outBufArray[ bufChCnt ]; + cmApSample_t iSig[ sigN ]; + cmApSample_t oSig[ sigN ]; + cmApSample_t* os = oSig; + cmApAudioPacket_t pkt; + int i,j; + + // create a simulated signal + for(i=0; iss->cbDataPtr; + + if( p != NULL && p->isLoadedFl ) + return cmDspSysRcvMsg(p->dsH,ctx, msgDataPtr, msgByteCnt, ctx->srcNetNodeId ); + + return cmOkRC; +} + +// This function is called by cmAudioSysReceiveMsg() to transfer messages from the +// cmAudioSys or cmDspSys to the client. +cmRC_t _cmAudioSysToClientCallback(void* userCbPtr, unsigned msgByteCnt, const void* msgDataPtr ) +{ + cmAd_t* p = (cmAd_t*)userCbPtr; + return p->cbFunc(p->cbDataPtr, msgByteCnt, msgDataPtr ); +} + +// This function is called by cmUdpNetReceive(), which is called in +// cmAudioSys.c:_cmAsThreadCallback() just prior to executing the DSP process. +void _cmAdUdpNetCallback( void* cbArg, cmUdpNetH_t h, const char* data, unsigned dataByteCnt, unsigned srcNetNodeId ) +{ + cmAd_t* p = (cmAd_t*)cbArg; + + // send the incoming message to the audio system for later delivery to the DSP system + cmAudioSysDeliverMsg(p->asH, data, dataByteCnt, srcNetNodeId ); +} + + +cmAdRC_t _cmAdParseMemberErr( cmAd_t* p, cmJsRC_t jsRC, const cmChar_t* errLabel, const cmChar_t* objectLabel ) +{ + if( jsRC == kNodeNotFoundJsRC && errLabel != NULL ) + return cmErrMsg(&p->err,kJsonFailAdRC,"The required field '%s'was not found in the audio DSP resource tree in the object '%s' in the file '%s'.",errLabel,cmStringNullGuard(objectLabel), cmStringNullGuard(p->sysJsFn)); + + return cmErrMsg(&p->err,kJsonFailAdRC,"JSON parsing failed on the Audio DSP resource file '%s' in the resource object '%s'.",cmStringNullGuard(p->sysJsFn),cmStringNullGuard(objectLabel)); + +} + + +cmAdRC_t _cmAdParseSysJsonTree( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + cmJsonNode_t* asCfgArrNodePtr = NULL; + cmJsonNode_t* aggDevArrNodePtr = NULL; + cmJsonNode_t* nrtDevArrNodePtr = NULL; + cmJsonNode_t* audDspNodePtr = NULL; + const cmChar_t* errLabelPtr = NULL; + unsigned i; + cmJsRC_t jsRC = kOkJsRC; + + // locate the aud_dsp container object + if( cmJsonNodeMember( cmJsonRoot(p->sysJsH), "aud_dsp", &audDspNodePtr ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAdRC,"The audio DSP system resource file '%s' does not contain an 'aud_dsp' object.",cmStringNullGuard(p->sysJsFn)); + goto errLabel; + } + + // locate the read the aud_dsp sub-elements + if(( jsRC = cmJsonMemberValues( audDspNodePtr, &errLabelPtr, + "midiPortBufByteCnt", kIntTId, &p->midiPortBufByteCnt, + "meterMs", kIntTId, &p->meterMs, + "msgsPerClientPoll", kIntTId, &p->msgsPerClientPoll, + "audioSysCfgArray", kArrayTId, &asCfgArrNodePtr, + "aggDevArray", kArrayTId | kOptArgJsFl, &aggDevArrNodePtr, + "nrtDevArray", kArrayTId | kOptArgJsFl, &nrtDevArrNodePtr, + NULL )) != kOkJsRC ) + { + rc = _cmAdParseMemberErr(p, jsRC, errLabelPtr, "aud_dsp" ); + goto errLabel; + } + + // parse the aggregate device specifications into p->aggDevArray[]. + if( aggDevArrNodePtr != NULL && (p->aggDevCnt = cmJsonChildCount(aggDevArrNodePtr)) > 0) + { + // alloc the aggregate spec. array + p->aggDevArray = cmMemResizeZ( cmAdAggDev_t, p->aggDevArray, p->aggDevCnt ); + + // for each agg. device spec. recd + for(i=0; iaggDevCnt; ++i) + { + const cmJsonNode_t* np = cmJsonArrayElementC(aggDevArrNodePtr,i); + const cmJsonNode_t* devIdxArrNodePtr = NULL; + unsigned j; + + // read aggDevArray record values + if(( jsRC = cmJsonMemberValues( np, &errLabelPtr, + "label", kStringTId, &p->aggDevArray[i].label, + "physDevIdxArray", kArrayTId, &devIdxArrNodePtr, + NULL )) != kOkJsRC ) + { + rc = _cmAdParseMemberErr(p, jsRC, errLabelPtr, cmStringNullGuard(p->aggDevArray[i].label) ); + goto errLabel; + } + + unsigned physDevCnt = cmJsonChildCount(devIdxArrNodePtr); + + // alloc the dev idx array for to hold the phys. dev indexes for this agg device + p->aggDevArray[i].physDevIdxArray = cmMemResizeZ( unsigned, p->aggDevArray[i].physDevIdxArray, physDevCnt); + p->aggDevArray[i].physDevCnt = physDevCnt; + + // store the phys. dev. idx's + for(j=0; jaggDevArray[i].physDevIdxArray + j ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAdRC,"Unable to retrieve a physical device index for the aggregate device '%s'.", cmStringNullGuard(p->aggDevArray[i].label)); + goto errLabel; + } + } + + } + + // parse the non-real-time device specifications into p->nrtDevArray[]. + if( nrtDevArrNodePtr != NULL && (p->nrtDevCnt = cmJsonChildCount(nrtDevArrNodePtr)) > 0) + { + // alloc the non-real-time spec. array + p->nrtDevArray = cmMemResizeZ( cmAdNrtDev_t, p->nrtDevArray, p->nrtDevCnt ); + + // for each nrt. device spec. recd + for(i=0; inrtDevCnt; ++i) + { + const cmJsonNode_t* np = cmJsonArrayElementC(nrtDevArrNodePtr,i); + + // read nrtDevArray record values + if(( jsRC = cmJsonMemberValues( np, &errLabelPtr, + "label", kStringTId, &p->nrtDevArray[i].label, + "srate", kRealTId, &p->nrtDevArray[i].srate, + "iChCnt", kIntTId, &p->nrtDevArray[i].iChCnt, + "oChCnt", kIntTId, &p->nrtDevArray[i].oChCnt, + "cbPeriodMs", kIntTId, &p->nrtDevArray[i].cbPeriodMs, + NULL )) != kOkJsRC ) + { + rc = _cmAdParseMemberErr(p, jsRC, errLabelPtr, cmStringNullGuard(p->nrtDevArray[i].label) ); + goto errLabel; + } + + } + + } + + if((p->asCfgCnt = cmJsonChildCount(asCfgArrNodePtr)) == 0 ) + goto errLabel; + + p->asCfgArray = cmMemResizeZ( cmAdAsCfg_t, p->asCfgArray, p->asCfgCnt); + + // for each cmAsAudioSysCfg record in audioSysCfgArray[] + for(i=0; iasCfgCnt; ++i) + { + unsigned j; + const cmJsonNode_t* asCfgNodePtr = cmJsonArrayElementC(asCfgArrNodePtr,i); + const cmJsonNode_t* ssArrayNodePtr = NULL; + const char* cfgLabel = NULL; + + // read cmAsAudioSysCfg record values + if(( jsRC = cmJsonMemberValues( asCfgNodePtr, &errLabelPtr, + "label", kStringTId, &cfgLabel, + "ssArray", kArrayTId, &ssArrayNodePtr, + NULL )) != kOkJsRC ) + { + rc = _cmAdParseMemberErr(p, jsRC, errLabelPtr, cmStringNullGuard(p->asCfgArray[i].label) ); + goto errLabel; + } + + p->asCfgArray[i].label = cfgLabel; + p->asCfgArray[i].cfg.ssCnt = cmJsonChildCount( ssArrayNodePtr ); + p->asCfgArray[i].cfg.ssArray = cmMemResizeZ( cmAudioSysSubSys_t, p->asCfgArray[i].cfg.ssArray, p->asCfgArray[i].cfg.ssCnt ); + p->asCfgArray[i].cfg.clientCbFunc = _cmAudioSysToClientCallback; + p->asCfgArray[i].cfg.clientCbData = p; + + + // for each audio system sub-subsystem + for(j=0; jasCfgArray[i].cfg.ssCnt; ++j) + { + cmAudioSysArgs_t* asap = &p->asCfgArray[i].cfg.ssArray[j].args; + const cmJsonNode_t* argsNodePtr = cmJsonArrayElementC(ssArrayNodePtr,j); + + if((jsRC = cmJsonMemberValues( argsNodePtr, &errLabelPtr, + "inDevIdx", kIntTId, &asap->inDevIdx, + "outDevIdx", kIntTId, &asap->outDevIdx, + "syncToInputFl", kTrueTId, &asap->syncInputFl, + "msgQueueByteCnt", kIntTId, &asap->msgQueueByteCnt, + "devFramesPerCycle", kIntTId, &asap->devFramesPerCycle, + "dspFramesPerCycle", kIntTId, &asap->dspFramesPerCycle, + "audioBufCnt", kIntTId, &asap->audioBufCnt, + "srate", kRealTId, &asap->srate, + NULL )) != kOkJsRC ) + { + rc = _cmAdParseMemberErr(p, jsRC, errLabelPtr, cmStringNullGuard(p->asCfgArray[i].label)); + goto errLabel; + } + + } + + + } + errLabel: + + return rc; +} + +cmAdRC_t _cmAdSetup( cmAd_t* p ) +{ + unsigned i; + cmAdRC_t rc = kOkAdRC; + + for(i=0; iasCfgCnt; ++i) + { + unsigned j; + + p->asCfgArray[i].cfg.meterMs = p->meterMs; + + // BUG BUG BUG - each sub-system should have it's own network + // manager, and socket port. + + p->asCfgArray[i].cfg.netH = p->netH; + + for(j=0; jasCfgArray[i].cfg.ssCnt; ++j) + { + p->asCfgArray[i].cfg.ssArray[j].cbDataPtr = NULL; + p->asCfgArray[i].cfg.ssArray[j].cbFunc = _cmAudDspCallback; + p->asCfgArray[i].cfg.ssArray[j].args.rpt = p->err.rpt; + } + } + + return rc; +} + +cmAdRC_t _cmAdCreateAggDevices( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + unsigned i; + + if( cmApAggAllocate(p->err.rpt) != kOkAgRC ) + return cmErrMsg(&p->err,kAggDevSysFailAdRC,"The aggregate device system allocation failed."); + + for(i=0; iaggDevCnt; ++i) + { + cmAdAggDev_t* adp = p->aggDevArray + i; + if( cmApAggCreateDevice(adp->label,adp->physDevCnt,adp->physDevIdxArray,kInAggFl | kOutAggFl) != kOkAgRC ) + rc = cmErrMsg(&p->err,kAggDevCreateFailAdRC,"The aggregate device '%s' creation failed.",cmStringNullGuard(adp->label)); + } + + return rc; +} + +cmAdRC_t _cmAdCreateNrtDevices( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + unsigned i; + + if( cmApNrtAllocate(p->err.rpt) != kOkApRC ) + return cmErrMsg(&p->err,kNrtDevSysFailAdRC,"The non-real-time device system allocation failed."); + + for(i=0; inrtDevCnt; ++i) + { + cmAdNrtDev_t* adp = p->nrtDevArray + i; + if( cmApNrtCreateDevice(adp->label,adp->srate,adp->iChCnt,adp->oChCnt,adp->cbPeriodMs) != kOkApRC ) + rc = cmErrMsg(&p->err,kNrtDevSysFailAdRC,"The non-real-time device '%s' creation failed.",cmStringNullGuard(adp->label)); + } + + return rc; +} + +cmAdRC_t _cmAdSendAudioSysCfgLabels( cmAd_t* p) +{ + cmAdRC_t rc = kOkAdRC; + unsigned i; + + for(i=0; iasCfgCnt; ++i) + { + cmDspValue_t v; + cmDsvSetStrcz(&v, p->asCfgArray[i].label); + + if( cmMsgSend( &p->err,cmInvalidIdx,kUiSelAsId,kAudioSysCfgDuiId,0,i,p->asCfgCnt,&v,p->cbFunc,p->cbDataPtr) != kOkMsgRC ) + { + rc = cmErrMsg(&p->err,kSendMsgFailAdRC,"Error sending audio system cfg. label message to host."); + break; + } + + } + return rc; +} + +cmAdRC_t _cmAdSendDeviceLabels( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + unsigned i,j; + + unsigned n = cmApDeviceCount(); + + for(i=0; i<2; ++i) + { + bool inputFl = i==0; + + for(j=0; jerr,cmInvalidIdx,kUiSelAsId,kDeviceDuiId,inputFl,j,n,&v,p->cbFunc,p->cbDataPtr) != kOkMsgRC ) + { + rc = cmErrMsg(&p->err,kSendMsgFailAdRC,"Error sending device label message to host."); + break; + } + + } + + } + return rc; +} + +cmAdRC_t _cmAdSendProgramLabels( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + unsigned pgmCnt = cmDspSysPgmCount(p->dsH); + unsigned i; + + for(i=0; idsH,i)); + + if( cmMsgSend( &p->err,cmInvalidIdx,kUiSelAsId,kProgramDuiId,0,i,pgmCnt,&v,p->cbFunc,p->cbDataPtr) != kOkMsgRC ) + { + rc = cmErrMsg(&p->err,kSendMsgFailAdRC,"Error sending program label message to host."); + break; + } + + } + + return rc; +} + +cmAdRC_t _cmAudDspFree( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + + if( cmAudioSysFree(&p->asH) != kOkAsRC ) + { + rc = cmErrMsg(&p->err,kAudioSysFailAdRC,"The audio system release failed."); + goto errLabel; + } + + if( cmDspSysFinalize(&p->dsH) != kOkDspRC ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"DSP system finalization failed."); + goto errLabel; + } + + if( cmUdpNetFree(&p->netH) != kOkUnRC ) + { + rc = cmErrMsg(&p->err,kNetSysFailAdRC,"UDP Network finalization failed."); + goto errLabel; + } + + if( cmMpIsInitialized() ) + if( cmMpFinalize() != kOkMpRC ) + { + rc = cmErrMsg(&p->err,kMidiSysFailAdRC,"MIDI system finalization failed."); + goto errLabel; + } + + if( cmApFinalize() != kOkApRC ) + { + rc = cmErrMsg(&p->err,kAudioPortFailAdRC,"Audio port finalization failed."); + goto errLabel; + } + + if( cmApBufFinalize() != kOkApRC ) + { + rc = cmErrMsg(&p->err,kAudioPortFailAdRC,"Audio port buffer finalization failed."); + goto errLabel; + } + + if( cmApNrtFree() != kOkAgRC ) + { + rc = cmErrMsg(&p->err,kNrtDevSysFailAdRC,"The non-real-time device system realease failed."); + goto errLabel; + } + + if( cmApAggFree() != kOkAgRC ) + { + rc = cmErrMsg(&p->err,kAggDevSysFailAdRC,"The aggregate device system release failed."); + goto errLabel; + } + + cmMemPtrFree(&p->nrtDevArray); + + unsigned i; + for(i=0; iaggDevCnt; ++i) + cmMemPtrFree(&p->aggDevArray[i].physDevIdxArray); + + cmMemPtrFree(&p->aggDevArray); + + for(i=0; iasCfgCnt; ++i) + cmMemPtrFree(&p->asCfgArray[i].cfg.ssArray); + + cmMemPtrFree(&p->asCfgArray); + + cmMemPtrFree(&p->dsSsArray); + + if( cmJsonFinalize(&p->sysJsH) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAdRC,"System JSON tree finalization failed."); + goto errLabel; + } + + if( p->sysJsFn != NULL ) + cmFsFreeFn(p->sysJsFn); + + cmMemFree(p); + + errLabel: + + return rc; +} + +cmAdRC_t cmAudDspAlloc( cmCtx_t* ctx, cmAdH_t* hp, cmMsgSendFuncPtr_t cbFunc, void* cbDataPtr ) +{ + + cmAdRC_t rc = kOkAdRC; + cmAdRC_t rc0 = kOkAdRC; + + if((rc = cmAudDspFree(hp)) != kOkAdRC ) + return rc; + + cmAd_t* p = cmMemAllocZ(cmAd_t,1); + + cmErrSetup(&p->err,&ctx->rpt,"Audio DSP Engine"); + + // form the audio dsp resource file name + if((p->sysJsFn = cmFsMakeFn( cmFsPrefsDir(),cmAudDspSys_FILENAME,NULL,NULL)) == NULL ) + { + rc = cmErrMsg(&p->err,kFileSysFailAdRC,"Unable to form the audio dsp system resource file name."); + goto errLabel; + } + + // open the audio dsp resource file + if(cmJsonInitializeFromFile(&p->sysJsH,p->sysJsFn,ctx) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAdRC,"Unable to open the audio dsp resource file: '%s'.",cmStringNullGuard(p->sysJsFn)); + goto errLabel; + } + + // parse the JSON tree + if((rc = _cmAdParseSysJsonTree(p)) != kOkAdRC ) + goto errLabel; + + // create the aggregate device + if( _cmAdCreateAggDevices(p) != kOkAdRC ) + goto errLabel; + + // create the non-real-time devices + if( _cmAdCreateNrtDevices(p) != kOkAdRC ) + goto errLabel; + + // initialize the audio device system + if( cmApInitialize(&ctx->rpt) != kOkApRC ) + { + rc = cmErrMsg(&p->err,kAudioPortFailAdRC,"Audio port intialization failed."); + goto errLabel; + } + + // initialize the audio buffer + if( cmApBufInitialize( cmApDeviceCount(), p->meterMs ) != kOkApRC ) + { + rc = cmErrMsg(&p->err,kAudioPortFailAdRC,"Audio port buffer initialization failed."); + goto errLabel; + } + + // initialize the MIDI system + if( cmMpInitialize(NULL,NULL,p->midiPortBufByteCnt,"app",&ctx->rpt) != kOkMpRC ) + { + rc = cmErrMsg(&p->err,kMidiSysFailAdRC,"The MIDI system initialization failed."); + goto errLabel; + } + + // initialize the UDP network - but do not go into 'listening' mode. + if( cmUdpNetAllocJson(ctx,&p->netH,p->sysJsH,_cmAdUdpNetCallback,p,kNetOptionalUnFl) != kOkUnRC ) + { + cmErrMsg(&p->err,kNetSysFailAdRC,"The UDP network initialization failed."); + goto errLabel; + } + + if((rc = _cmAdSetup(p)) != kOkAdRC ) + goto errLabel; + + // initialize the DSP system + if( cmDspSysInitialize(ctx,&p->dsH,p->netH) ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"The DSP system initialization failed."); + goto errLabel; + } + + // allocate the audio system + if( cmAudioSysAllocate(&p->asH, &ctx->rpt, NULL ) != kOkAsRC ) + { + rc = cmErrMsg(&p->err,kAudioSysFailAdRC,"The audio system allocation failed."); + goto errLabel; + } + + p->cbFunc = cbFunc; + p->cbDataPtr = cbDataPtr; + p->curAsCfgIdx = cmInvalidIdx; + p->ctx = *ctx; + + // notify the client of the available audio system configurations + if((rc = _cmAdSendAudioSysCfgLabels(p)) != kOkAdRC ) + goto errLabel; + + // notify the client of the available devices + if((rc = _cmAdSendDeviceLabels(p)) != kOkAdRC) + goto errLabel; + + // notify the client of the available programs + if((rc = _cmAdSendProgramLabels(p)) != kOkAdRC ) + goto errLabel; + + + hp->h = p; + + errLabel: + + + if( rc != kOkAdRC ) + rc0 = _cmAudDspFree(p); + + return rc == kOkAdRC ? rc0 : rc; +} + +cmAdRC_t cmAudDspFree( cmAdH_t* hp ) +{ + cmAdRC_t rc = kOkAdRC; + + if( hp == NULL || cmAudDspIsValid(*hp)==false ) + return kOkAdRC; + + cmAd_t* p = _cmAdHandleToPtr(*hp); + + if((rc = _cmAudDspFree(p)) != kOkAdRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmAudDspIsValid( cmAdH_t h ) +{ return h.h != NULL; } + + +cmAdRC_t _cmAudDspUnloadPgm( cmAd_t* p, unsigned asSubSysIdx ) +{ + cmAdRC_t rc = kOkAdRC; + const cmAdAsCfg_t* cfgPtr = NULL; + + // Must disable audio thread callbacks to _cmAudDspCallback() + // while changing DSP system data structures. + if( cmAudioSysIsEnabled(p->asH) ) + if(cmAudioSysEnable(p->asH,false) != kOkAsRC ) + { + rc = cmErrMsg(&p->err,kAudioSysFailAdRC,"The audio system could not be disabled."); + goto errLabel; + } + + // validate the sub-system index + if( asSubSysIdx >= p->dsSsCnt ) + { + rc = cmErrMsg(&p->err,kInvalidSubSysIdxAdRC,"The invalid sub-system index %i was encountered while unloading a program.",asSubSysIdx); + goto errLabel; + } + + // if a valid cfg recd exists + if( p->curAsCfgIdx != cmInvalidIdx ) + { + // pointer to audio system configuration + cfgPtr = p->asCfgArray + p->curAsCfgIdx; + + // count of audio system sub-systems should be the same as the current cfg + assert( p->dsSsCnt == cfgPtr->cfg.ssCnt ); + + // mark the DSP program as unloaded and pre-sync + p->dsSsArray[ asSubSysIdx ].isLoadedFl = false; + p->dsSsArray[ asSubSysIdx ].isSyncFl = false; + p->syncFl = false; + } + + // unload the current program + if( cmDspSysUnload(p->dsSsArray[asSubSysIdx].dsH) != kOkDspRC ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"Program unload failed."); + goto errLabel; + } + + + errLabel: + return rc; +} + + +cmAdRC_t _cmAudDspUnloadAudioSys( cmAd_t* p ) +{ + unsigned i; + cmAdRC_t rc = kOkAdRC; + + p->syncFl = false; + + for(i=1; idsSsCnt; ++i) + { + if((rc = _cmAudDspUnloadPgm(p,i)) != kOkAdRC ) + goto errLabel; + + if( cmDspSysFinalize(&p->dsSsArray[i].dsH) != kOkDspRC ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"DSP system finalization failed."); + goto errLabel; + } + } + + p->curAsCfgIdx = cmInvalidIdx; + + errLabel: + return rc; +} + +cmAdRC_t _cmAdSendIntMsgToHost( cmAd_t* p, unsigned asSubIdx, unsigned selId, unsigned flags, unsigned intValue ) +{ + cmAdRC_t rc = kOkAdRC; + cmDspValue_t v; + cmDsvSetUInt(&v,intValue); + + if( cmMsgSend( &p->err,asSubIdx,kUiSelAsId,selId,flags,cmInvalidId,cmInvalidId,&v,p->cbFunc,p->cbDataPtr) != kOkMsgRC ) + rc = cmErrMsg(&p->err,kSendMsgFailAdRC,"Error sending message to host."); + + return rc; +} + +// verify that a valid audio cfg has been selected +cmAdRC_t _cmAdIsAudioSysLoaded( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + + if( cmAudioSysHandleIsValid(p->asH) == false ) + { + rc = cmErrMsg(&p->err,kAudioSysFailAdRC,"The audio system is not allocated."); + goto errLabel; + } + + if( p->curAsCfgIdx == cmInvalidIdx ) + return kInvalidCfgIdxAdRC; + + // verify that an audio system is loaded + if( cmAudioSysIsInitialized(p->asH) && p->curAsCfgIdx == cmInvalidIdx ) + { + rc = cmErrMsg(&p->err,kInvalidCfgIdxAdRC,"The audio system has not been configured."); + goto errLabel; + } + + // count of audio system sub-systems should be the same as the current cfg + assert( p->dsSsCnt == p->asCfgArray[p->curAsCfgIdx].cfg.ssCnt ); + + errLabel: + return rc; +} + +// verify that a valid audio cfg and DSP program has been selected +cmAdRC_t _cmAdIsPgmLoaded( cmAd_t* p, bool verboseFl ) +{ + cmAdRC_t rc; + + // a program cannot be loaded if the audio system has not been configured + if((rc = _cmAdIsAudioSysLoaded(p)) != kOkAdRC ) + return rc; + + unsigned i; + + // for each sub-system + for(i=0; idsSsCnt; ++i) + { + // verify that the DSP system has been created + if( cmDspSysIsValid(p->dsSsArray[i].dsH) == false ) + return cmErrMsg(&p->err,kDspSysFailAdRC,"The DSP sub-system at index %i is not initialized.",i); + + // verify that the DSP program was loaded + if( p->dsSsArray[ i ].isLoadedFl == false ) + { + if( verboseFl ) + cmErrMsg(&p->err,kNoPgmLoadedAdRC,"There is no program loaded."); + + return kNoPgmLoadedAdRC; + } + } + + return rc; +} + +bool _cmAudDspIsPgmSynced( cmAd_t* p ) +{ + unsigned syncCnt = 0; + unsigned i; + + if( p->syncFl ) + return true; + + // if the pgm is not loaded then it cannot be sync'd + if(_cmAdIsPgmLoaded(p,false) != kOkAdRC ) + return false; + + // check each sub-system + for(i=0; idsSsCnt; ++i) + { + unsigned syncState = cmDspSysSyncState(p->dsSsArray[i].dsH); + + // if the subsys is already synced + if( p->dsSsArray[i].isSyncFl ) + ++syncCnt; + else + { + switch( syncState ) + { + // the sub-sys is pre or pending sync mode + case kSyncPreDspId: + case kSyncPendingDspId: + break; + + // sync mode completed - w/ success or fail + case kSyncSuccessDspId: + case kSyncFailDspId: + { + // notify the client of the the sync state + bool syncFl = syncState == kSyncSuccessDspId; + _cmAdSendIntMsgToHost(p,cmInvalidIdx,kSyncDuiId,syncFl,cmInvalidIdx); + p->dsSsArray[i].isSyncFl = syncFl; + } + break; + + } + } + } + + p->syncFl = syncCnt == p->dsSsCnt; + + return p->syncFl; +} + + +cmAdRC_t _cmAudDspLoadPgm( cmAd_t* p, unsigned asSubSysIdx, unsigned pgmIdx ) +{ + cmAdRC_t rc = kOkAdRC; + unsigned i; + + p->syncFl = false; + + // the audio system must be configured before a program is loaded + if((rc = _cmAdIsAudioSysLoaded(p)) != kOkAdRC ) + return cmErrMsg(&p->err,rc,"Program load failed."); + + // validate the sub-system index arg. + if( asSubSysIdx!=cmInvalidIdx && asSubSysIdx >= p->dsSsCnt ) + { + rc = cmErrMsg(&p->err,kInvalidSubSysIdxAdRC,"The sub-system index %i is invalid.",asSubSysIdx); + goto errLabel; + } + + // for each sub-system + for(i=0; idsSsCnt; ++i) + if( asSubSysIdx==cmInvalidIdx || i==asSubSysIdx ) + { + // unload any currently loaded program on this sub-system + // (unloading a program automatically disables the audio system) + if((rc = _cmAudDspUnloadPgm(p, i )) != kOkAdRC ) + goto errLabel; + + // load the program + if( cmDspSysLoad(p->dsSsArray[ i ].dsH, cmAudioSysContext(p->asH,i), pgmIdx ) != kOkDspRC ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"The program load failed on audio sub-system %i.",i); + goto errLabel; + } + + // update the state of the DSP sub-system + p->dsSsArray[i].curPgmIdx = pgmIdx; + p->dsSsArray[i].isLoadedFl = true; + p->dsSsArray[i].isSyncFl = false; + p->syncFl = false; + + // notify the host of the new program + _cmAdSendIntMsgToHost(p,i,kSetPgmDuiId,0,pgmIdx); + } + + errLabel: + return rc; +} + +cmAdRC_t _cmAdReinitAudioSys( cmAd_t* p ) +{ + cmAdRC_t rc = kOkAdRC; + + p->syncFl = false; + + // pointer to the new audio system configuration + cmAdAsCfg_t* cfgPtr = p->asCfgArray + p->curAsCfgIdx; + + // initialize the audio system + if( cmAudioSysInitialize(p->asH, &cfgPtr->cfg ) != kOkAsRC ) + { + rc = cmErrMsg(&p->err,kAudioSysFailAdRC,"The audio system initialization failed."); + goto errLabel; + } + + // reload any currently loaded programs + unsigned i; + for(i=0; idsSsCnt; ++i) + { + unsigned pgmIdx; + if((pgmIdx = p->dsSsArray[i].curPgmIdx) != cmInvalidIdx ) + if((rc = _cmAudDspLoadPgm(p,i,pgmIdx)) != kOkAdRC ) + break; + } + + errLabel: + return rc; +} + +cmAdRC_t _cmAudDspLoadAudioSys( cmAd_t* p, unsigned asCfgIdx ) +{ + cmAdRC_t rc = kOkAdRC; + cmAdAsCfg_t* cfgPtr; + unsigned i; + + // validate asCfgIdx + if( asCfgIdx >= p->asCfgCnt ) + { + cmErrMsg(&p->err,kInvalidCfgIdxAdRC,"The audio system index %i is invalid.",asCfgIdx); + goto errLabel; + } + + // clear the current audio system setup - this will automatically disable the audio system + if((rc = _cmAudDspUnloadAudioSys(p)) != kOkAdRC ) + goto errLabel; + + // pointer to the new audio system configuration + cfgPtr = p->asCfgArray + asCfgIdx; + + // get the count of audio system sub-systems + p->dsSsCnt = cfgPtr->cfg.ssCnt; + + // store the index of the current audio system configuration + p->curAsCfgIdx = asCfgIdx; + + if( p->dsSsCnt > 0 ) + { + p->dsSsArray = cmMemResizeZ(cmAdDsSubSys_t, p->dsSsArray, p->dsSsCnt ); + + for(i=0; idsSsCnt; ++i) + { + cmDspSysH_t dsH; + + // the first sub-system will always use the existing DSP system handle ... + if( i==0 ) + dsH = p->dsH; + else + { + // ... and allocate additional DSP systems when more than one sub-sys is + // defined in the audio system configuration + if( cmDspSysInitialize(&p->ctx,&dsH,p->netH) != kOkDspRC ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"Unable to initialize an additional DSP system."); + goto errLabel; + } + } + + p->dsSsArray[i].dsH = dsH; + p->dsSsArray[i].curPgmIdx = cmInvalidIdx; + p->dsSsArray[i].isLoadedFl= false; + + // this cbDataPtr is picked up in _cmAudDspCallback(). + // It is used to connect the audio system to a DSP system handle. + cfgPtr->cfg.ssArray[i].cbDataPtr = p->dsSsArray + i; + + } + } + + // notify the client of the change of audio configuration + _cmAdSendIntMsgToHost(p,cmInvalidIdx,kSetAudioCfgDuiId,0,asCfgIdx); + + // notify the client of the count of audio sub-systems + _cmAdSendIntMsgToHost(p,cmInvalidIdx,kSubSysCntDuiId,0,p->dsSsCnt); + + // for each sub-system + for(i=0; idsSsCnt; ++i) + { + // notify the client of the currently selected devices + _cmAdSendIntMsgToHost(p,i,kSetAudioDevDuiId,true, cfgPtr->cfg.ssArray[i].args.inDevIdx); + _cmAdSendIntMsgToHost(p,i,kSetAudioDevDuiId,false,cfgPtr->cfg.ssArray[i].args.outDevIdx); + + // notify the client of the sample rate + _cmAdSendIntMsgToHost(p,i,kSetSampleRateDuiId,0,(unsigned)cfgPtr->cfg.ssArray[i].args.srate); + + _cmAdSendIntMsgToHost(p,i,kSetPgmDuiId,0,cmInvalidIdx); + } + + // the audio system configuration changed so we need to initialize the audio system + if((rc = _cmAdReinitAudioSys(p)) != kOkAdRC ) + goto errLabel; + + errLabel: + if( rc != kOkAdRC ) + _cmAudDspUnloadAudioSys(p); + + return rc; +} + + + +cmAdRC_t _cmAudDspEnableAudio( cmAd_t* p, bool enableFl ) +{ + cmAdRC_t rc = kOkAdRC; + + if( enableFl ) + { + // verify an audio system cfg and DSP program has been selected + if(( rc = _cmAdIsPgmLoaded(p,true)) != kOkAdRC ) + return cmErrMsg(&p->err,rc,"Audio enable failed."); + + // if the audio system is already enabled/disabled then do nothing + if( cmAudioSysIsEnabled(p->asH) == enableFl ) + return kOkAdRC; + + // for each sub-system + unsigned i; + for(i=0; idsSsCnt; ++i) + { + if( cmDspSysReset(p->dsSsArray[i].dsH) != kOkDspRC ) + { + rc = cmErrMsg(&p->err,kDspSysFailAdRC,"The DSP system reset failed."); + goto errLabel; + } + } + + } + + // start/stop the audio sub-system + if( cmAudioSysEnable(p->asH,enableFl) != kOkAsRC ) + { + rc = cmErrMsg(&p->err,kAudioSysFailAdRC,"The audio system %s failed.", enableFl ? "enable" : "disable"); + goto errLabel; + } + + // notify the host of the new enable state + _cmAdSendIntMsgToHost(p,cmInvalidIdx,kEnableDuiId,enableFl,cmInvalidIdx); + + errLabel: + return rc; + +} + +cmAdRC_t _cmAudDspSetDevice(cmAd_t* p,unsigned asSubIdx, bool inputFl, unsigned devIdx) +{ + cmAdRC_t rc; + + // a device cannot be set if the audio system is not already configured + if((rc = _cmAdIsAudioSysLoaded(p)) != kOkAdRC ) + return cmErrMsg(&p->err,rc,"Set audio device failed."); + + cmAdAsCfg_t* cfgPtr = p->asCfgArray + p->curAsCfgIdx; + + // validate the sub-system index + if( asSubIdx >= p->dsSsCnt ) + { + rc = cmErrMsg(&p->err,kInvalidSubSysIdxAdRC,"The sub-system index %i is invalid.",asSubIdx); + goto errLabel; + } + + // assign the new device index to the indicated audio system configuration recd + if( inputFl ) + { + if( cfgPtr->cfg.ssArray[ asSubIdx ].args.inDevIdx != devIdx ) + cfgPtr->cfg.ssArray[ asSubIdx ].args.inDevIdx = devIdx; + + } + else + { + if( cfgPtr->cfg.ssArray[ asSubIdx ].args.outDevIdx != devIdx ) + cfgPtr->cfg.ssArray[ asSubIdx ].args.outDevIdx = devIdx; + } + + // notify the host that the new device has been set + _cmAdSendIntMsgToHost(p,asSubIdx,kSetAudioDevDuiId,inputFl, devIdx); + + // reinitialize the audio system + rc = _cmAdReinitAudioSys(p); + + errLabel: + + return rc; +} + +cmAdRC_t _cmAudDspSetSampleRate(cmAd_t* p, unsigned asSubIdx, double srate ) +{ + cmAdRC_t rc; + + if((rc = _cmAdIsAudioSysLoaded(p)) != kOkAdRC ) + return cmErrMsg(&p->err,rc,"Set audio device failed."); + + cmAdAsCfg_t* cfgPtr = p->asCfgArray + p->curAsCfgIdx; + + // validate the sub-system index + if( asSubIdx != cmInvalidIdx && asSubIdx >= p->dsSsCnt ) + { + rc = cmErrMsg(&p->err,kInvalidSubSysIdxAdRC,"The sub-system index %i is invalid.",asSubIdx); + goto errLabel; + } + + unsigned i; + for(i=0; idsSsCnt; ++i) + { + // assign the new device index to the indicated audio system configuration recd + if( asSubIdx==cmInvalidIdx || asSubIdx == i ) + { + if( cfgPtr->cfg.ssArray[ i ].args.srate != srate ) + cfgPtr->cfg.ssArray[ i ].args.srate = srate; + } + } + + // notify the client of the new sample rate + _cmAdSendIntMsgToHost(p,asSubIdx,kSetSampleRateDuiId,0,(unsigned)srate); + + // reinitialize the audio system + rc = _cmAdReinitAudioSys(p); + + errLabel: + + return rc; +} + + +cmAdRC_t _cmAudDspClientMsgPoll( cmAd_t* p ) +{ + unsigned i = 0; + + // if the program is not synced then don't bother polling the audio system + if( _cmAudDspIsPgmSynced(p) == false ) + return kOkAdRC; + + for(i=0; imsgsPerClientPoll; ++i) + { + if( cmAudioSysIsMsgWaiting(p->asH) == 0 ) + break; + + if(cmAudioSysReceiveMsg(p->asH,NULL,0) != kOkAsRC ) + return cmErrMsg(&p->err,kAudioSysFailAdRC,"The delivery of an audio system msg for the client failed."); + + } + + return kOkAdRC; +} + +cmAdRC_t cmAudDspReceiveClientMsg( cmAdH_t h, unsigned msgByteCnt, const void* msg ) +{ + cmAdRC_t rc = kOkAdRC; + cmAd_t* p = _cmAdHandleToPtr(h); + cmDspUiHdr_t* m = (cmDspUiHdr_t*)msg; + /* + if( m->uiId != kUiSelAsId ) + { + rc = cmErrMsg(&p->err,kUnknownMsgTypeAdRC,"The message type %i is unknown."); + goto errLabel; + } + */ + + switch( m->selId ) + { + case kDevReportDuiId: + cmApReport(p->err.rpt); + break; + + case kSetAudioCfgDuiId: + rc = _cmAudDspLoadAudioSys(p,cmDsvUInt(&m->value)); + break; + + case kSetPgmDuiId: + rc = _cmAudDspLoadPgm(p,m->asSubIdx,cmDsvUInt(&m->value)); + break; + + case kSetAudioDevDuiId: + rc = _cmAudDspSetDevice(p,m->asSubIdx,m->flags,cmDsvUInt(&m->value)); + break; + + case kSetSampleRateDuiId: + rc = _cmAudDspSetSampleRate(p,m->asSubIdx,cmDsvDouble(&m->value)); + break; + + case kEnableDuiId: + rc = _cmAudDspEnableAudio(p,m->flags); + break; + + case kSetNotifyEnableDuiId: + cmAudioSysStatusNotifyEnable(p->asH, cmInvalidIdx, m->flags ); + break; + + case kClientMsgPollDuiId: + rc = _cmAudDspClientMsgPoll(p); + break; + + default: + if( cmAudioSysDeliverMsg(p->asH,msg,msgByteCnt,cmInvalidId) != kOkAsRC ) + rc = cmErrMsg(&p->err,kSendMsgFailAdRC,"Message delivery to the audio system failed."); + break; + } + + + + // errLabel: + return rc; +} diff --git a/cmAudDsp.h b/cmAudDsp.h new file mode 100644 index 0000000..14bc8f4 --- /dev/null +++ b/cmAudDsp.h @@ -0,0 +1,55 @@ +#ifndef cmAudDsp_h +#define cmAudDsp_h + +#ifdef __cplusplus +extern "C" { +#endif + + // This API supports a serialized interface to an internal instance of + // cmAudioSys and cmDspSys. + + enum + { + kOkAdRC = cmOkRC, + kAudioPortFailAdRC, + kAudioSysFailAdRC, + kMidiSysFailAdRC, + kDspSysFailAdRC, + kFileSysFailAdRC, + kJsonFailAdRC, + kSendMsgFailAdRC, + kInvalidCfgIdxAdRC, + kNoPgmLoadedAdRC, + kInvalidSubSysIdxAdRC, + kUnknownMsgTypeAdRC, + kAggDevSysFailAdRC, + kAggDevCreateFailAdRC, + kNrtDevSysFailAdRC, + kNetSysFailAdRC + }; + + + typedef cmRC_t cmAdRC_t; + typedef cmHandle_t cmAdH_t; + + extern cmAdH_t cmAdNullHandle; + + // Create a audio dsp engine and send device and program information to the + // host application. + // cbPtr provides a function used by cmAudDsp to send messages to the client. + cmAdRC_t cmAudDspAlloc( cmCtx_t* ctx, cmAdH_t* hp, cmMsgSendFuncPtr_t cbPtr, void* cbDataPtr ); + cmAdRC_t cmAudDspFree( cmAdH_t* hp ); + + bool cmAudDspIsValid( cmAdH_t h ); + + // This function provides the primary interface for communication from the + // client program to the aud_dsp system. + cmAdRC_t cmAudDspReceiveClientMsg( cmAdH_t h, unsigned msgBytecnt, const void* msg ); + + +#ifdef __cplusplus + } +#endif + + +#endif diff --git a/cmAudDspIF.c b/cmAudDspIF.c new file mode 100644 index 0000000..297fe37 --- /dev/null +++ b/cmAudDspIF.c @@ -0,0 +1,291 @@ +#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 "cmFileSys.h" +#include "cmJson.h" +#include "cmThread.h" +#include "dsp/cmDspValue.h" +#include "cmMsgProtocol.h" +#include "cmAudDspIF.h" + + +cmAiH_t cmAiNullHandle = cmSTATIC_NULL_HANDLE; + +typedef struct +{ + cmErr_t err; + cmAdIfParm_t parms; + cmJsonH_t jsH; +} cmAi_t; + +cmAi_t* _cmAiHandleToPtr( cmAiH_t h ) +{ + cmAi_t* p = (cmAi_t*)h.h; + assert(p != NULL); + return p; +} + + +// Dispatch a message to the client application. +// This function is called from within cmTsQueueDequeueMsg() which is called +// by cmAdIfDispatchMsgToHost(). +cmRC_t _cmAiDispatchMsgToClient(void* cbDataPtr, unsigned msgByteCnt, const void* msgDataPtr ) +{ + cmAi_t* p = (cmAi_t*)cbDataPtr; + cmDspUiHdr_t* m = (cmDspUiHdr_t*)msgDataPtr; + cmRC_t rc = cmOkRC; + + switch( m->uiId ) + { + case kStatusSelAsId: + { + // handle a status mesage + const char* base = (const char*)msgDataPtr; + const cmAudioSysStatus_t* st = (const cmAudioSysStatus_t*)(base + (2 * sizeof(unsigned))); + const double* iMeterArray = (const double*)(st + 1); + const double* oMeterArray = iMeterArray + st->iMeterCnt; + rc = p->parms.dispatchRecd.statusFunc(p->parms.dispatchRecd.cbDataPtr, st, iMeterArray, oMeterArray ); + } + break; + + case kSsInitSelAsId: + { + // handle an ssInit message + const cmAudioSysSsInitMsg_t* sip = (const cmAudioSysSsInitMsg_t*)msgDataPtr; + const char* iDevLabel = (const char*)(sip+1); + const char* oDevLabel = iDevLabel + strlen(iDevLabel) + 1; + rc = p->parms.dispatchRecd.ssInitFunc(p->parms.dispatchRecd.cbDataPtr, sip, iDevLabel, oDevLabel ); + } + break; + + case kUiSelAsId: + { + bool jsFl = false; + cmDsvRC_t rc = kOkDsvRC; + + // if the value associated with this msg is a mtx then set + // its mtx data area pointer to just after the msg header. + if( cmDsvIsJson(&m->value) ) + { + rc = cmDsvDeserializeJson(&m->value,p->jsH); + jsFl = true; + } + else + rc = cmDsvDeserializeInPlace(&m->value,msgByteCnt-sizeof(cmDspUiHdr_t)); + + if( rc != kOkDsvRC ) + cmErrMsg(&p->err,kDeserialFailAiRC,"Deserialize failed."); + else + rc = p->parms.dispatchRecd.uiFunc(p->parms.dispatchRecd.cbDataPtr,m); + + if( jsFl ) + cmJsonClearTree(p->jsH); + } + break; + + default: + cmErrMsg(&p->err,kUnknownMsgTypeAiRC,"The message type %i is unknown.",m->uiId); + break; + } + + return rc; +} + +cmAiRC_t _cmAdIfReadCfgFile( cmAi_t* p, cmCtx_t* ctx ) +{ + cmAiRC_t rc = kOkAiRC; + const cmChar_t* sysJsFn = NULL; + cmJsonH_t jsH = cmJsonNullHandle; + cmJsonNode_t* audDspNodePtr = NULL; + //const cmChar_t* errLabelPtr = NULL; + + // form the audio dsp resource file name + if((sysJsFn = cmFsMakeFn( cmFsPrefsDir(),cmAudDspSys_FILENAME,NULL,NULL)) == NULL ) + { + rc = cmErrMsg(&p->err,kFileSysFailAiRC,"Unable to form the audio dsp system resource file name."); + goto errLabel; + } + + // open the audio dsp resource file + if(cmJsonInitializeFromFile(&jsH,sysJsFn,ctx) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAiRC,"Unable to open the audio dsp resource file: '%s'.",cmStringNullGuard(sysJsFn)); + goto errLabel; + } + + // locate the aud_dsp container object + if( cmJsonNodeMember( cmJsonRoot(jsH), "aud_dsp", &audDspNodePtr ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAiRC,"The audio DSP system resource file '%s' does not contain an 'aud_dsp' object.",cmStringNullGuard(sysJsFn)); + goto errLabel; + } + + /* + // locate the read the aud_dsp sub-elements + if( cmJsonMemberValues( audDspNodePtr, &errLabelPtr, + "msgQueueByteCnt", kIntTId, &p->msgQueueByteCnt, + NULL ) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAiRC,"Syntax error while parsing the top level fields in the audio DSP system resource file:%s.",cmStringNullGuard(sysJsFn)); + goto errLabel; + } + */ + + errLabel: + if( cmJsonFinalize(&jsH) != kOkJsRC ) + rc = cmErrMsg(&p->err,kJsonFailAiRC,"JSON finalization failed."); + + if( sysJsFn != NULL ) + cmFsFreeFn(sysJsFn); + + return rc; + +} + + +cmAiRC_t _cmAdIfSendIntMsg(cmAiH_t h, unsigned selId, unsigned asSubIdx, unsigned flags, unsigned iv, double dv ) +{ + cmAi_t* p = _cmAiHandleToPtr( h ); + cmDspValue_t v; + + if(iv == cmInvalidIdx ) + cmDsvSetDouble(&v,dv); + else + cmDsvSetUInt(&v,iv); + + if( cmMsgSend(&p->err,asSubIdx,kUiSelAsId,selId,flags,cmInvalidId,cmInvalidId,&v,p->parms.audDspFunc,p->parms.audDspFuncDataPtr) != kOkMsgRC ) + return cmErrMsg(&p->err,kSendFailAiRC,"The integer message sel id:%i value:%i transmission failed.",selId,iv); + + return kOkAiRC; + +} + +cmAiRC_t _cmAdIfFree( cmAi_t* p ) +{ + cmAiRC_t rc = kOkAiRC; + + if( cmJsonFinalize(&p->jsH) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAiRC,"JSON finalization failed."); + goto errLabel; + } + + cmMemFree(p); + + errLabel: + return rc; +} + + +cmAiRC_t cmAdIfAllocate( cmCtx_t* ctx, cmAiH_t* hp, const cmAdIfParm_t* parms ) +{ + cmAiRC_t rc; + if((rc = cmAdIfFree(hp)) != kOkAiRC ) + return rc; + + cmAi_t* p = cmMemAllocZ(cmAi_t,1); + + cmErrSetup(&p->err,&ctx->rpt,"Audio DSP Interface"); + + p->parms = *parms; + + // read the system configuration file + if((rc = _cmAdIfReadCfgFile(p, ctx )) != kOkAiRC ) + goto errLabel; + + + // initialize a JSON tree for use in deserializing JSON messages + if((rc = cmJsonInitialize( &p->jsH, ctx )) != kOkJsRC ) + { + rc = cmErrMsg(&p->err,kJsonFailAiRC,"JSON initialization failed."); + goto errLabel; + } + + + hp->h = p; + + errLabel: + if( rc != kOkAiRC ) + _cmAdIfFree(p); + + return rc; +} + +cmAiRC_t cmAdIfFree( cmAiH_t* hp ) +{ + cmAiRC_t rc = kOkAiRC; + + if( hp==NULL || cmAdIfIsValid(*hp)==false ) + return kOkAiRC; + + cmAi_t* p = _cmAiHandleToPtr(*hp); + + if((rc = _cmAdIfFree(p)) != kOkAiRC ) + return rc; + + hp->h = NULL; + return rc; +} + +bool cmAdIfIsValid( cmAiH_t h ) +{ return h.h != NULL; } + +cmAiRC_t cmAdIfRecvAudDspMsg( cmAiH_t h, unsigned msgByteCnt, const void* msg ) +{ + cmAi_t* p = _cmAiHandleToPtr(h); + cmAiRC_t rc = kOkAiRC; + + _cmAiDispatchMsgToClient(p,msgByteCnt,msg); + return rc; +} + +cmAiRC_t cmAdIfDeviceReport( cmAiH_t h ) +{ return _cmAdIfSendIntMsg(h,kDevReportDuiId,cmInvalidIdx,0,cmInvalidIdx,0.0); } + +cmAiRC_t cmAdIfSetAudioSysCfg( cmAiH_t h, unsigned asCfgIdx ) +{ return _cmAdIfSendIntMsg(h,kSetAudioCfgDuiId,cmInvalidIdx,0,asCfgIdx,0.0); } + +cmAiRC_t cmAdIfSetAudioDevice( cmAiH_t h, unsigned asSubIdx, bool inputFl, unsigned devIdx ) +{ return _cmAdIfSendIntMsg(h,kSetAudioDevDuiId,asSubIdx,inputFl,devIdx,0.0); } + +cmAiRC_t cmAdIfSetSampleRate( cmAiH_t h, unsigned asSubIdx, double srate ) +{ return _cmAdIfSendIntMsg(h,kSetSampleRateDuiId,asSubIdx,0,cmInvalidIdx,srate); } + +cmAiRC_t cmAdIfLoadProgram( cmAiH_t h, unsigned asSubIdx, unsigned pgmIdx ) +{ return _cmAdIfSendIntMsg(h,kSetPgmDuiId,asSubIdx,0,pgmIdx,0.0); } + +cmAiRC_t cmAdIfEnableAudio( cmAiH_t h, bool enableFl ) +{ return _cmAdIfSendIntMsg(h,kEnableDuiId,cmInvalidIdx,enableFl,cmInvalidIdx,0.0); } + +cmAiRC_t cmAdIfEnableStatusNotify( cmAiH_t h, bool enableFl ) +{ return _cmAdIfSendIntMsg(h,kSetNotifyEnableDuiId,cmInvalidIdx,enableFl,cmInvalidIdx,0.0); } + +cmAiRC_t cmAdIfSendMsgToAudioDSP( + cmAiH_t h, + unsigned asSubIdx, + unsigned msgTypeId, + unsigned selId, + unsigned flags, + unsigned instId, + unsigned instVarId, + const cmDspValue_t* valPtr ) +{ + cmAiRC_t rc = kOkAiRC; + cmAi_t* p = _cmAiHandleToPtr(h); + + if( cmMsgSend( &p->err, asSubIdx, msgTypeId,selId,flags,instId,instVarId,valPtr, p->parms.audDspFunc, p->parms.audDspFuncDataPtr ) != kOkMsgRC ) + rc = cmErrMsg(&p->err, kSendFailAiRC, "A UI message intened for the the audio DSP system was not successfully delivered."); + + return rc; + +} + + +cmAiRC_t cmAdIfDispatchMsgToHost( cmAiH_t h ) +{ return _cmAdIfSendIntMsg(h,kClientMsgPollDuiId,cmInvalidIdx,0,cmInvalidIdx,0.0); } + diff --git a/cmAudDspIF.h b/cmAudDspIF.h new file mode 100644 index 0000000..eae3ba1 --- /dev/null +++ b/cmAudDspIF.h @@ -0,0 +1,167 @@ +#ifndef cmAudDspIF_h +#define cmAudDspIF_h + +#ifdef __cplusplus +extern "C" { +#endif + + // This API has two basic responsibilities: + // + // 1) Provides a function based interface to the audio DSP system for the + // client application. + // The client calls these API functions to send commands to the audio DSP + // system. Internally the cmAdIfxxx functions converts the commands to + // raw message packets and passes them to a transmission service + // via cmAdIfParm_t audioDspFunc(). + // + // 2) Acts as the receiver of raw message streams from whatever external + // service (e.g. cmAudDspLocal, cmAudDspUdp) is receiving raw message packets + // from audio DSP system. + // + // This process is driven by periodic calls from the client to + // cmAdIfDispatchMsgToHost(). + // cmAdIfDispatchMsgToHost() then generates an internal + // 'kClientMsgPollDuiId' msg which is passed toward the + // cmAudDsp system. + // When the msg encounters a sub-system with queued msgs waiting + // for the client a callback chain ensues which eventually + // calls cmAdIfRecvAudDspMsg() which in turn calls the appropriate + // client provided cmAdIfDispatch_t function (ssInitFunc,statusFunc or uiFunc). + // Note that this entire chain of calls occurs in the client thread + // and in the context of the cmAdIfDispatchMsgToHost() procedure. + + + + enum + { + kOkAiRC = cmOkRC, + kAudDspFailAiRC, + kLHeapFailAiRC, + kUnknownMsgTypeAiRC, + kMsgCorruptAiRC, + kSendFailAiRC, + kQueueFailAiRC, + kNoMsgAiRC, + kJsonFailAiRC, + kDeserialFailAiRC, + kFileSysFailAiRC + }; + + typedef cmRC_t cmAiRC_t; + + typedef cmHandle_t cmAiH_t; + + // These functions are provided by the client to receive messages + // from the audio DSP sytem. These functions are only called from the client thread + // from within cmAdIfDispatchMsgToHost(). + typedef struct + { + void* cbDataPtr; // data to send as the first arg. to app. callbacks + + cmRC_t (*ssInitFunc)( void* cbDataPtr, const cmAudioSysSsInitMsg_t* r, const char* iDevLabel, const char* oDevLabel ); + cmRC_t (*statusFunc)( void* cbDataPtr, const cmAudioSysStatus_t* r, const double* iMeterArray, const double* oMeterArray ); + cmRC_t (*uiFunc)( void* cbDataPtr, const cmDspUiHdr_t* r ); + } cmAdIfDispatch_t; + + typedef struct + { + cmAdIfDispatch_t dispatchRecd; // client application callback pointers + cmMsgSendFuncPtr_t audDspFunc; // the cmAdIfXXX functions use the callback to send msgs to the audio DSP system. + void* audDspFuncDataPtr; // data to send with the audio DSP callback function + } cmAdIfParm_t; + + + extern cmAiH_t cmAiNullHandle; + + cmAiRC_t cmAdIfAllocate( cmCtx_t* ctx, cmAiH_t* hp, const cmAdIfParm_t* parms ); + cmAiRC_t cmAdIfFree( cmAiH_t* hp ); + + bool cmAdIfIsValid( cmAiH_t h ); + + // Receive a msg from the audio DSP system. This is the main point of entry + // for all calls from the audio DSP system to the client. + // This function is provided as a callback to the owner of this cmAudDspIF + // (e.g. cmAudDspLocal, cmAudDspUdpClient) it should never need to be called + // by the client. + cmAiRC_t cmAdIfRecvAudDspMsg( cmAiH_t h, unsigned msgByteCnt, const void* msgDataPtr); + + + //------------------------------------------------------------------------- + // + // The functions below are used to send commands to the audio DSP system + // from the client application. + // + + // Print a hardware report. + cmAiRC_t cmAdIfDeviceReport( cmAiH_t h ); + + // Select a audio system configuration. This must be done prior to + // sending any other commands. + cmAiRC_t cmAdIfSetAudioSysCfg( cmAiH_t h, unsigned asCfgIdx ); + + // Select an audio input or output device for a given audio sub-system. + // An audio configuration must have been selected via cmAdIfSetAudioSysCfg() + // prior to calling this function. + cmAiRC_t cmAdIfSetAudioDevice( cmAiH_t h, unsigned asSubIdx, bool inputFl, unsigned devIdx ); + + // Set the sample rate for a given audio sub-system or the entire audio system. + // Set asSubIdx to cmInvalidIdx to assign the sample rate to all sub-systems + // of the current audio system configuration. + // An audio configuration must have been selected via cmAdIfSetAudioSysCfg() + // prior to calling this function. + cmAiRC_t cmAdIfSetSampleRate( cmAiH_t h, unsigned asSubIdx, double srate ); + + // Select a DSP program for a given audio sub-system or the entire audio system. + // Set asSubIdx to cmInvalidIdx to load the program on all sub-systems + // of the current audio system configuration. + // An audio configuration must have been selected via cmAdIfSetAudioSysCfg() + // prior to calling this function. + cmAiRC_t cmAdIfLoadProgram( cmAiH_t h, unsigned asSubIdx, unsigned pgmIdx ); + + // Start the audio streaming. + // An audio configuration must have been selected via cmAdIfSetAudioSysCfg() + // and a DSP program must have been selected via cmAdIfLoadProgram() + // prior to calling this function. + cmAiRC_t cmAdIfEnableAudio( cmAiH_t h, bool enableFl ); + + // Enable/disable periodic audio system status notifications. + cmAiRC_t cmAdIfEnableStatusNotify( cmAiH_t h, bool enableFl ); + + // Send a kUiSelAsId style message to the audio DSP system. + cmAiRC_t cmAdIfSendMsgToAudioDSP( + cmAiH_t h, + unsigned asSubIdx, + unsigned msgTypeId, + unsigned selId, + unsigned flags, + unsigned instId, + unsigned instVarId, + const cmDspValue_t* valPtr ); + + // The client application must periodically call this function to + // receive pending messages from the audio DSP system. The + // messages are delivered via callbacks provided by cmAdIfDispatch_t. + // This function should only be called from the client thread. + cmAiRC_t cmAdIfDispatchMsgToHost( cmAiH_t h ); + + /* + Local call chain: + cmAdIfDispatchMsgToHost() -> p->parms.audDspFunc = cmAudDspLocal::_cmAdlAudDspSendFunc() + -> cmAudioDsp::cmAudDspReceiveClientMsg() + -> cmAudioDsp::_cmAudDspClientMsgPoll() + -> cmAudioSys::cmAudioSysReceiveMsg() + -> cmThread::cmTs1p1cDequeueMsg() + -> cmAudioSysCfg_t::clientCbFunc = cmAudDsp::_cmAudioSysToClientCallback() + -> cmAudDsp::cmAd_t::cbFunc = cmAudDspLocal::_cmAudDspLocalCallback() + -> cmAudDspIF::cmAdIfRecvAudDspMsg() + -> cmAudDspIF::_cmAiDispatchMsgToClient() + -> cmAudDspIF::cmAdIfDispatch_t.uiFunc = kcApp::_s_handleUiMsg() + + */ + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmAudDspLocal.c b/cmAudDspLocal.c new file mode 100644 index 0000000..7c25c0b --- /dev/null +++ b/cmAudDspLocal.c @@ -0,0 +1,147 @@ +#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 "cmJson.h" +#include "dsp/cmDspValue.h" +#include "cmMsgProtocol.h" +#include "cmAudDsp.h" +#include "cmAudDspIF.h" +#include "cmAudDspLocal.h" + + +cmAdlH_t cmAdlNullHandle = cmSTATIC_NULL_HANDLE; + +typedef struct +{ + cmErr_t err; + cmAiH_t aiH; + cmAdH_t adH; +} cmAdl_t; + +cmAdl_t* _cmAdlHandleToPtr( cmAdlH_t h ) +{ + cmAdl_t* p = (cmAdl_t*)h.h; + assert( p != NULL ); + return p; +} + +// Forward messages coming from the audio DSP system to the audio DSP IF +// for later dispatch to the client application. +cmMsgRC_t _cmAudDspLocalCallback(void* cbDataPtr, unsigned msgByteCnt, const void* msg ) +{ + cmMsgRC_t rc = kOkMsgRC; + cmAdl_t* p = (cmAdl_t*)cbDataPtr; + + if( cmAdIfRecvAudDspMsg(p->aiH, msgByteCnt, msg ) != kOkAiRC ) + { + cmErrMsg(&p->err,kAudDspIfFailAdlRC,"Message transmission to the audio DSP interface failed."); + rc = kSendFailMsgRC; + } + + return rc; +} + +// Forward messages from the audio DSP interface to the audio DSP system. +cmMsgRC_t _cmAdlAudDspSendFunc( void* cbDataPtr, unsigned msgByteCnt, const void* msg ) +{ + cmMsgRC_t rc = kOkMsgRC; + cmAdl_t* p = (cmAdl_t*)cbDataPtr; + + if( cmAudDspReceiveClientMsg( p->adH, msgByteCnt, msg ) != kOkAdRC ) + { + cmErrMsg(&p->err,kAudDspFailAdlRC,"Message transmission the audio DSP system failed."); + rc = kSendFailMsgRC; + } + + return rc; + +} + +cmAdlRC_t _cmAudDspLocalFree( cmAdl_t* p ) +{ + cmAdlRC_t rc = kOkAdlRC; + + if( cmAdIfFree(&p->aiH) != kOkAiRC ) + { + rc = cmErrMsg(&p->err,kAudDspIfFailAdlRC,"The audio DSP interface release failed."); + goto errLabel; + } + + if( cmAudDspFree(&p->adH) != kOkAdRC ) + { + rc = cmErrMsg(&p->err,kAudDspFailAdlRC,"The audio DSP release failed."); + goto errLabel; + } + + cmMemFree(p); + errLabel: + return rc; +} + + + +cmAdlRC_t cmAudDspLocalAllocate( cmCtx_t* ctx, cmAdlH_t* hp, const cmAdIfDispatch_t* recd ) +{ + cmAdlRC_t rc; + if((rc = cmAudDspLocalFree(hp)) != kOkAdlRC ) + return rc; + + cmAdl_t* p = cmMemAllocZ(cmAdl_t,1); + cmErrSetup(&p->err,&ctx->rpt,"Audio DSP Local"); + + cmAdIfParm_t parms; + parms.dispatchRecd = *recd; + parms.audDspFunc = _cmAdlAudDspSendFunc; + parms.audDspFuncDataPtr = p; + + if( cmAdIfAllocate(ctx, &p->aiH, &parms ) != kOkAiRC ) + { + rc = cmErrMsg(&p->err,kAudDspIfFailAdlRC,"The audio DSP interface system allocation failed."); + goto errLabel; + } + + if( cmAudDspAlloc(ctx, &p->adH, _cmAudDspLocalCallback, p ) != kOkAdRC ) + { + rc = cmErrMsg(&p->err,kAudDspFailAdlRC,"The audio DSP system allocation failed."); + goto errLabel; + } + + hp->h = p; + + errLabel: + if( rc != kOkAdlRC ) + _cmAudDspLocalFree(p); + + return rc; +} + +cmAdlRC_t cmAudDspLocalFree( cmAdlH_t* hp ) +{ + cmAdlRC_t rc = kOkAdlRC; + + if( hp == NULL || cmAudDspLocalIsValid(*hp) == false ) + return kOkAdlRC; + + cmAdl_t* p = _cmAdlHandleToPtr(*hp); + + if((rc = _cmAudDspLocalFree(p)) != kOkAdlRC ) + return rc; + + hp->h = NULL; + return rc; +} + +bool cmAudDspLocalIsValid( cmAdlH_t h ) +{ return h.h != NULL; } + +cmAiH_t cmAudDspLocalIF_Handle( cmAdlH_t h ) +{ + cmAdl_t* p = _cmAdlHandleToPtr(h); + return p->aiH; +} + diff --git a/cmAudDspLocal.h b/cmAudDspLocal.h new file mode 100644 index 0000000..09c55e3 --- /dev/null +++ b/cmAudDspLocal.h @@ -0,0 +1,38 @@ +#ifndef cmAudDspLocal_h +#define cmAudDspLocal_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkAdlRC = cmOkRC, + kAudDspIfFailAdlRC, + kAudDspFailAdlRC, + kFileSysFailAdlRC, + kJsonFailAdlRC + }; + + typedef cmRC_t cmAdlRC_t; + typedef cmHandle_t cmAdlH_t; + + extern cmAdlH_t cmAdlNullHandle; + + cmAdlRC_t cmAudDspLocalAllocate( + cmCtx_t* ctx, + cmAdlH_t* hp, + const cmAdIfDispatch_t* recd ); + + cmAdlRC_t cmAudDspLocalFree( cmAdlH_t* hp ); + + bool cmAudDspLocalIsValid( cmAdlH_t h ); + + cmAiH_t cmAudDspLocalIF_Handle( cmAdlH_t h ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmAudLabelFile.c b/cmAudLabelFile.c new file mode 100644 index 0000000..1f839ab --- /dev/null +++ b/cmAudLabelFile.c @@ -0,0 +1,316 @@ +#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 "cmLinkedHeap.h" +#include "cmFile.h" +#include "cmAudLabelFile.h" + +cmAlfH_t cmAlfNullHandle = cmSTATIC_NULL_HANDLE; + +typedef struct cmAlfRecd_str +{ + cmAlfLabel_t r; + struct cmAlfRecd_str* link; +} cmAlfRecd_t; + +typedef struct +{ + cmErr_t err; + cmLHeapH_t lH; + cmFileH_t fH; + cmAlfRecd_t* list; + int cnt; +} cmAlf_t; + +cmAlf_t* _cmAlfHandleToPtr( cmAlfH_t h ) +{ + cmAlf_t* p = (cmAlf_t*)h.h; + assert( p != NULL ); + return p; +} + +cmAlfRC_t _cmAlfFree( cmAlf_t* p ) +{ + cmAlfRC_t rc = kOkAlfRC; + + cmLHeapDestroy(&p->lH); + + cmMemPtrFree(&p); + + return rc; +} + + +cmAlfRC_t cmAudLabelFileAlloc( cmCtx_t* ctx, cmAlfH_t* hp ) +{ + cmAlfRC_t rc; + if((rc = cmAudLabelFileFree(hp)) != kOkAlfRC ) + return rc; + + cmAlf_t* p = cmMemAllocZ(cmAlf_t,1); + cmErrSetup(&p->err,&ctx->rpt,"Audio Label File"); + + if(!cmLHeapIsValid( p->lH = cmLHeapCreate(1024,ctx))) + { + cmErrMsg(&p->err,kLHeapFailAlfRC,"Linked heap create failed."); + goto errLabel; + } + + hp->h = p; + + errLabel: + return rc; +} + +cmAlfRC_t cmAudLabelFileAllocOpen( cmCtx_t* ctx, cmAlfH_t* hp, const cmChar_t* fn ) +{ + cmAlfRC_t rc; + if((rc = cmAudLabelFileAlloc(ctx,hp)) != kOkAlfRC) + return rc; + + return cmAudLabelFileOpen(*hp,fn); +} + +cmAlfRC_t cmAudLabelFileFree( cmAlfH_t* hp ) +{ + cmAlfRC_t rc = kOkAlfRC; + + if( hp == NULL || cmAudLabelFileIsValid(*hp)==false ) + return kOkAlfRC; + + cmAlf_t* p = _cmAlfHandleToPtr(*hp); + + if((rc = _cmAlfFree(p)) != kOkAlfRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmAudLabelFileIsValid( cmAlfH_t h ) +{ return h.h != NULL; } + +void _cmAlfInsert( cmAlf_t* p, cmReal_t begSecs, cmReal_t endSecs, const cmChar_t* label ) +{ + cmAlfRecd_t* np = p->list; + cmAlfRecd_t* pp = NULL; + cmAlfRecd_t* ip = cmLhAllocZ(p->lH,cmAlfRecd_t,1); + + ip->r.begSecs = begSecs; + ip->r.endSecs = endSecs; + ip->r.label = label==NULL || strlen(label)==0 ? NULL : cmLhAllocStr(p->lH,label); + + // set np to the next recd and + // set pp to the prev recd + while(np != NULL ) + { + if( np->r.begSecs > begSecs ) + break; + + pp = np; + np = np->link; + } + + ip->link = np; + + // if the new recd is first on the list + if( pp == NULL ) + p->list = ip; + else + pp->link = ip; + + // incr the recd count + ++p->cnt; +} + +// remove the record just after pp +void _cmAlfRemove( cmAlf_t* p, cmAlfRecd_t* pp ) +{ + // if the list is already empty + if( p->list == NULL ) + return; + + // if the first recd should be removed + if( pp == NULL ) + { + p->list = p->list->link; + } + else + { + // if pp points to the last recd + if( pp->link == NULL ) + return; + + // remove pp->link from the list + pp->link = pp->link->link; + } + + assert( p->cnt != 0 ); + --p->cnt; + +} + +cmAlfRC_t cmAudLabelFileOpen( cmAlfH_t h, const cmChar_t* fn ) +{ + cmAlfRC_t rc; + cmAlf_t* p = _cmAlfHandleToPtr(h); + cmChar_t* lineBuf = NULL; + unsigned lineBufByteCnt = 0; + unsigned line = 1; + cmFileH_t fH = cmFileNullHandle; + + // open the label file + if( cmFileOpen(&fH,fn,kReadFileFl,p->err.rpt) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kFileFailAlfRC,"The audio label file '%s' could not be openend.",cmStringNullGuard(fn)); + goto errLabel; + } + + // read each line + while( cmFileGetLineAuto(fH,&lineBuf,&lineBufByteCnt) == kOkFileRC ) + { + cmReal_t begSecs; + cmReal_t endSecs; + cmChar_t* label = NULL; + cmChar_t* begPtr = lineBuf; + cmChar_t* endPtr = NULL; + + // parse the start time in seconds + errno = 0; + begSecs = strtod(begPtr,&endPtr); + if( errno != 0 ) + return cmErrMsg(&p->err,kSyntaxErrAlfRC,"Begin time conversion error on line %i in '%s'.",line,cmFileName(fH)); + + // parse the end time in seconds + begPtr = endPtr; + endSecs = strtod(begPtr,&endPtr); + if( errno != 0 ) + return cmErrMsg(&p->err,kSyntaxErrAlfRC,"End time conversion error on line %i in '%s'.",line,cmFileName(fH)); + + label = endPtr; + + // eat any leading white space off the label + while( *label ) + { + if( isspace(*label) ) + ++label; + else + break; + } + + // trim trailing space and '\n' from the label. + int i = strlen(label)-1; + for(; i>=0; --i) + { + if( isspace(label[i]) ) + label[i]=0; + else + break; + } + + // if the label does not exist + if( strlen(label)==0 ) + label = NULL; + + // insert a new recd + _cmAlfInsert(p,begSecs,endSecs,label); + + ++line; + } + + cmMemPtrFree(&lineBuf); + + if( cmFileClose(&fH) != kOkFileRC ) + rc = cmErrMsg(&p->err,kFileFailAlfRC,"The audio label file close failed."); + + errLabel: + return rc; +} + +cmAlfRC_t cmAudLabelFileInsert( cmAlfH_t h, cmReal_t begSecs, cmReal_t endSecs, const cmChar_t* label ) +{ + cmAlfRC_t rc = kOkAlfRC; + cmAlf_t* p = _cmAlfHandleToPtr(h); + _cmAlfInsert(p,begSecs,endSecs,label); + return rc; +} + +unsigned cmAudLabelFileCount( cmAlfH_t h ) +{ + cmAlf_t* p = _cmAlfHandleToPtr(h); + return p->cnt; +} +const cmAlfLabel_t* cmAudLabelFileLabel( cmAlfH_t h, unsigned idx ) +{ + cmAlf_t* p = _cmAlfHandleToPtr(h); + cmAlfRecd_t* lp = p->list; + unsigned i; + + for(i=0; lp!=NULL && ilink; + + return &lp->r; +} + +cmAlfRC_t cmAudLabelFileWrite( cmAlfH_t h, const cmChar_t* fn ) +{ + cmAlfRC_t rc = kOkAlfRC; + cmAlf_t* p = _cmAlfHandleToPtr(h); + cmAlfRecd_t* lp = p->list; + cmFileH_t fH = cmFileNullHandle; + + if( cmFileOpen(&fH,fn,kWriteFileFl,p->err.rpt) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kFileFailAlfRC,"The audio label output file '%s' could not be created.",cmStringNullGuard(fn)); + goto errLabel; + } + + for(; lp!=NULL; lp=lp->link) + { + if( cmFilePrintf(fH,"%f %f %s",lp->r.begSecs,lp->r.endSecs,lp->r.label == NULL ? "" : lp->r.label) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kFileFailAlfRC,"The audio label output file write failed."); + goto errLabel; + } + } + + errLabel: + if( cmFileClose(&fH) != kOkFileRC ) + { + rc = cmErrMsg(&p->err,kFileFailAlfRC,"The audio label output file '%s' close failed.",cmStringNullGuard(fn)); + + } + + return rc; +} + + +void cmAudLabelFileTest( cmCtx_t* ctx ) +{ + const cmChar_t* fn = "/home/kevin/temp/labels.txt"; + const cmChar_t* ofn = "/home/kevin/temp/labels_out.txt"; + cmAlfH_t h = cmAlfNullHandle; + + if( cmAudLabelFileAllocOpen(ctx,&h,fn) == kOkAlfRC ) + { + unsigned n = cmAudLabelFileCount(h); + unsigned i; + for(i=0; irpt,"%f %f %s\n",lp->begSecs,lp->endSecs,lp->label); + + } + + cmAudLabelFileWrite(h,ofn); + + cmAudLabelFileFree(&h); + } +} diff --git a/cmAudLabelFile.h b/cmAudLabelFile.h new file mode 100644 index 0000000..3389fdd --- /dev/null +++ b/cmAudLabelFile.h @@ -0,0 +1,50 @@ +#ifndef cmAudLabelFile_h +#define cmAudLabelFile_h + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + kOkAlfRC = cmOkRC, + kLHeapFailAlfRC, + kFileFailAlfRC, + kSyntaxErrAlfRC, + kAlfFileFailPuRC +}; + + typedef cmRC_t cmAlfRC_t; + typedef cmHandle_t cmAlfH_t; + + extern cmAlfH_t cmAlfNullHandle; + + typedef struct + { + cmReal_t begSecs; + cmReal_t endSecs; + const cmChar_t* label; + } cmAlfLabel_t; + + cmAlfRC_t cmAudLabelFileAlloc( cmCtx_t* ctx, cmAlfH_t* hp ); + cmAlfRC_t cmAudLabelFileAllocOpen( cmCtx_t* ctx, cmAlfH_t* hp, const cmChar_t* fn ); + cmAlfRC_t cmAudLabelFileFree( cmAlfH_t* hp ); + + bool cmAudLabelFileIsValid( cmAlfH_t h ); + + cmAlfRC_t cmAudLabelFileOpen( cmAlfH_t h, const cmChar_t* fn ); + + cmAlfRC_t cmAudLabelFileInsert( cmAlfH_t h, cmReal_t begSecs, cmReal_t endSecs, const cmChar_t* label ); + + unsigned cmAudLabelFileCount( cmAlfH_t h ); + const cmAlfLabel_t* cmAudLabelFileLabel( cmAlfH_t h, unsigned idx ); + + cmAlfRC_t cmAudLabelFileWrite( cmAlfH_t h, const cmChar_t* fn ); + + void cmAudLabelFileTest( cmCtx_t* ctx ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmAudioAggDev.c b/cmAudioAggDev.c new file mode 100644 index 0000000..b9c9617 --- /dev/null +++ b/cmAudioAggDev.c @@ -0,0 +1,942 @@ +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmAudioPort.h" +#include "cmAudioAggDev.h" +#include "cmThread.h" // cmThUIntIncr() + +#include "cmApBuf.h" // only needed for cmApBufTest(). + +//#include // usleep + +enum +{ + kBufArrayCnt = 2 +}; + +struct cmApAgg_str; + +typedef struct +{ + unsigned physDevIdx; + struct cmApAgg_str* ap; + + unsigned iChIdx; + unsigned iChCnt; + + unsigned oChIdx; + unsigned oChCnt; +} cmApAggDev_t; + +typedef struct cmApAgg_str +{ + cmChar_t* label; // agg. device label + unsigned aggDevIdx; // agg. device index + unsigned sysDevIdx; // system device index + unsigned devCnt; // count of phys devices + cmApAggDev_t* devArray; // devArray[ devCnt ] - physical device array + unsigned iChCnt; // sum of phys device input channels + unsigned oChCnt; // sum of phys device output channels + double srate; // agg. dev sample rate + unsigned framesPerCycle; // agg. dev frames per cycle + unsigned flags; // kAgInFl | kAgOutFl + cmApCallbackPtr_t cbFunc; // client supplied callback func + void* cbArg; // client supplied callback func arg. + bool startedFl; // true if the agg. device is started + struct cmApAgg_str* link; // _cmAg.list link +} cmApAgg_t; + +typedef struct +{ + cmErr_t err; + cmApAgg_t* list; +} cmApAggMain_t; + +cmApAggMain_t _cmAg; + + +void _cmApAggCb( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) +{ + unsigned i; + cmApAudioPacket_t pkt; + + for(i=0; iap->sysDevIdx; + pkt.begChIdx = dp->iChIdx; + pkt.userCbPtr = dp->ap->cbArg; + dp->ap->cbFunc( &pkt, 1, NULL, 0 ); + } + + for(i=0; iap->sysDevIdx; + pkt.begChIdx = dp->oChIdx; + pkt.userCbPtr = dp->ap->cbArg; + dp->ap->cbFunc( NULL, 0, &pkt, 1 ); + } + +} + + +void _cmApAgDeleteAggDev( cmApAgg_t* ap ) +{ + cmApAgg_t* cp = _cmAg.list; + cmApAgg_t* pp = NULL; + while( cp != NULL ) + { + if( cp == ap ) + { + if( pp == NULL ) + _cmAg.list = cp->link; + else + pp->link = cp->link; + + cmMemFree(ap->label); + cmMemFree(ap->devArray); + cmMemFree(ap); + return; + } + pp = cp; + cp = cp->link; + } +} + +cmAgRC_t cmApAggAllocate( cmRpt_t* rpt ) +{ + cmAgRC_t rc = kOkAgRC; + + cmErrSetup(&_cmAg.err,rpt,"cmAudioAggDev"); + + _cmAg.list = NULL; + + return rc; +} + +cmAgRC_t cmApAggFree() +{ + cmAgRC_t rc = kOkAgRC; + + while( _cmAg.list != NULL ) + _cmApAgDeleteAggDev(_cmAg.list ); + + return rc; +} + +cmAgRC_t cmApAggInitialize( cmRpt_t* rpt, unsigned baseApDevIdx ) +{ + cmApAgg_t* ap = _cmAg.list; + unsigned i; + + assert( baseApDevIdx == cmApDeviceCount() ); + + for(i=0; ap!=NULL; ap=ap->link,++i) + { + ap->sysDevIdx = cmApDeviceCount() + i; + ap->iChCnt = 0; + ap->oChCnt = 0; + + unsigned i; + for(i=0; idevCnt; ++i) + { + ap->devArray[i].iChIdx = ap->iChCnt; + ap->devArray[i].oChIdx = ap->oChCnt; + ap->devArray[i].iChCnt = cmApDeviceChannelCount(ap->devArray[i].physDevIdx,true); + ap->devArray[i].oChCnt = cmApDeviceChannelCount(ap->devArray[i].physDevIdx,false); + ap->iChCnt += ap->devArray[i].iChCnt; + ap->oChCnt += ap->devArray[i].oChCnt; + } + + + } + + return kOkAgRC; +} + +cmAgRC_t cmApAggFinalize() +{ return kOkAgRC; } + +cmAgRC_t cmApAggCreateDevice( + const cmChar_t* label, + unsigned devCnt, + const unsigned physDevIdxArray[], + unsigned flags ) +{ + cmAgRC_t rc = kOkAgRC; + unsigned i; + + if( devCnt < 2 ) + return cmErrMsg(&_cmAg.err,kMustAggTwoAgRC,"Cannot aggregate less than two devices."); + /* + + for(i=0; ilabel = cmMemAllocStr(label==NULL?"Aggregated Device":label); + ap->devArray = cmMemAllocZ(cmApAggDev_t,devCnt); + ap->aggDevIdx = cmApAggDeviceCount(); + ap->sysDevIdx = cmInvalidIdx; + ap->devCnt = devCnt; + ap->iChCnt = 0; + ap->oChCnt = 0; + + for(i=0; idevArray[i].ap = ap; + ap->devArray[i].physDevIdx = physDevIdxArray[i]; + } + + ap->link = _cmAg.list; + _cmAg.list = ap; + + return rc; +} + +cmApAgg_t* _cmApAggDevIdxToPtr( unsigned aggDevIdx ) +{ + cmApAgg_t* ap = _cmAg.list; + unsigned i = 0; + for(; ap!=NULL; ap=ap->link,++i) + if( ap->aggDevIdx == aggDevIdx ) + return ap; + return NULL; +} + +cmAgRC_t _cmApAggGetAgg( unsigned aggDevIdx, cmApAgg_t** retPtrPtr ) +{ + if((*retPtrPtr = _cmApAggDevIdxToPtr(aggDevIdx)) == NULL ) + return cmErrMsg(&_cmAg.err,kInvalidDevIdxAgRC,"The aggregate system device index '%i' is invalid."); + return kOkAgRC; +} + + +bool cmApAggIsDeviceAggregated( unsigned physDevIdx ) +{ + cmApAgg_t* ap = _cmAg.list; + for(; ap!=NULL; ap=ap->link) + { + unsigned i; + for(i=0; idevCnt; ++i) + if( ap->devArray[i].physDevIdx == physDevIdx ) + return true; + } + return false; +} + +cmAgRC_t cmApAggDeviceCount() +{ + unsigned devCnt=0; + cmApAgg_t* ap = _cmAg.list; + for(; ap!=NULL; ap=ap->link) + ++devCnt; + + return devCnt; +} + +const char* cmApAggDeviceLabel( unsigned aggDevIdx ) +{ + cmApAgg_t* ap; + cmAgRC_t rc; + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) + return ap->label; + return NULL; +} + +unsigned cmApAggDeviceChannelCount( unsigned aggDevIdx, bool inputFl ) +{ + cmApAgg_t* ap; + cmAgRC_t rc; + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) + return inputFl ? ap->iChCnt : ap->oChCnt; + return 0; +} + +double cmApAggDeviceSampleRate( unsigned aggDevIdx ) +{ + cmApAgg_t* ap; + cmAgRC_t rc; + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) + return ap->srate; + return 0; +} + +unsigned cmApAggDeviceFramesPerCycle( unsigned aggDevIdx, bool inputFl ) +{ + cmApAgg_t* ap; + cmAgRC_t rc; + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) == kOkAgRC ) + return ap->framesPerCycle; + return 0; +} + +cmAgRC_t cmApAggDeviceSetup( + unsigned aggDevIdx, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* userCbPtr ) +{ + cmApAgg_t* ap; + cmAgRC_t rc; + unsigned i; + + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) != kOkAgRC ) + return rc; + + if((rc = cmApAggDeviceStop(aggDevIdx)) != kOkAgRC ) + return rc; + + for(i=0; idevCnt; ++i) + { + unsigned physDevIdx = ap->devArray[i].physDevIdx; + cmApAggDev_t* devPtr = ap->devArray + i; + + if( cmApDeviceSetup( physDevIdx, srate, framesPerCycle, _cmApAggCb, devPtr ) != kOkApRC ) + rc = cmErrMsg(&_cmAg.err,kPhysDevSetupFailAgRC,"The physical device (index:%i '%s') setup failed for sample rate:%f frames-per-cycle:%i.",physDevIdx,cmStringNullGuard(cmApDeviceLabel(physDevIdx)),srate,framesPerCycle); + } + + if( rc == kOkAgRC ) + { + ap->cbFunc = callbackPtr; + ap->cbArg = userCbPtr; + } + + return rc; +} + +cmAgRC_t cmApAggDeviceStart( unsigned aggDevIdx ) +{ + cmAgRC_t rc = kOkAgRC; + cmApAgg_t* ap; + unsigned i; + + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) != kOkAgRC ) + return rc; + + for(i=0; idevCnt; ++i) + { + unsigned physDevIdx = ap->devArray[i].physDevIdx; + + if( cmApDeviceStart( physDevIdx ) != kOkApRC ) + return cmErrMsg(&_cmAg.err,kPhysDevStartFailAgRC,"The physical device (index:%i '%s') start failed.",physDevIdx,cmStringNullGuard(cmApDeviceLabel(physDevIdx))); + + //usleep(1000); + } + + ap->startedFl = true; + + return rc; +} + +cmAgRC_t cmApAggDeviceStop( unsigned aggDevIdx ) +{ + cmAgRC_t rc = kOkAgRC; + cmApAgg_t* ap; + unsigned i; + + if((rc = _cmApAggGetAgg(aggDevIdx, &ap )) != kOkAgRC ) + return rc; + + for(i=0; idevCnt; ++i) + { + unsigned physDevIdx = ap->devArray[i].physDevIdx; + + if( cmApDeviceStop( physDevIdx ) != kOkApRC ) + return cmErrMsg(&_cmAg.err,kPhysDevStartFailAgRC,"The physical device (index:%i '%s') start failed.",physDevIdx,cmStringNullGuard(cmApDeviceLabel(physDevIdx))); + } + + ap->startedFl = false; + + return rc; +} + +bool cmApAggDeviceIsStarted( unsigned aggDevIdx ) +{ + cmApAgg_t* ap; + + if(_cmApAggGetAgg(aggDevIdx, &ap ) != kOkAgRC ) + return false; + + return ap->startedFl; +} + + + + + + +typedef struct +{ + unsigned bufCnt; // 2=double buffering 3=triple buffering + unsigned chIdx; // first test channel + unsigned chCnt; // count of channels to test + unsigned framesPerCycle; // DSP frames per cycle + unsigned bufFrmCnt; // count of DSP frames used by the audio buffer (bufCnt * framesPerCycle) + unsigned bufSmpCnt; // count of samples used by the audio buffer (chCnt * bufFrmCnt) + unsigned inDevIdx; // input device index + unsigned outDevIdx; // output device index + double srate; // audio sample rate + unsigned meterMs; // audio meter buffer length + + // param's and state for cmApSynthSine() + bool synthFl; + unsigned phase; // sine synth phase + double frqHz; // sine synth frequency in Hz + + // buffer and state for cmApCopyIn/Out() + cmApSample_t* buf; // buf[bufSmpCnt] - circular interleaved audio buffer + unsigned bufInIdx; // next input buffer index + unsigned bufOutIdx; // next output buffer index + unsigned bufFullCnt; // count of full samples + + unsigned cbCnt; // count the callback + unsigned underunCnt; // + unsigned overunCnt; + + double* iMeter; // iMeter[ chCnt ] + + FILE* ifp; + FILE* ofp; +} cmApAggPortTestRecd; + +// The application can request any block of channels from the device. The packets are provided with the starting +// device channel and channel count. This function converts device channels and channel counts to buffer +// channel indexes and counts. +// +// Example: +// input output +// i,n i n +// App: 0,4 0 1 2 3 -> 2 2 +// Pkt 2,8 2 3 4 5 6 7 8 -> 0 2 +// +// The return value is the count of application requested channels located in this packet. +// +// input: *appChIdxPtr and appChCnt describe a block of device channels requested by the application. +// *pktChIdxPtr and pktChCnt describe a block of device channels provided to the application +// +// output:*appChIdxPtr and describe a block of app buffer channels which will send/recv samples. +// *pktChIdxPtr and describe a block of pkt buffer channels which will send/recv samples +// +unsigned _cmApAggDeviceToBuffer( unsigned* appChIdxPtr, unsigned appChCnt, unsigned* pktChIdxPtr, unsigned pktChCnt ) +{ + unsigned abi = *appChIdxPtr; + unsigned aei = abi+appChCnt-1; + + unsigned pbi = *pktChIdxPtr; + unsigned pei = pbi+pktChCnt-1; + + // if the ch's rqstd by the app do not overlap with this packet - return false. + if( aei < pbi || abi > pei ) + return 0; + + // if the ch's rqstd by the app overlap with the beginning of the pkt channel block + if( abi < pbi ) + { + appChCnt -= pbi - abi; + *appChIdxPtr = pbi - abi; + *pktChIdxPtr = 0; + } + else + { + // the rqstd ch's begin inside the pkt channel block + pktChCnt -= abi - pbi; + *pktChIdxPtr = abi - pbi; + *appChIdxPtr = 0; + } + + // if the pkt channels extend beyond the rqstd ch block + if( aei < pei ) + pktChCnt -= pei - aei; + else + appChCnt -= aei - pei; // the rqstd ch's extend beyond or coincide with the pkt block + + // the returned channel count must always be the same for both the rqstd and pkt + return cmMin(appChCnt,pktChCnt); + +} + + +// synthesize a sine signal into an interleaved audio buffer +unsigned _cmApAggSynthSine( cmApAggPortTestRecd* r, float* p, unsigned chIdx, unsigned chCnt, unsigned frmCnt, unsigned phs, double hz ) +{ + long ph = 0; + unsigned i; + unsigned bufIdx = r->chIdx; + unsigned bufChCnt; + + if( (bufChCnt = _cmApAggDeviceToBuffer( &bufIdx, r->chCnt, &chIdx, chCnt )) == 0) + return phs; + + + //if( r->cbCnt < 50 ) + // printf("ch:%i cnt:%i ch:%i cnt:%i bi:%i bcn:%i\n",r->chIdx,r->chCnt,chIdx,chCnt,bufIdx,bufChCnt); + + + for(i=bufIdx; israte )); + } + } + + return ph; +} + +// Copy the audio samples in the interleaved audio buffer sp[srcChCnt*srcFrameCnt] +// to the internal record buffer. +void _cmApAggCopyIn( cmApAggPortTestRecd* r, const cmApSample_t* sp, unsigned srcChIdx, unsigned srcChCnt, unsigned srcFrameCnt ) +{ + unsigned i,j; + + unsigned chCnt = cmMin(r->chCnt,srcChCnt); + + // write the incoming sample to an output file for debugging + if( r->ifp != NULL ) + if( fwrite(sp,sizeof(cmApSample_t),srcChCnt*srcFrameCnt,r->ifp) != srcChCnt*srcFrameCnt ) + printf("file write fail.\n"); + + // zero the input meter array + for(i=0; ichCnt; ++i) + r->iMeter[i] = 0; + + for(i=0; ibuf[ r->bufInIdx + j ] = sp[ (i*srcChCnt) + srcChIdx + j ]; + + // record the max value in the input meter array + if( r->buf[ r->bufInIdx + j ] > r->iMeter[j] ) + r->iMeter[j] = r->buf[ r->bufInIdx + j ]; + } + + // zero channels that are not used in the buffer + for(; jchCnt; ++j) + r->buf[ r->bufInIdx + j ] = 0; + + // advance to the next frame + r->bufInIdx = (r->bufInIdx+r->chCnt) % r->bufFrmCnt; + } + + //r->bufFullCnt = (r->bufFullCnt + srcFrameCnt) % r->bufFrmCnt; + cmThUIntIncr(&r->bufFullCnt,srcFrameCnt); + + if( r->bufFullCnt > r->bufFrmCnt ) + { + //printf("Input buffer overrun.\n"); + ++r->overunCnt; + r->bufFullCnt = 0; + } + +} + +// Copy audio samples out of the internal record buffer into dp[dstChCnt*dstFrameCnt]. +void _cmApAggCopyOut( cmApAggPortTestRecd* r, cmApSample_t* dp, unsigned dstChIdx, unsigned dstChCnt, unsigned dstFrameCnt ) +{ + + // if there are not enough samples available to fill the destination + // buffer then zero the dst buf. + if( r->bufFullCnt < dstFrameCnt ) + { + //printf("Empty Output Buffer %i < %i\n",r->bufFullCnt,dstFrameCnt); + memset( dp, 0, dstFrameCnt*dstChCnt*sizeof(cmApSample_t) ); + ++r->underunCnt; + } + else + { + unsigned i,j; + unsigned chCnt = cmMin(dstChCnt,r->chCnt); + + for(i=0; ibuf[ r->bufOutIdx + j ]; + + // zero unset channels in the dst buffer + for(; jbufOutIdx = (r->bufOutIdx + r->chCnt) % r->bufFrmCnt; + } + + cmThUIntDecr(&r->bufFullCnt,dstFrameCnt); + } + + if( r->ofp != NULL ) + fwrite(dp,sizeof(cmApSample_t),dstChCnt*dstFrameCnt,r->ofp); +} + +// Audio port callback function called from the audio device thread. +void _cmApAggPortCb( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) +{ + unsigned i; + + // for each incoming audio packet + + for(i=0; isynthFl==false && inPktArray[i].devIdx == r->inDevIdx ) + { + // copy the incoming audio into an internal buffer where it can be picked up by _cpApCopyOut(). + _cmApAggCopyIn( r, (cmApSample_t*)inPktArray[i].audioBytesPtr, inPktArray[i].begChIdx, inPktArray[i].chCnt, inPktArray[i].audioFramesCnt ); + } + ++r->cbCnt; + + //printf("i %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); + } + + unsigned hold_phase = 0; + + // for each outgoing audio packet + for(i=0; ioutDevIdx ) + { + // zero the output buffer + memset(outPktArray[i].audioBytesPtr,0,outPktArray[i].chCnt * outPktArray[i].audioFramesCnt * sizeof(cmApSample_t) ); + + // if the synth is enabled + if( r->synthFl ) + { + unsigned tmp_phase = _cmApAggSynthSine( r, outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt, r->phase, r->frqHz ); + + // the phase will only change on packets that are actually used + if( tmp_phase != r->phase ) + hold_phase = tmp_phase; + } + else + { + // copy the any audio in the internal record buffer to the playback device + _cmApAggCopyOut( r, (cmApSample_t*)outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt ); + } + } + + r->phase = hold_phase; + + //printf("o %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); + // count callbacks + ++r->cbCnt; + } +} + + +// print the usage message for cmAudioPortTest.c +void _cmApAggPrintUsage( cmRpt_t* rpt ) +{ +char msg[] = + "cmApAggPortTest() command switches\n" + "-r -c -b -f -i -o -t -p -h \n" + "\n" + "-r = sample rate\n" + "-a = first channel\n" + "-c = audio channels\n" + "-b = count of buffers\n" + "-f = count of samples per buffer\n" + "-i = input device index\n" + "-o = output device index\n" + "-p = print report but do not start audio devices\n" + "-h = print this usage message\n"; + + cmRptPrintf(rpt,msg); +} + +// Get a command line option. +int _cmApAggGetOpt( int argc, const char* argv[], const char* label, int defaultVal, bool boolFl ) +{ + int i = 0; + for(; irpt; + + if( _cmApAggGetOpt(argc,argv,"-h",0,true) ) + _cmApAggPrintUsage(rpt); + + + runFl = _cmApAggGetOpt(argc,argv,"-p",!runFl,true)?false:true; + r.chIdx = _cmApAggGetOpt(argc,argv,"-a",0,false); + r.chCnt = _cmApAggGetOpt(argc,argv,"-c",2,false); + r.bufCnt = _cmApAggGetOpt(argc,argv,"-b",3,false); + r.framesPerCycle = _cmApAggGetOpt(argc,argv,"-f",512,false); + r.bufFrmCnt = (r.bufCnt*r.framesPerCycle); + r.bufSmpCnt = (r.chCnt * r.bufFrmCnt); + r.synthFl = false; + r.meterMs = 50; + + cmApSample_t buf[r.bufSmpCnt]; + double imeter[r.chCnt]; + + r.iMeter = imeter; + + r.inDevIdx = _cmAggGlobalInDevIdx = _cmApAggGetOpt(argc,argv,"-i",0,false); + r.outDevIdx = _cmAggGlobalOutDevIdx = _cmApAggGetOpt(argc,argv,"-o",2,false); + r.phase = 0; + r.frqHz = 2000; + r.srate = 44100; + r.bufInIdx = 0; + r.bufOutIdx = 0; + r.bufFullCnt = 0; + + r.buf = buf; + r.cbCnt = 0; + r.underunCnt = 0; + r.overunCnt = 0; + r.ifp = NULL; + r.ofp = NULL; + + + if(0) + { + if((r.ifp = fopen("/home/kevin/temp/itemp0.bin","wb")) == NULL ) + cmRptPrintf(rpt,"File open failed.\n"); + + if((r.ofp = fopen("/home/kevin/temp/otemp0.bin","wb")) == NULL ) + cmRptPrintf(rpt,"File open failed.\n"); + } + + cmRptPrintf(rpt,"%s in:%i out:%i chidx:%i chs:%i bufs=%i frm=%i rate=%f\n",runFl?"exec":"rpt",r.inDevIdx,r.outDevIdx,r.chIdx,r.chCnt,r.bufCnt,r.framesPerCycle,r.srate); + + // allocate the aggregate device system + if( cmApAggAllocate(rpt) != kOkAgRC ) + { + cmRptPrintf(rpt,"The aggregate device system allocation failed.\n"); + return 1; + } + + + unsigned physDevIdxArray[] = { 0, 1 }; + unsigned physDevCnt = sizeof(physDevIdxArray)/sizeof(physDevIdxArray[0]); + if( cmApAggCreateDevice("aggdev",physDevCnt,physDevIdxArray,kInAggFl | kOutAggFl) != kOkAgRC ) + { + cmRptPrintf(rpt,"The aggregate device creation failed.n"); + goto doneLabel; + } + + + // initialize the audio device interface + if( cmApInitialize(rpt) != kOkApRC ) + { + cmRptPrintf(rpt,"Initialize failed.\n"); + goto doneLabel; + } + + // report the current audio device configuration + for(i=0; irpt,"Audio Buf"); + + _cmBa.devArray = cmMemAllocZ(cmAudioBufDev_t,devCnt); + _cmBa.devCnt = devCnt; + _cmBa.zeroBufByteCnt = 0; + _cmBa.zeroBuf = NULL; + _cmBa.updateCnt = 0; + _cmBa.nextSeqId = 0; + return rc; +} + +cmBaRC_t _cmAudioBufIoFree( cmAudioBufIO_t* iop ) +{ + unsigned i; + for(i=0; isubBufCnt; ++i) + cmMemPtrFree(&iop->subBufArray[i].buf); + + cmMemPtrFree(&iop->subBufArray); + + return kOkBaRC; +} + +cmBaRC_t cmAudioBufFinal() +{ + cmBaRC_t rc = kOkBaRC; + unsigned i,j; + for(i=0; i<_cmBa.devCnt; ++i) + for(j=0; jioArray + i; + + _cmAudioBufIoFree(iop); + + iop->subBufArray = cmMemAllocZ(cmAudioBufSubBuf_t,subBufCnt); + iop->subBufCnt = 0; + + unsigned maxSampleWidthByteCnt = 4; + + // max size of any buffer arriving via cmAudioBufUpdate() for this device/direction + unsigned bufByteCnt = frameCnt*chCnt*maxSampleWidthByteCnt; + + // initialize the sub-buf array for this device/direction + for(j=0; jsubBufArray[j].buf = cmMemAllocZ(char,bufByteCnt); + iop->subBufArray[j].bufByteCnt = bufByteCnt; + } + + // track the largest buffer size and make _cmBa.zeroBuf[] that size + if( bufByteCnt > _cmBa.zeroBufByteCnt ) + { + cmMemResizeZ(char,_cmBa.zeroBuf,bufByteCnt); + _cmBa.zeroBufByteCnt = bufByteCnt; + } + } +} + +// Called from the audio driver within incoming samples to store (inPktArray) +// and empty buffers (outPktArray) to fill with outgoin samples. +cmBaRC_t cmAudioBufUpdate( + cmApAudioPacket_t* inPktArray, ///< full audio packets from incoming audio (from ADC) + unsigned inPktCnt, ///< count of incoming audio packets + cmApAudioPacket_t* outPktArray, ///< empty audio packet for outgoing audio (to DAC) + unsigned outPktCnt ///< count of outgoing audio packets + ) +{ + cmBaRC_t rc = kOkBaRC; + + ++_cmBa.updateCnt; + + unsigned i; + for(i=0; idevIdx ].ioArray + kInIdx; + + // check for overruns + if( iop->fullCnt == iop->subBufCnt ) + { + // input overrun + ++ip->faultCnt; + rc = cmErrMsg(&_cmBa.err,kBufOverrunBaRC,"Input buffer overrun."); + } + else + { + // get the next available sub-buf + cmAudioBufSubBuf_t* sbp = iop->subBufArray + iop->iSubBufIdx; + + // store the packet header + sbp->pkt = *ipp; + sbp->audioBytesPtr = sbp->buf; + + // calc the count of bytes of incoming packet audio + unsigned pktBufByteCnt = ipp->chCnt * ipp->audioFramesCnt * (bitsPerSample/8); + assert( pktBufByteCnt <= sbp->bufByteCnt ); + + // copy the samples into the buffer + memcpy(sbp->buf,ipp->audioBytesPtr,pktBufByteCnt); + + // advance the input sub-buffer + iop->iSubBufIdx = (iop->iSubBufIdx + 1) % iop->subBufCnt; + + iop->fullCnt+=1; + } + } + + for(i=0; idevIdx ].ioArray + kOutIdx; + + // calc the number of requested bytes + unsigned pktBufByteCnt = opp->chCnt * opp->audioFramesCnt * (bitsPerSample/8); + + // locate the oldest sub-buf which matches the pkt begin channel index + cmAudioBufSubBuf_t* sbp = NULL; + cmAudioBufSubBuf_t* tsbp = iop->subBufArray; + for(j=0; jsubBufCnt; ++j,++tsbp) + if( tsbp->fullFl && (tsbp->pkt.begChIdx == opp->pkt.begChIdx) ) + if( sbp==NULL || tsbp->seqId < sbp->seqId ) + sbp = tsbp; + + if( sbp == NULL ) + { + ++opp->faultCnt; + rc = cmErrMsg(&_cmBa.err,kBufOverrunBaRC,"Output buffer underrun."); + + // zero the pkt buffer + memset(opp->audioBytePtr,0,pktBufByteCnt); + } + else + { + // the channel count assoc'd with a given begin channel index should always match + assert( sbp->pkt.chCnt == opp->chCnt ); + + // we guarantee that the sample word width will always match + assert( sbp->pkt.bitsPerSample == opp->bitsPerSample); + + // we don't want to deal with the case where the requested number of samples + // is less than the number available from a single stored buffer - this would + // require sending out a partial buffer + assert( opp->audioFrameCnt >= sbp->pkt.audioFrameCnt ); + + // calc the number of bytes to copy out + unsigned bufByteCnt = sbp->pkt.chCnt * sbp->pkt.audioFramesCnt * (sbp->pkt.bitsPerSample/8); + + assert(bufByteCnt <= pktBufByteCnt ); + + // copy out the requested samples + memcpy(opp->audioBytesPtr,sbp->buf,cmMin(bufByteCnt,pktBufByteCnt)); + + opp->audioFramesCnt = sbp->pkt.audioFramesCnt; + + // mark the sub-buffer as available + sbp->fullFl = false; + iop->fullCnt -= 1;; + } + } + + + returnr c; +} + +bool cmAudioBufIsDeviceReady( unsigned devIdx, unsigned flags ) +{ + unsigned i; + assert( devIdx < _cmBa.devCnt ); + + // + if( cmIsFlag(flags,kInBaFl) ) + if( _cmBa.devArray[devIdx].ioArray[kInIdx].fullCnt==0) + return false; + + if( cmIsFlag(flags,kOutBaFl) ) + if( _cmBa.devArray[devIdx].ioArray[kOutIdx].fullCnt == _cmBa.devArray[devIdx].ioArray[kOutIdx].subBufCnt ) + return false; + + return true; + +} + +cmBaRC_t cmAudioBufGet( + unsigned devIdx, + unsigned flags, + cmApAudioPacket_t* pktArray[], + unsigned pktCnt ) +{ +} + +cmBaRC_t cmAudioBufAdvance( unsigned devIdx, unsigned flags ) +{ +} diff --git a/cmAudioBuf.h b/cmAudioBuf.h new file mode 100644 index 0000000..464ff0f --- /dev/null +++ b/cmAudioBuf.h @@ -0,0 +1,63 @@ +#ifndef cmAudioBuf_h +#define cmAudioBuf_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkBaRC = cmOkRC + kBufOverunBaRC, + kBufUnderunBaRC + }; + + enum + { + kInBaFl = 0x01, + kOutBaFl = 0x02 + }; + + typedef cmRC_t cmBaRC_t; + + cmBaRC_t cmAudioBufInit( cmCtx_t* ctx, unsigned devCnt ); + + cmBaRC_t cmAudioBufFinal(); + + cmBaRC_t cmAudioBufSetup( + unsigned devIdx, + unsigned cycleCnt, + unsigned inSubDevCnt, + unsigned inChCnt, + unsigned inFrameCnt, + unsigned outChCnt, + unsigned outFrameCnt, + unsigned outSubDevCnt ); + + // Called from the audio driver within incoming samples to store (inPktArray) + // and empty buffers (outPktArray) to fill with outgoin samples. + cmBaRC_t cmAudioBufUpdate( + cmApAudioPacket_t* inPktArray, ///< full audio packets from incoming audio (from ADC) + unsigned inPktCnt, ///< count of incoming audio packets + cmApAudioPacket_t* outPktArray, ///< empty audio packet for outgoing audio (to DAC) + unsigned outPktCnt ///< count of outgoing audio packets + ); + + bool cmAudioBufIsDeviceReady( unsigned devIdx, unsigned flags ); + + cmBaRC_t cmAudioBufGet( + unsigned devIdx, + unsigned flags, + cmApAudioPacket_t* pktArray[], + unsigned pktCnt ); + + cmBaRC_t cmAudioBufAdvance( unsigned devIdx, unsigned flags ); + + + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/cmAudioFile.c b/cmAudioFile.c new file mode 100644 index 0000000..11aa3a3 --- /dev/null +++ b/cmAudioFile.c @@ -0,0 +1,1696 @@ +#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 "cmAudioFile.h" +#include "cmMath.h" +#include "cmFileSys.h" + + +// #define _24to32_aif( p ) ((int)( ((p[0]>127?255:0) << 24) + (((int)p[0]) << 16) + (((int)p[1]) <<8) + p[2])) // no-swap equivalent +// See note in:_cmAudioFileReadAiffHeader() +// Note that this code byte swaps as it converts - this is to counter the byte swap that occurs in cmAudioFileReadInt(). +#define _24to32_aif( p ) ((int)( ((p[0]>127?255:0) << 0) + (((int)p[2]) << 24) + (((int)p[1]) <<16) + (((int)p[0]) << 8))) + +#define _24to32_wav( p ) ((int)( ((p[2]>127?255:0) << 24) + (((int)p[2]) << 16) + (((int)p[1]) <<8) + p[0])) + +#ifdef cmBIG_ENDIAN +#define _cmAfSwap16(v) (v) +#define _cmAfSwap32(v) (v) +#define _cmAifSwapFl (0) +#define _cmWavSwapFl (1) +#else +#define _cmAfSwap16(v) cmSwap16(v) +#define _cmAfSwap32(v) cmSwap32(v) +#define _cmAifSwapFl (1) +#define _cmWavSwapFl (0) +#endif + +enum +{ + kAiffFileId = 'FORM', + kAiffChkId = 'AIFF', + kAifcChkId = 'AIFC', + kSowtCompId = 'sowt', + kNoneCompId = 'NONE', + + kWavFileId = 'FFIR', + kWavChkId = 'EVAW', +}; + +enum { kWriteAudioGutsFl=0x01 }; + +typedef struct +{ + unsigned rc; + const char* msg; +} cmAudioErrRecd; + +typedef struct +{ + cmErr_t err; + FILE* fp; // file handle + cmAudioFileInfo_t info; // audio file details + unsigned curFrmIdx; // current frame offset + unsigned fileByteCnt; // file byte cnt + unsigned smpByteOffs; // byte offset of the first sample + cmAudioFileMarker_t* markArray; + unsigned flags; + cmChar_t* fn; +} cmAudioFileGuts; + +cmAudioErrRecd _cmAudioFileErrArray[] = +{ + { kOkAfRC, "No Error." }, + { kOpenFailAfRC, "Audio file open failed."}, + { kReadFailAfRC, "Audio file read failed."}, + { kWriteFailAfRC, "Audio file write failed."}, + { kSeekFailAfRC, "Audio file seek failed."}, + { kCloseFailAfRC, "Audio file close failed."}, + { kNotAiffAfRC, "Not an audio file."}, + { kInvalidBitWidthAfRC, "Invalid audio file bit width."}, + { kInvalidFileModeAfRC, "Invalid audio file mode."}, + { kInvalidHandleAfRC, "Invalid audio file handle."}, + { kUnknownErrAfRC, "Uknown audio file error."} +}; + +cmAudioFileH_t cmNullAudioFileH = { NULL }; + +cmAudioFileGuts* _cmAudioFileGutsPtr( cmAudioFileH_t h ) +{ + cmAudioFileGuts* p = (cmAudioFileGuts*)h.h; + + if( p == NULL ) + assert(p!=NULL); + + return p; +} + +cmRC_t _cmAudioFileError( cmAudioFileGuts* p, cmRC_t rc ) +{ + if( rc > kUnknownErrAfRC ) + rc = kUnknownErrAfRC; + + cmErrMsg(&p->err,rc,"%s Error:%s",cmStringNullGuard(p->fn),_cmAudioFileErrArray[rc].msg); + return rc; +} + +cmAudioFileGuts* _cmAudioFileValidate( cmAudioFileH_t h, cmRC_t* rcPtr, bool writeFl ) +{ + *rcPtr = kOkAfRC; + + cmAudioFileGuts* p = _cmAudioFileGutsPtr(h); + + if( p == NULL ) + *rcPtr = kInvalidHandleAfRC; + else + if( cmIsFlag(p->flags,kWriteAudioGutsFl) != writeFl ) + *rcPtr = _cmAudioFileError( p, kInvalidFileModeAfRC ); + + + return *rcPtr == kOkAfRC ? p : NULL; +} + +cmAudioFileGuts* _cmAudioFileReadGutsPtr( cmAudioFileH_t h, cmRC_t* rcPtr ) +{ return _cmAudioFileValidate( h, rcPtr, false ); } + +cmAudioFileGuts* _cmAudioFileWriteGutsPtr( cmAudioFileH_t h, cmRC_t* rcPtr ) +{ return _cmAudioFileValidate( h, rcPtr, true ); } + + + +cmRC_t _cmAudioFileSeek( cmAudioFileGuts* p, long byteOffset, int origin ) +{ + if( fseek(p->fp,byteOffset,origin) != 0 ) + return _cmAudioFileError(p,kSeekFailAfRC); + return kOkAfRC; +} + +cmRC_t _cmAudioFileRead( cmAudioFileGuts* p, void* eleBuf, unsigned bytesPerEle, unsigned eleCnt ) +{ + if( fread(eleBuf,bytesPerEle,eleCnt,p->fp) != eleCnt ) + return _cmAudioFileError(p,kReadFailAfRC); + + return kOkAfRC; +} + +cmRC_t _cmAudioFileReadUInt32( cmAudioFileGuts* p, cmUInt32_t* valuePtr ) +{ + cmRC_t rc; + + if(( rc = _cmAudioFileRead(p, valuePtr, sizeof(*valuePtr), 1 )) != kOkAfRC ) + return rc; + + + if( cmIsFlag(p->info.flags,kSwapAfFl) ) + *valuePtr = _cmAfSwap32(*valuePtr); + + return rc; +} + + +cmRC_t _cmAudioFileReadUInt16( cmAudioFileGuts* p, cmUInt16_t* valuePtr ) +{ + cmRC_t rc; + + if(( rc = _cmAudioFileRead(p, valuePtr, sizeof(*valuePtr), 1 )) != kOkAfRC ) + return rc; + + if( cmIsFlag(p->info.flags,kSwapAfFl) ) + *valuePtr = _cmAfSwap16(*valuePtr); + + return rc; +} + +cmRC_t _cmAudioFileReadPascalString( cmAudioFileGuts* p, char s[kAudioFileLabelCharCnt] ) +{ + cmRC_t rc; + unsigned char n; + + if((rc = _cmAudioFileRead(p,&n,sizeof(n),1)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileRead(p,s,n,1)) != kOkAfRC ) + return rc; + + s[n] = '\0'; + + if( n % 2 == 0 ) + rc = _cmAudioFileSeek(p,1,SEEK_CUR); + + return rc; +} + +cmRC_t _cmAudioFileReadString( cmAudioFileGuts* p, char* s, unsigned sn ) +{ + cmRC_t rc; + if((rc = _cmAudioFileRead(p,s,sn,1)) != kOkAfRC ) + return rc; + + return kOkAfRC; +} + +cmRC_t _cmAudioFileReadX80( cmAudioFileGuts* p, double* x80Ptr ) +{ + unsigned char s[10]; + cmRC_t rc = kOkAfRC; + + if((rc = _cmAudioFileRead(p,s,10,1)) != kOkAfRC ) + return rc; + + *x80Ptr = cmX80ToDouble(s); + return kOkAfRC; +} + +cmRC_t _cmAudioFileReadChunkHdr( cmAudioFileGuts* p, cmUInt32_t* chkIdPtr, unsigned* chkByteCntPtr ) +{ + cmRC_t rc = kOkAfRC; + + *chkIdPtr = 0; + *chkByteCntPtr = 0; + + if((rc = _cmAudioFileReadUInt32(p,chkIdPtr)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt32(p,chkByteCntPtr)) != kOkAfRC ) + return rc; + + // the actual on disk chunk size is always incrmented up to the next even integer + *chkByteCntPtr += (*chkByteCntPtr) % 2; + + return rc; +} + +cmRC_t _cmAudioFileReadAiffHeader( cmAudioFileGuts* p, unsigned constFormId, unsigned constAifId, bool swapFl ) +{ + cmRC_t rc = kOkAfRC; + cmUInt32_t formId = 0; + cmUInt32_t aifId = 0; + unsigned chkByteCnt = 0; + + p->info.flags = 0; + p->curFrmIdx = 0; + p->fileByteCnt = 0; + + if((rc = _cmAudioFileSeek(p,0,SEEK_SET)) != kOkAfRC ) + return rc; + + // set the swap flags + p->info.flags = cmEnaFlag(p->info.flags,kSwapAfFl, swapFl); + p->info.flags = cmEnaFlag(p->info.flags,kSwapSamplesAfFl,swapFl); + + if((rc = _cmAudioFileReadChunkHdr(p,&formId,&p->fileByteCnt)) != kOkAfRC ) + return rc; + + // + // use -Wno-multichar on GCC cmd line to disable the multi-char warning + // + + + // check the FORM/RIFF id + if( formId != constFormId ) + return kNotAiffAfRC; + + // read the AIFF/WAVE id + if((rc = _cmAudioFileReadChunkHdr(p,&aifId,&chkByteCnt)) != kOkAfRC ) + return rc; + + // check for the AIFC + if( formId == kAiffFileId && aifId != constAifId ) + { + if( aifId == kAifcChkId ) + p->info.flags = cmSetFlag(p->info.flags,kAifcAfFl); + else + return kNotAiffAfRC; + } + + // set the audio file type flag + if( aifId==kAiffChkId || aifId==kAifcChkId ) + p->info.flags = cmSetFlag(p->info.flags,kAiffAfFl); + + if( aifId==kWavChkId ) + p->info.flags = cmSetFlag(p->info.flags,kWavAfFl); + + + return rc; +} + +cmRC_t _cmAudioFileReadCommChunk( cmAudioFileGuts* p ) +{ + cmRC_t rc = kOkAfRC; + cmUInt16_t ui16; + cmUInt32_t ui32; + + if((rc = _cmAudioFileReadUInt16(p,&ui16)) != kOkAfRC ) + return rc; + p->info.chCnt = ui16; + + if((rc = _cmAudioFileReadUInt32(p,&ui32)) != kOkAfRC ) + return rc; + p->info.frameCnt = ui32; + + if((rc = _cmAudioFileReadUInt16(p,&ui16)) != kOkAfRC ) + return rc; + p->info.bits = ui16; + + if((rc = _cmAudioFileReadX80(p,&p->info.srate)) != kOkAfRC ) + return rc; + + // if this is an AIFC format file ... + if( cmIsFlag(p->info.flags,kAifcAfFl) ) + { + if((rc = _cmAudioFileReadUInt32(p,&ui32)) != kOkAfRC ) + return rc; + + switch( ui32 ) + { + case kNoneCompId: + break; + + case kSowtCompId: + // If the compression type is set to 'swot' + // then the samples are written in little-endian (Intel) format + // rather than the default big-endian format. + p->info.flags = cmTogFlag(p->info.flags,kSwapSamplesAfFl); + break; + + default: + rc = _cmAudioFileError(p,kNotAiffAfRC ); + } + + + + } + + return rc; +} + +cmRC_t _cmAudioFileReadSsndChunk( cmAudioFileGuts* p ) +{ + cmRC_t rc = kOkAfRC; + + cmUInt32_t smpOffs=0, smpBlkSize=0; + + if((rc = _cmAudioFileReadUInt32(p,&smpOffs)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt32(p,&smpBlkSize)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileSeek(p,smpOffs, SEEK_CUR)) != kOkAfRC ) + return rc; + + p->smpByteOffs = ftell(p->fp); + + return rc; +} + +cmRC_t _cmAudioFileReadMarkerChunk( cmAudioFileGuts* p ) +{ + cmRC_t rc = kOkAfRC; + + cmUInt16_t ui16; + cmUInt32_t ui32; + unsigned i; + + if((rc = _cmAudioFileReadUInt16(p,&ui16)) != kOkAfRC ) + return rc; + + p->info.markerCnt = ui16; + + assert(p->markArray == NULL); + + cmAudioFileMarker_t* m = cmMemAllocZ(cmAudioFileMarker_t,p->info.markerCnt); + + p->info.markerArray = m; + + for(i=0; iinfo.markerCnt; ++i) + { + if((rc = _cmAudioFileReadUInt16(p,&ui16)) != kOkAfRC ) + return rc; + + m[i].id = ui16; + + if((rc = _cmAudioFileReadUInt32(p,&ui32)) != kOkAfRC ) + return rc; + + m[i].frameIdx = ui32; + + if((rc = _cmAudioFileReadPascalString(p,m[i].label)) != kOkAfRC ) + return rc; + + } + return rc; +} + +cmRC_t _cmAudioFileReadFmtChunk( cmAudioFileGuts* p ) +{ + cmRC_t rc = kOkAfRC; + unsigned short fmtId, chCnt, blockAlign, bits; + unsigned srate, bytesPerSec; + + if((rc = _cmAudioFileReadUInt16(p,&fmtId)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt16(p,&chCnt)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt32(p,&srate)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt32(p,&bytesPerSec)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt16(p,&blockAlign)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt16(p,&bits)) != kOkAfRC ) + return rc; + + p->info.chCnt = chCnt; + p->info.bits = bits; + p->info.srate = srate; + + // if the 'data' chunk was read before the 'fmt' chunk then info.frameCnt + // holds the number of bytes in the data chunk + if( p->info.frameCnt != 0 ) + p->info.frameCnt = p->info.frameCnt / (p->info.chCnt * p->info.bits/8); + + return rc; +} + +cmRC_t _cmAudioFileReadDatcmhunk( cmAudioFileGuts* p, unsigned chkByteCnt ) +{ + // if the 'fmt' chunk was read before the 'data' chunk then info.chCnt is non-zero + if( p->info.chCnt != 0 ) + p->info.frameCnt = chkByteCnt / (p->info.chCnt * p->info.bits/8); + else + p->info.frameCnt = chkByteCnt; + + p->smpByteOffs = ftell(p->fp); + + return kOkAfRC; +} + +cmRC_t _cmAudioFileReadBextChunk( cmAudioFileGuts* p) +{ + cmRC_t rc = kOkAfRC; + + if((rc = _cmAudioFileReadString(p,p->info.bextRecd.desc,kAfBextDescN)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadString(p,p->info.bextRecd.origin,kAfBextOriginN)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadString(p,p->info.bextRecd.originRef,kAfBextOriginRefN)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadString(p,p->info.bextRecd.originDate,kAfBextOriginDateN)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadString(p,p->info.bextRecd.originTime,kAfBextOriginTimeN)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt32(p,&p->info.bextRecd.timeRefLow)) != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileReadUInt32(p,&p->info.bextRecd.timeRefHigh)) != kOkAfRC ) + return rc; + + return rc; +} + + + +cmAudioFileH_t cmAudioFileNewOpen( const cmChar_t* fn, cmAudioFileInfo_t* afInfoPtr, cmRC_t* rcPtr, cmRpt_t* rpt ) +{ + cmAudioFileH_t h; + cmRC_t rc = kOkAfRC; + + h.h = cmMemAllocZ( cmAudioFileGuts, 1 ); + cmErrSetup(&((cmAudioFileGuts*)h.h)->err,rpt,"Audio File"); + + if( fn != NULL ) + if((rc = cmAudioFileOpen(h,fn,afInfoPtr)) != kOkAfRC ) + { + + if( rcPtr != NULL ) + *rcPtr = rc; + + cmAudioFileDelete(&h); + } + + if( rcPtr != NULL ) + *rcPtr = rc; + + return h; +} + +cmAudioFileH_t cmAudioFileNewCreate( const cmChar_t* fn, double srate, unsigned bits, unsigned chCnt, cmRC_t* rcPtr, cmRpt_t* rpt ) +{ + cmAudioFileH_t h; + cmRC_t rc = kOkAfRC; + + h.h = cmMemAllocZ(cmAudioFileGuts,1); + cmErrSetup(&((cmAudioFileGuts*)h.h)->err,rpt,"Audio File"); + + if( fn != NULL ) + if((rc = cmAudioFileCreate(h,fn,srate,bits,chCnt)) != kOkAfRC ) + { + + if( rcPtr != NULL ) + *rcPtr = rc; + + cmAudioFileDelete(&h); + } + + if( rcPtr != NULL ) + *rcPtr = rc; + + return h; +} + +cmRC_t cmAudioFileOpen( cmAudioFileH_t h, const cmChar_t* fn, cmAudioFileInfo_t* infoPtr ) +{ + cmRC_t rc = kOkAfRC; + + cmAudioFileGuts* p = _cmAudioFileGutsPtr(h); + + // verify the file is closed before opening + if( cmAudioFileIsOpen(h) ) + if((rc = cmAudioFileClose(&h)) != kOkAfRC ) + return rc; + + // zero the info record + memset(&p->info,0,sizeof(p->info)); + + + // open the file + if((p->fp = fopen(fn,"rb")) == NULL ) + { + p->fn = (cmChar_t*)fn; // set the file name so that the error msg can use it + rc = _cmAudioFileError(p,kOpenFailAfRC); + p->fn = NULL; + goto errLabel; + } + + // read the file header + if((rc = _cmAudioFileReadAiffHeader(p,kAiffFileId,kAiffChkId,_cmAifSwapFl)) != kOkAfRC ) + if((rc = _cmAudioFileReadAiffHeader(p,kWavFileId,kWavChkId,_cmWavSwapFl)) != kOkAfRC ) + goto errLabel; + + // seek past the file header + if((rc = _cmAudioFileSeek(p,12,SEEK_SET)) != kOkAfRC ) + goto errLabel; + + // zero chCnt and frameCnt to allow the order of the 'data' and 'fmt' chunks to be noticed + p->info.chCnt = 0; + p->info.frameCnt = 0; + + while( ftell(p->fp ) < p->fileByteCnt ) + { + unsigned chkId, chkByteCnt; + if((rc = _cmAudioFileReadChunkHdr(p,&chkId,&chkByteCnt)) != kOkAfRC ) + goto errLabel; + + unsigned offs = ftell(p->fp); + + if( cmIsFlag(p->info.flags,kAiffAfFl) ) + switch(chkId) + { + case 'COMM': + if((rc = _cmAudioFileReadCommChunk(p)) != kOkAfRC ) + goto errLabel; + break; + + case 'SSND': + if((rc = _cmAudioFileReadSsndChunk(p)) != kOkAfRC ) + goto errLabel; + break; + + case 'MARK': + if((rc = _cmAudioFileReadMarkerChunk(p)) != kOkAfRC ) + goto errLabel; + break; + } + else + switch(chkId) + { + case ' tmf': + if((rc = _cmAudioFileReadFmtChunk(p)) != kOkAfRC ) + goto errLabel; + break; + + case 'atad': + if((rc = _cmAudioFileReadDatcmhunk(p,chkByteCnt)) != kOkAfRC ) + goto errLabel; + break; + + case 'txeb': + if((rc = _cmAudioFileReadBextChunk(p)) != kOkAfRC ) + goto errLabel; + break; + } + + + // seek to the end of this chunk + if((rc = _cmAudioFileSeek(p,offs+chkByteCnt,SEEK_SET)) != kOkAfRC ) + goto errLabel; + } + + // seek to the first sample offset + if((rc = _cmAudioFileSeek(p,p->smpByteOffs,SEEK_SET)) != kOkAfRC ) + goto errLabel; + + p->fn = cmMemResize( char, p->fn, strlen(fn)+1 ); + strcpy(p->fn,fn); + + if( infoPtr != NULL) + memcpy(infoPtr,&p->info,sizeof(*infoPtr)); + + return rc; + + errLabel: + cmAudioFileClose(&h); + return rc; + +} + +cmRC_t _cmAudioFileWriteBytes( cmAudioFileGuts* p, const void* b, unsigned bn ) +{ + cmRC_t rc = kOkAfRC; + if( fwrite( b, bn, 1, p->fp ) != 1 ) + return _cmAudioFileError(p,kWriteFailAfRC); + + return rc; +} + +cmRC_t _cmAudioFileWriteId( cmAudioFileGuts* p, const char* s ) +{ return _cmAudioFileWriteBytes( p, s, strlen(s)) ; } + +cmRC_t _cmAudioFileWriteUInt32( cmAudioFileGuts* p, unsigned v ) +{ + v = _cmAfSwap32(v); + return _cmAudioFileWriteBytes( p, &v, sizeof(v)) ; +} + +cmRC_t _cmAudioFileWriteUInt16( cmAudioFileGuts* p, unsigned short v ) +{ + v = _cmAfSwap16(v); + return _cmAudioFileWriteBytes( p, &v, sizeof(v)) ; + +} + +cmRC_t _cmAudioFileWriteHdr( cmAudioFileGuts* p ) +{ + cmRC_t rc = kOkAfRC; + unsigned char srateX80[10]; + + cmDoubleToX80( p->info.srate, srateX80 ); + + unsigned hdrByteCnt = 54; + unsigned ssndByteCnt = 8 + (p->info.chCnt * p->info.frameCnt * (p->info.bits/8)); + unsigned formByteCnt = hdrByteCnt + ssndByteCnt - 8; + unsigned commByteCnt = 18; + unsigned ssndSmpOffs = 0; + unsigned ssndBlkSize = 0; + + if( cmIsOddU( ssndByteCnt ) ) + { + formByteCnt++; + } + + if(( rc = _cmAudioFileSeek( p, 0, SEEK_SET )) != kOkAfRC ) + return rc; + + if(( rc = _cmAudioFileWriteId( p, "FORM")) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt32( p, formByteCnt)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteId( p, "AIFF")) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteId( p, "COMM")) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt32( p, commByteCnt)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt16( p, p->info.chCnt)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt32( p, p->info.frameCnt)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt16( p, p->info.bits)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteBytes( p, &srateX80,10)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteId( p, "SSND")) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt32( p, ssndByteCnt)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt32( p, ssndSmpOffs)) != kOkAfRC ) return rc; + if(( rc = _cmAudioFileWriteUInt32( p, ssndBlkSize)) != kOkAfRC ) return rc; + + return rc; +} + +cmRC_t cmAudioFileCreate( cmAudioFileH_t h, const cmChar_t* fn, double srate, unsigned bits, unsigned chCnt ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileGutsPtr(h); + cmFileSysPathPart_t* pp = NULL; + + // verify the file is closed before opening + if( cmAudioFileIsOpen(h) ) + if((rc = cmAudioFileClose(&h)) != kOkAfRC ) + return rc; + + // all audio files are written as AIF's - if the file name is given some other extension then issue a warning + if( strlen(fn) && ((pp = cmFsPathParts(fn)) != NULL) ) + { + unsigned i; + unsigned n = strlen(pp->extStr); + cmChar_t ext[n+1]; + strcpy(ext,pp->extStr); + + // convert the extension to upper case + for(i=0; ierr.rpt,"The AIF audio file '%s' is being written with a file extension other than 'AIF' or 'AIFF'."); + + cmFsFreePathParts(pp); + } + + // open the file + if((p->fp = fopen(fn,"wb")) == NULL ) + { + p->fn = (cmChar_t*)fn; // set the file name so that the error msg can use it + rc = _cmAudioFileError(p,kOpenFailAfRC); + p->fn = NULL; + goto errLabel; + } + + p->fn = cmMemResize( char, p->fn, strlen(fn)+1 ); + p->info.srate = srate; + p->info.bits = bits; + p->info.chCnt = chCnt; + p->info.frameCnt = 0; + p->flags = kWriteAudioGutsFl; + + strcpy(p->fn,fn); + + if((rc = _cmAudioFileWriteHdr( p )) != kOkAfRC ) + goto errLabel; + + return rc; + + errLabel: + cmAudioFileClose(&h); + return rc; + +} + +cmRC_t cmAudioFileClose( cmAudioFileH_t* h ) +{ + assert( h != NULL); + + cmAudioFileGuts* p = _cmAudioFileGutsPtr(*h); + cmRC_t rc = kOkAfRC; + + if( p->fp == NULL ) + return kOkAfRC; + + if( cmIsFlag( p->flags, kWriteAudioGutsFl ) ) + if((rc = _cmAudioFileWriteHdr(p)) != kOkAfRC ) + return rc; + + if( fclose(p->fp) != 0 ) + rc = _cmAudioFileError(p,kCloseFailAfRC); + else + { + p->fp = NULL; + + cmMemPtrFree( &(p->info.markerArray)); + + memset(&p->info,0,sizeof(p->info)); + } + return rc; + +} + +cmRC_t cmAudioFileDelete( cmAudioFileH_t* h) +{ + assert(h!=NULL); + + cmRC_t rc = kOkAfRC; + + // prevent double deletes + if( h->h == NULL ) + return kOkAfRC; + + cmAudioFileGuts* p = _cmAudioFileGutsPtr(*h); + + if( p->fp != NULL ) + rc = cmAudioFileClose(h); + + cmMemPtrFree(&p->fn); + + cmMemPtrFree(&(h->h)); + + return rc; +} + +bool cmAudioFileIsValid( cmAudioFileH_t h ) +{ return h.h != NULL; } + +bool cmAudioFileIsOpen( cmAudioFileH_t h ) +{ + if( !cmAudioFileIsValid(h) ) + return false; + + return _cmAudioFileGutsPtr(h)->fp != NULL; +} + + +bool cmAudioFileIsEOF( cmAudioFileH_t h ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc); + return (rc != kOkAfRC) || (p==NULL) || (p->curFrmIdx >= p->info.frameCnt) || (p->fp==NULL) || feof(p->fp) ? true : false; +} + +unsigned cmAudioFileTell( cmAudioFileH_t h ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc); + return (rc==kOkAfRC && p!=NULL) ? p->curFrmIdx : cmInvalidIdx; +} + +cmRC_t cmAudioFileSeek( cmAudioFileH_t h, unsigned frmIdx ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc); + + if( rc != kOkAfRC ) + return rc; + + if((rc = _cmAudioFileSeek(p,p->smpByteOffs + (frmIdx * p->info.chCnt * (p->info.bits/8)), SEEK_SET)) != kOkAfRC ) + return rc; + + p->curFrmIdx = frmIdx; + + return rc; + +} + + + +cmRC_t _cmAudioFileReadInt( cmAudioFileH_t h, unsigned totalFrmCnt, unsigned chIdx, unsigned chCnt, int* buf[], unsigned* actualFrmCntPtr, bool sumFl ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc); + + if( rc != kOkAfRC ) + return rc; + + if( actualFrmCntPtr != NULL ) + *actualFrmCntPtr = 0; + + unsigned bps = p->info.bits / 8; // bytes per sample + unsigned bpf = bps * p->info.chCnt; // bytes per file frame + unsigned bufFrmCnt = cmMin(totalFrmCnt,cmAudioFile_MAX_FRAME_READ_CNT); + unsigned bytesPerBuf = bufFrmCnt * bpf; + unsigned char fbuf[ bytesPerBuf ]; // raw bytes buffer + unsigned ci; + unsigned frmCnt = 0; + unsigned totalReadFrmCnt; + int* ptrBuf[ chCnt ]; + + + for(ci=0; ciinfo.frameCnt - p->curFrmIdx, cmMin( totalFrmCnt-totalReadFrmCnt, bufFrmCnt )); + + + // read the file frmCnt sample + if((rc = _cmAudioFileRead(p,fbuf,frmCnt*bpf,1)) != kOkAfRC ) + return rc; + + if( actualFrmCntPtr != NULL ) + *actualFrmCntPtr += frmCnt; + + assert( chIdx+chCnt <= p->info.chCnt ); + + + for(ci=0; ciinfo.bits == 8 ) + { + if( cmIsFlag(p->info.flags,kAiffAfFl) ) + { + for(; dpinfo.flags,kSwapSamplesAfFl) ) + { + switch( p->info.bits ) + { + case 8: + break; + + case 16: + for(; dpinfo.flags,kAiffAfFl) ) + { + for(; dpinfo.bits) + { + case 8: + break; + + case 16: + for(; dpinfo.flags,kAiffAfFl) ) + { + for(; dpcurFrmIdx += frmCnt; + } + + if( totalReadFrmCnt < totalFrmCnt ) + { + for(ci=0; ciinfo.flags,kSwapAfFl)); + + return rc; +} + +cmRC_t _cmAudioFileReadRealSamples( cmAudioFileH_t h, unsigned totalFrmCnt, unsigned chIdx, unsigned chCnt, float** fbuf, double** dbuf, unsigned* actualFrmCntPtr, bool sumFl ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc); + + if( rc != kOkAfRC ) + return rc; + + if( actualFrmCntPtr != NULL ) + *actualFrmCntPtr = 0; + + + unsigned totalReadCnt = 0; + unsigned bufFrmCnt = cmMin( totalFrmCnt, cmAudioFile_MAX_FRAME_READ_CNT ); + unsigned bufSmpCnt = bufFrmCnt * chCnt; + float fltMaxSmpVal = 0; + + int buf[ bufSmpCnt ]; + int* ptrBuf[ chCnt ]; + float* fPtrBuf[ chCnt ]; + double* dPtrBuf[ chCnt ]; + unsigned i; + unsigned frmCnt = 0; + + switch( p->info.bits ) + { + case 8: fltMaxSmpVal = 0x80; break; + case 16: fltMaxSmpVal = 0x8000; break; + case 24: fltMaxSmpVal = 0x800000; break; + case 32: fltMaxSmpVal = 0x80000000; break; + default: + return _cmAudioFileError(p,kInvalidBitWidthAfRC); + } + + double dblMaxSmpVal = fltMaxSmpVal; + + // initialize the audio ptr buffers + for(i=0; icurFrmIdx < p->info.frameCnt; totalReadCnt+=frmCnt) + { + unsigned actualReadFrmCnt = 0; + frmCnt = cmMin( p->info.frameCnt - p->curFrmIdx, cmMin( totalFrmCnt-totalReadCnt, bufFrmCnt ) ); + + // fill the integer audio buffer from the file + if((rc = _cmAudioFileReadInt( h, frmCnt, chIdx, chCnt, ptrBuf, &actualReadFrmCnt, false )) != kOkAfRC ) + return rc; + + if( actualFrmCntPtr != NULL ) + *actualFrmCntPtr += actualReadFrmCnt; + + // convert the integer buffer to floating point + for(i=0; i 0 ) + if((rc = cmAudioFileSeek( *hp, begFrmIdx )) != kOkAfRC ) + cmAudioFileDelete(hp); + + return rc; +} + +cmRC_t _cmAudioFileGetInt( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, bool sumFl, cmRpt_t* rpt ) +{ + cmRC_t rc0,rc1; + + cmAudioFileH_t h; + + if((rc0 = _cmAudioFileGet(fn,begFrmIdx,&h,afInfoPtr,rpt)) != kOkAfRC ) + return rc0; + + rc0 = _cmAudioFileReadInt(h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, sumFl ); + + if((rc1=cmAudioFileDelete(&h)) != kOkAfRC && rc0==kOkAfRC ) + rc0 = rc1; + + return rc0; +} + +cmRC_t _cmAudioFileGetFloat( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, bool sumFl, cmRpt_t* rpt ) +{ + cmRC_t rc0,rc1; + + cmAudioFileH_t h; + + if((rc0 = _cmAudioFileGet(fn,begFrmIdx,&h,afInfoPtr,rpt)) != kOkAfRC ) + return rc0; + + rc0 = _cmAudioFileReadFloat(h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, sumFl ); + + if((rc1=cmAudioFileDelete(&h)) != kOkAfRC && rc0==kOkAfRC ) + rc0 = rc1; + + return rc0; +} + +cmRC_t _cmAudioFileGetDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, bool sumFl, cmRpt_t* rpt ) +{ + cmRC_t rc0,rc1; + + cmAudioFileH_t h; + + if((rc0 = _cmAudioFileGet(fn,begFrmIdx,&h,afInfoPtr,rpt)) != kOkAfRC ) + return rc0; + + rc0 = _cmAudioFileReadDouble(h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, sumFl ); + + if((rc1=cmAudioFileDelete(&h)) != kOkAfRC && rc0==kOkAfRC ) + rc0 = rc1; + + return rc0; +} + +cmRC_t cmAudioFileGetInt( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ) +{ return _cmAudioFileGetInt( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, false, rpt ); } + +cmRC_t cmAudioFileGetFloat( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ) +{ return _cmAudioFileGetFloat( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, false, rpt ); } + +cmRC_t cmAudioFileGetDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ) +{ return _cmAudioFileGetDouble( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, false, rpt); } + +cmRC_t cmAudioFileGetSumInt( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ) +{ return _cmAudioFileGetInt( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, true, rpt ); } + +cmRC_t cmAudioFileGetSumFloat( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ) +{ return _cmAudioFileGetFloat( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, true, rpt ); } + +cmRC_t cmAudioFileGetSumDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ) +{ return _cmAudioFileGetDouble( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, true, rpt); } + + + +cmRC_t cmAudioFileWriteInt( cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, int** srcPtrPtr ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileWriteGutsPtr(h,&rc ); + + if( rc != kOkAfRC ) + return rc; + + unsigned bytesPerSmp = p->info.bits / 8; + unsigned bufFrmCnt = 1024; + unsigned bufByteCnt = bufFrmCnt * bytesPerSmp; + unsigned ci; + unsigned wrFrmCnt = 0; + char buf[ bufByteCnt * chCnt ]; + + while( wrFrmCnt < frmCnt ) + { + unsigned n = cmMin( frmCnt-wrFrmCnt, bufFrmCnt ); + + for(ci=0; ciinfo.bits ) + { + case 8: + { + char* dbp = buf + ci; + for(; sbp < sep; dbp+=chCnt ) + *dbp = (char)*sbp++; + } + break; + + case 16: + { + short* dbp = (short*)buf; + for(dbp+=ci; sbp < sep; dbp+=chCnt, ++sbp ) + *dbp = _cmAfSwap16((short)*sbp); + } + break; + + case 24: + { + unsigned char* dbp = (unsigned char*)buf; + for( dbp+=(ci*3); sbp < sep; dbp+=(3*chCnt), ++sbp) + { + unsigned char* s = (unsigned char*)sbp; + dbp[0] = s[2]; + dbp[1] = s[1]; + dbp[2] = s[0]; + } + } + break; + + + case 32: + { + int* dbp = (int*)buf; + for(dbp+=ci; sbp < sep; dbp+=chCnt, ++sbp ) + *dbp = _cmAfSwap32(*sbp); + } + break; + + default: + { assert(0);} + } + } + + wrFrmCnt+=n; + + if( fwrite( buf, n*bytesPerSmp*chCnt, 1, p->fp ) != 1) + { + rc = _cmAudioFileError(p,kWriteFailAfRC); + break; + } + } + + p->info.frameCnt += wrFrmCnt; + + return rc; +} + +cmRC_t _cmAudioFileWriteRealSamples( cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, const void* srcPtrPtr, unsigned realSmpByteCnt ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileWriteGutsPtr(h,&rc ); + + if( rc != kOkAfRC ) + return rc; + + unsigned bufFrmCnt = 1024; + unsigned wrFrmCnt = 0; + unsigned i = 0; + int maxSmpVal = 0; + + int buf[ chCnt * bufFrmCnt ]; + int* srcCh[ chCnt ]; + + for(i=0; iinfo.bits ) + { + case 8: maxSmpVal = 0x7f; break; + case 16: maxSmpVal = 0x7fff; break; + case 24: maxSmpVal = 0x7fffff; break; + case 32: maxSmpVal = 0x7fffffb0; break; // Note: the full range is not used for 32 bit numbers + default: // because it was found to cause difficult to detect overflows + { assert(0); } // when the signal approached full scale. + } + + // duplicate the audio buffer ptr array - this will allow the buffer ptr's to be changed + // during the float to int conversion without changing the ptrs passed in from the client + const void* ptrArray[ chCnt ]; + memcpy(ptrArray,srcPtrPtr,sizeof(ptrArray)); + + const float** sfpp = (const float**)ptrArray; + const double** sdpp = (const double**)ptrArray; + + while( wrFrmCnt < frmCnt ) + { + unsigned n = cmMin( frmCnt - wrFrmCnt, bufFrmCnt ); + + for(i=0; icurFrmIdx; + + if((rc = cmAudioFileSeek(h,0)) != kOkAfRC ) + return rc; + + *minPtr = cmSample_MAX; + *maxPtr = -cmSample_MAX; + + unsigned bufN = 1024; + cmSample_t buf[ bufN ]; + unsigned frmCnt = 0; + unsigned actualFrmCnt; + cmSample_t* bufPtr[1] = { &buf[0] }; + + for(; frmCntinfo.frameCnt; frmCnt+=actualFrmCnt) + { + actualFrmCnt = 0; + unsigned n = cmMin( p->info.frameCnt-frmCnt, bufN ); + + if((rc = cmAudioFileReadSample(h, n, chIdx, 1, bufPtr, &actualFrmCnt)) != kOkAfRC ) + return rc; + + const cmSample_t* sbp = buf; + const cmSample_t* sep = buf + actualFrmCnt; + + for(; sbp < sep; ++sbp ) + { + *meanPtr += *sbp; + if( *minPtr > *sbp ) + *minPtr = *sbp; + if( *maxPtr < *sbp ) + *maxPtr = *sbp; + } + + } + + if( frmCnt > 0 ) + *meanPtr /= frmCnt; + else + *minPtr = *maxPtr = 0; + + return cmAudioFileSeek( h, orgFrmIdx ); + +} + +cmRC_t cmAudioFileWriteFileInt( const char* fn, double srate, unsigned bits, unsigned frmCnt, unsigned chCnt, int** bufPtrPtr, cmRpt_t* rpt ) +{ + cmRC_t rc0, rc1; + cmAudioFileH_t h = cmAudioFileNewCreate(fn, srate, bits, chCnt, &rc0, rpt ); + + if( (cmAudioFileIsValid(h)==false) || (rc0!=kOkAfRC)) + return rc0; + + rc0 = cmAudioFileWriteInt( h, frmCnt, chCnt, bufPtrPtr ); + + if(((rc1 = cmAudioFileDelete(&h))!=kOkAfRC) && (rc0!=kOkAfRC)) + rc0 = rc1; + + return rc0; +} + +cmRC_t cmAudioFileWriteFileFloat( const char* fn, double srate, unsigned bits, unsigned frmCnt, unsigned chCnt, float** bufPtrPtr, cmRpt_t* rpt ) +{ + cmRC_t rc0, rc1; + cmAudioFileH_t h = cmAudioFileNewCreate(fn, srate, bits, chCnt, &rc0, rpt ); + + if( (cmAudioFileIsValid(h)==false) || (rc0!=kOkAfRC)) + return rc0; + + rc0 = cmAudioFileWriteFloat( h, frmCnt, chCnt, bufPtrPtr ); + + if(((rc1 = cmAudioFileDelete(&h))!=kOkAfRC) && (rc0!=kOkAfRC)) + rc0 = rc1; + + return rc0; +} + +cmRC_t cmAudioFileWriteFileDouble( const char* fn, double srate, unsigned bits, unsigned frmCnt, unsigned chCnt, double** bufPtrPtr, cmRpt_t* rpt ) +{ + cmRC_t rc0, rc1; + cmAudioFileH_t h = cmAudioFileNewCreate(fn, srate, bits, chCnt, &rc0, rpt ); + + if( (cmAudioFileIsValid(h)==false) || (rc0!=kOkAfRC)) + return rc0; + + rc0 = cmAudioFileWriteDouble( h, frmCnt, chCnt, bufPtrPtr ); + + if(((rc1 = cmAudioFileDelete(&h))!=kOkAfRC) && (rc0!=kOkAfRC)) + rc0 = rc1; + + return rc0; +} + + +cmRC_t cmAudioFileMinMaxMeanFn( const cmChar_t* fn, unsigned chIdx, cmSample_t* minPtr, cmSample_t* maxPtr, cmSample_t* meanPtr, cmRpt_t* rpt ) +{ + cmRC_t rc0 = kOkAfRC; + cmRC_t rc1; + + cmAudioFileH_t afH = cmAudioFileNewOpen( fn, NULL, &rc0, rpt ); + + if( rc0 != kOkAfRC ) + return rc0; + + rc0 = cmAudioFileMinMaxMean( afH, chIdx, minPtr, maxPtr, meanPtr ); + rc1 = cmAudioFileDelete(&afH); + + return rc0 != kOkAfRC ? rc0 : rc1; +} + + + +const cmChar_t* cmAudioFileName( cmAudioFileH_t h ) +{ + cmRC_t rc; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc ); + + if( rc != kOkAfRC ) + return NULL; + + return p->fn; +} + +const char* cmAudioFileErrorMsg( unsigned rc ) +{ + unsigned i; + + for(i=0; _cmAudioFileErrArray[i].rc != kUnknownErrAfRC; ++i ) + if( _cmAudioFileErrArray[i].rc == rc ) + break; + + return _cmAudioFileErrArray[i].msg; + +} + + + +cmRC_t cmAudioFileGetInfo( const cmChar_t* fn, cmAudioFileInfo_t* infoPtr, cmRpt_t* rpt ) +{ + cmRC_t rc = kOkAfRC; + + cmAudioFileH_t afH = cmAudioFileNewOpen( fn, infoPtr, &rc, rpt ); + + if( rc != kOkAfRC ) + return rc; + + return cmAudioFileDelete(&afH); +} + + +void cmAudioFilePrintInfo( const cmAudioFileInfo_t* infoPtr, cmRpt_t* rpt ) +{ + char* typeStr = "AIFF"; + char* swapStr = ""; + char* aifcStr = ""; + unsigned i; + + if( cmIsFlag(infoPtr->flags,kWavAfFl) ) + typeStr = "WAV"; + + if( cmIsFlag(infoPtr->flags,kSwapAfFl) ) + swapStr = "Swap:On"; + + if( cmIsFlag(infoPtr->flags,kAifcAfFl)) + aifcStr = "AIFC"; + + cmRptPrintf(rpt,"bits:%i chs:%i srate:%f frames:%i type:%s %s %s\n", infoPtr->bits, infoPtr->chCnt, infoPtr->srate, infoPtr->frameCnt, typeStr, swapStr, aifcStr ); + + for(i=0; imarkerCnt; ++i) + cmRptPrintf(rpt,"%i %i %s\n", infoPtr->markerArray[i].id, infoPtr->markerArray[i].frameIdx, infoPtr->markerArray[i].label); + + if( strlen(infoPtr->bextRecd.desc) ) + cmRptPrintf(rpt,"Bext Desc:%s\n",infoPtr->bextRecd.desc ); + + if( strlen(infoPtr->bextRecd.origin) ) + cmRptPrintf(rpt,"Bext Origin:%s\n",infoPtr->bextRecd.origin ); + + if( strlen(infoPtr->bextRecd.originRef) ) + cmRptPrintf(rpt,"Bext Origin Ref:%s\n",infoPtr->bextRecd.originRef ); + + if( strlen(infoPtr->bextRecd.originDate) ) + cmRptPrintf(rpt,"Bext Origin Date:%s\n",infoPtr->bextRecd.originDate ); + + if( strlen(infoPtr->bextRecd.originTime ) ) + cmRptPrintf(rpt,"Bext Origin Time:%s\n",infoPtr->bextRecd.originTime ); + + cmRptPrintf(rpt,"Bext time high:%i low:%i 0x%x%x\n",infoPtr->bextRecd.timeRefHigh,infoPtr->bextRecd.timeRefLow, infoPtr->bextRecd.timeRefHigh,infoPtr->bextRecd.timeRefLow); + +} + +cmRC_t cmAudioFileReport( cmAudioFileH_t h, cmRpt_t* rpt, unsigned frmIdx, unsigned frmCnt ) +{ + cmRC_t rc = kOkAfRC; + cmAudioFileGuts* p = _cmAudioFileReadGutsPtr(h,&rc); + + if( rc != kOkAfRC ) + return rc; + + cmRptPrintf(rpt,"function cm_audio_file_test()\n"); + cmRptPrintf(rpt,"#{\n"); + cmAudioFilePrintInfo(&p->info,rpt); + cmRptPrintf(rpt,"#}\n"); + + float buf[ p->info.chCnt * frmCnt ]; + float* bufPtr[p->info.chCnt]; + unsigned i,j,cmtFrmCnt=0; + + for(i=0; iinfo.chCnt; ++i) + bufPtr[i] = buf + (i*frmCnt); + + if((rc = cmAudioFileSeek(h,frmIdx)) != kOkAfRC ) + return rc; + + if((rc= cmAudioFileReadFloat(h,frmCnt,0,p->info.chCnt,bufPtr,&cmtFrmCnt )) != kOkAfRC) + return rc; + + cmRptPrintf(rpt,"m = [\n"); + for(i=0; iinfo.chCnt; ++j) + cmRptPrintf(rpt,"%f ", bufPtr[j][i] ); + cmRptPrintf(rpt,"\n"); + } + cmRptPrintf(rpt,"];\nplot(m)\nendfunction\n"); + + return rc; + +} + +cmRC_t cmAudioFileReportFn( const cmChar_t* fn, unsigned frmIdx, unsigned frmCnt, cmRpt_t* rpt ) +{ + cmAudioFileInfo_t info; + cmRC_t rc; + cmAudioFileH_t h = cmAudioFileNewOpen( fn, &info, &rc, rpt ); + + if(rc == kOkAfRC ) + { + cmAudioFileReport(h,rpt,frmIdx,frmCnt); + } + + return cmAudioFileDelete(&h); +} + +/// [cmAudioFileExample] + +void cmAudioFileTest( const cmChar_t* audioFn, const cmChar_t* outFn, cmRpt_t* rpt ) +{ + cmAudioFileInfo_t afInfo; + cmRC_t cmRC; + + // open an audio file + cmAudioFileH_t afH = cmAudioFileNewOpen( audioFn, &afInfo, &cmRC, rpt ); + + if( cmRC != kOkAfRC || cmAudioFileIsValid(afH)==false ) + { + cmRptPrintf(rpt,"Unable to open the audio file:%s\n",audioFn); + return; + } + + // print the header information and one seconds worth of samples + //cmAudioFileReport( afH, rpt, 66000, (unsigned)afInfo.srate); + cmAudioFileReport( afH, rpt, 0, 0); + + // close and delete the audio file handle + cmAudioFileDelete(&afH); + + +} +/// [cmAudioFileExample] diff --git a/cmAudioFile.h b/cmAudioFile.h new file mode 100644 index 0000000..afd6a0c --- /dev/null +++ b/cmAudioFile.h @@ -0,0 +1,305 @@ +/// \file cmAudioFile.h +/// \brief Audio file reader/writer class. +/// +/// This class supports reading uncompressed AIFF and WAV files and writing uncompressed AIFF files. +/// The reading and writing routines are known to work with 8,16,24, and 32 bit integer sample formats. +/// +/// Testing and example usage for this API can be found in cmProcTest.c cmAudioReadWriteTest(). +/// +/// Usage example: +/// \snippet cmAudioFile.c cmAudioFileExample + +#ifndef cmAudioFile_h +#define cmAudioFile_h + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef cmAudioFile_MAX_FRAME_READ_CNT +/// Maximum number of samples which will be read in one call to fread(). +/// This value is only significant in that an internal buffer is created on the stack +/// whose size must be limited to prevent stack overflows. +#define cmAudioFile_MAX_FRAME_READ_CNT (8192) +#endif + + + /// Audio file result codes. + enum + { + kOkAfRC = 0, + kOpenFailAfRC, + kReadFailAfRC, + kWriteFailAfRC, + kSeekFailAfRC, + kCloseFailAfRC, + kNotAiffAfRC, + kInvalidBitWidthAfRC, + kInvalidFileModeAfRC, + kInvalidHandleAfRC, + kUnknownErrAfRC + }; + + /// Informational flags used by audioFileInfo + enum + { + kAiffAfFl = 0x01, ///< this is an AIFF file + kWavAfFl = 0x02, ///< this is a WAV file + kSwapAfFl = 0x04, ///< file header bytes must be swapped + kAifcAfFl = 0x08, ///< this is an AIFC file + kSwapSamplesAfFl = 0x10 ///< file sample bytes must be swapped + }; + + + /// Constants + enum + { + kAudioFileLabelCharCnt = 256, + + kAfBextDescN = 256, + kAfBextOriginN = 32, + kAfBextOriginRefN = 32, + kAfBextOriginDateN = 10, + kAfBextOriginTimeN = 8 + }; + + /// Aiff marker record + typedef struct + { + unsigned id; + unsigned frameIdx; + char label[kAudioFileLabelCharCnt]; + } cmAudioFileMarker_t; + + /// Broadcast WAV header record As used by ProTools audio files. See http://en.wikipedia.org/wiki/Broadcast_Wave_Format + /// When generated from Protools the timeRefLow/timeRefHigh values appear to actually refer + /// to the position on the Protools time-line rather than the wall clock time. + typedef struct + { + char desc[ kAfBextDescN + 1 ]; + char origin[ kAfBextOriginN + 1 ]; + char originRef[ kAfBextOriginRefN + 1 ]; + char originDate[kAfBextOriginDateN + 1 ]; + char originTime[kAfBextOriginTimeN + 1 ]; + unsigned timeRefLow; // sample count since midnight low word + unsigned timeRefHigh; // sample count since midnight high word + } cmAudioFileBext_t; + + /// Audio file information record used by audioFileNew and audioFileOpen + typedef struct + { + unsigned bits; ///< bits per sample + unsigned chCnt; ///< count of audio file channels + double srate; ///< audio file sample rate in samples per second + unsigned frameCnt; ///< total number of sample frames in the audio file + unsigned flags; ///< informational flags + unsigned markerCnt; ///< count of markers in markerArray + cmAudioFileMarker_t* markerArray; ///< array of markers + cmAudioFileBext_t bextRecd; ///< only used with Broadcast WAV files + } cmAudioFileInfo_t; + + + + typedef cmHandle_t cmAudioFileH_t; ///< opaque audio file handle + extern cmAudioFileH_t cmNullAudioFileH; ///< NULL audio file handle + + /// Create an audio file handle and optionally use the handle to open an audio file. + /// + /// \param fn The audio file name to open or NULL to create the audio file handle without opening the file. + /// \param infoPtr A pointer to an audioFileInfo record to be filled when the file is open or NULL to ignore. + /// \param rcPtr A pointer to a result code to be set in the event of a runtime error or NULL to ignore. + /// \param rpt A pointer to a cmRpt_t object which error messages from this class will be directed to. + /// \retval cmAudioFileH_t A new audio file handle. + /// + cmAudioFileH_t cmAudioFileNewOpen( const cmChar_t* fn, cmAudioFileInfo_t* infoPtr, cmRC_t* rcPtr, cmRpt_t* rpt ); + + /// Open an audio file for writing + cmAudioFileH_t cmAudioFileNewCreate( const cmChar_t* fn, double srate, unsigned bits, unsigned chCnt, cmRC_t* rcPtr, cmRpt_t* rpt ); + + + /// Open an audio file for reading using a handle returned from an earlier call to audioFileNewXXX(). + /// + /// \param h A file handle returned from and earlier call to cmAudioFileNewOpen() or cmAudioFileNewCreate(). + /// \param fn The audio file name to open or NULL to create the audio file handle without opening the file. + /// \param infoPtr A pointer to an audioFileInfo record to be filled when the file is open or NULL to ignore. + /// \retval Returns an cmRC_t value indicating the success (kOkAfRC) or failure of the call. + /// + /// If the audio file handle 'h' refers to an open file then it is automatically closed prior to being + /// reopened with the new file. + cmRC_t cmAudioFileOpen( cmAudioFileH_t h, const cmChar_t* fn, cmAudioFileInfo_t* infoPtr ); + + /// Open an audio file for writing. + cmRC_t cmAudioFileCreate( + cmAudioFileH_t h, ///< Handle returned from an earlier call to cmAudioFileNewCreate() or cmAudioFileNewOpen(). + const cmChar_t* fn, ///< File name of the new file. + double srate, ///< Sample rate of the new file. + unsigned bits, ///< Sample word width for the new file in bits (must be 8,16,24 or 32). + unsigned chCnt ///< Audio channel count for the new file. + ); + + /// Close a the file associated with handle 'h' but do not release the handle. + /// If the file was opened for writing (cmAudioFileCreate()) then this function will + /// write the file header prior to closing the file. + cmRC_t cmAudioFileClose( cmAudioFileH_t* h ); + + /// Close the file associated with handle 'h' (via an internal call to + /// cmAudioFileClose()) and release the handle and any resources + /// associated with it. This is the complement to cmAudioFileOpen/Create(). + cmRC_t cmAudioFileDelete( cmAudioFileH_t* h ); + + /// Return true if the handle is not closed or deleted. + bool cmAudioFileIsValid( cmAudioFileH_t h ); + + /// Return true if the handle is open. + bool cmAudioFileIsOpen( cmAudioFileH_t h ); + + /// Return true if the current file position is at the end of the file. + bool cmAudioFileIsEOF( cmAudioFileH_t h ); + + /// Return the current file position as a frame index. + unsigned cmAudioFileTell( cmAudioFileH_t h ); + + /// Set the current file position as an offset from the first frame. + cmRC_t cmAudioFileSeek( cmAudioFileH_t h, unsigned frmIdx ); + + /// \name Sample Reading Functions. + ///@{ + /// Fill a user suppled buffer with up to frmCnt samples. + /// If less than frmCnt samples are available at the specified audio file location then the unused + /// buffer space is set to zero. Check *actualFrmCntPtr for the count of samples actually available + /// in the return buffer. Functions which do not include a begFrmIdx argument begin reading from + /// the current file location (see cmAudioFileSeek()). The buf argument is always a pointer to an + /// array of pointers of length chCnt. Each channel buffer specified in buf[] must contain at least + /// frmCnt samples. + /// + /// \param h An audio file handle returned from an earlier call to audioFileNew() + /// \param fn The name of the audio file to read. + /// \param begFrmIdx The frame index of the first sample to read. Functions that do not use this parameter begin reading at the current file location (See cmAudioFileTell()). + /// \param frmCnt The number of samples allocated in buf. + /// \param chIdx The index of the first channel to read. + /// \param chCnt The count of channels to read. + /// \param buf An array containing chCnt pointers to arrays of frmCnt samples. + /// \param actualFrmCntPtr The number of frames actually written to the return buffer (ignored if NULL) + + cmRC_t cmAudioFileReadInt( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr ); + cmRC_t cmAudioFileReadFloat( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr ); + cmRC_t cmAudioFileReadDouble( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr ); + + cmRC_t cmAudioFileGetInt( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ); + cmRC_t cmAudioFileGetFloat( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ); + cmRC_t cmAudioFileGetDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ); + + ///@} + + /// \name Sum the returned samples into the output buffer. + ///@{ + cmRC_t cmAudioFileReadSumInt( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr ); + cmRC_t cmAudioFileReadSumFloat( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr ); + cmRC_t cmAudioFileReadSumDouble( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr ); + + cmRC_t cmAudioFileGetSumInt( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ); + cmRC_t cmAudioFileGetSumFloat( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ); + cmRC_t cmAudioFileGetSumDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt ); + ///@} + + ///@} + + /// \name Sample Writing Functions + ///@{ + cmRC_t cmAudioFileWriteInt( cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, int** bufPtrPtr ); + cmRC_t cmAudioFileWriteFloat( cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, float** bufPtrPtr ); + cmRC_t cmAudioFileWriteDouble( cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, double** bufPtrPtr ); + + cmRC_t cmAudioFileWriteFileInt( const char* fn, double srate, unsigned bit, unsigned frmCnt, unsigned chCnt, int** bufPtrPtr, cmRpt_t* rpt ); + cmRC_t cmAudioFileWriteFileFloat( const char* fn, double srate, unsigned bit, unsigned frmCnt, unsigned chCnt, float** bufPtrPtr, cmRpt_t* rpt ); + cmRC_t cmAudioFileWriteFileDouble( const char* fn, double srate, unsigned bit, unsigned frmCnt, unsigned chCnt, double** bufPtrPtr, cmRpt_t* rpt ); + ///@} + + + + /// \name cmSample_t and cmReal_t Alias Macros + ///@{ + /// Alias the cmSample_t and cmReal_t sample reading and writing functions to the appropriate + /// type based on #CM_FLOAT_SMP and #CM_FLOAT_REAL. + +#if CM_FLOAT_SMP == 1 + +#define cmAudioFileReadSample cmAudioFileReadFloat +#define cmAudioFileReadSumSample cmAudioFileReadSumFloat +#define cmAudioFileGetSample cmAudioFileGetFloat +#define cmAudioFileGetSumSample cmAudioFileGetSumFloat +#define cmAudioFileWriteSample cmAudioFileWriteFloat +#define cmAudioFileWriteFileSample cmAudioFileWriteFileFloat + +#else + +#define cmAudioFileReadSample cmAudioFileReadDouble +#define cmAudioFileReadSumSample cmAudioFileReadSumDouble +#define cmAudioFileGetSample cmAudioFileGetDouble +#define cmAudioFileGetSumSample cmAudioFileGetSumDouble +#define cmAudioFileWriteSample cmAudioFileWriteDouble +#define cmAudioFileWriteFileSample cmAudioFileWriteFileDouble + +#endif + +#if CM_FLOAT_REAL == 1 + +#define cmAudioFileReadReal cmAudioFileReadFloat +#define cmAudioFileReadSumReal cmAudioFileReadSumFloat +#define cmAudioFileGetReal cmAudioFileGetFloat +#define cmAudioFileGetSumReal cmAudioFileGetSumFloat +#define cmAudioFileWriteReal cmAudioFileWriteFloat +#define cmAudioFileWriteFileReal cmAudioFileWriteFileFloat + +#else + +#define cmAudioFileReadReal cmAudioFileReadDouble +#define cmAudioFileReadSumReal cmAudioFileReadSumDouble +#define cmAudioFileGetReal cmAudioFileGetDouble +#define cmAudioFileGetSumReal cmAudioFileGetSumDouble +#define cmAudioFileWriteReal cmAudioFileWriteDouble +#define cmAudioFileWriteFileReal cmAudioFileWriteFileDouble + +#endif + ///@} + + + /// \name Minimum, Maximum, Mean + ///@{ + /// Scan an entire audio file and return the minimum, maximum and mean sample value. + /// On error *minPtr, *maxPtr, and *meanPtr are set to -acSample_MAX, cmSample_MAX, and 0 respectively + cmRC_t cmAudioFileMinMaxMean( cmAudioFileH_t h, unsigned chIdx, cmSample_t* minPtr, cmSample_t* maxPtr, cmSample_t* meanPtr ); + cmRC_t cmAudioFileMinMaxMeanFn( const cmChar_t* fn, unsigned chIdx, cmSample_t* minPtr, cmSample_t* maxPtr, cmSample_t* meanPtr, cmRpt_t* rpt ); + ///@} + + /// Return the file name associated with a audio file handle. + const cmChar_t* cmAudioFileName( cmAudioFileH_t h ); + + /// Given an error code return the associated message. + const char* cmAudioFileErrorMsg( unsigned rc ); + + /// \name Get information about an audio file + ///@{ + + /// Return the cmAudioFileInfo_t record associated with a file. + cmRC_t cmAudioFileGetInfo( const cmChar_t* fn, cmAudioFileInfo_t* infoPtr, cmRpt_t* rpt ); + + /// Print the cmAudioFileInfo_t to a file. + void cmAudioFilePrintInfo( const cmAudioFileInfo_t* infoPtr, cmRpt_t* ); + + /// Print the file header information and frmCnt sample values beginning at frame index frmIdx. + cmRC_t cmAudioFileReport( cmAudioFileH_t h, cmRpt_t* rpt, unsigned frmIdx, unsigned frmCnt ); + + /// Print the file header information and frmCnt sample values beginning at frame index frmIdx. + cmRC_t cmAudioFileReportFn( const cmChar_t* fn, unsigned frmIdx, unsigned frmCnt, cmRpt_t* rpt ); + ///@} + + /// Testing and example routine for functions in cmAudioFile.h. + /// Also see cmProcTest.c cmAudioFileReadWriteTest() + void cmAudioFileTest( const cmChar_t* audioFn, const cmChar_t* outFn, cmRpt_t* rpt ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmAudioFileDev.c b/cmAudioFileDev.c new file mode 100644 index 0000000..3929b37 --- /dev/null +++ b/cmAudioFileDev.c @@ -0,0 +1,561 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmAudioFile.h" +#include "cmThread.h" +#include "cmAudioPort.h" +#include "cmAudioFileDev.h" + +#include // usleep() + +#ifdef OS_OSX +#include "osx/clock_gettime_stub.h" +#endif + +#ifdef OS_LINUX +#include // clock_gettime() +#endif + + +cmAfdH_t cmAfdNullHandle = cmSTATIC_NULL_HANDLE; + +#define cmAfd_Billion (1000000000) +#define cmAfd_Million (1000000) + +typedef struct +{ + cmErr_t err; // error object + cmApCallbackPtr_t callbackPtr; // client callback function + void* cbDataPtr; // argument to be passed with the client callback + + unsigned devIdx; + cmChar_t* label; + cmChar_t* oFn; + unsigned oBits; + unsigned oChCnt; + + cmAudioFileH_t iAfH; // audio input file handle + cmAudioFileH_t oAfH; // audio output file handle + cmThreadH_t tH; // thread handle + + double srate; // file device sample rate + unsigned framesPerCycle; // count of samples sent/recv'd from the client on each callback + + cmApAudioPacket_t iPkt; // audio packet used sent to the client via callbackPtr. + cmApAudioPacket_t oPkt; // + cmApSample_t** iChArray; // audio buffer channel arrays used with cmAudioFile + cmApSample_t** oChArray; // + + bool runFl; // set to true as long as the thread should continue looping + bool rewindFl; // set to true when the input file should rewind + unsigned readErrCnt; // count of read errors from the input file + bool eofFl; // set to true when the input file reaches the EOF + unsigned writeErrCnt; // count of write errors from the output file + + long nanosPerCycle; // nano-seconds per cycle + struct timespec baseTime; + struct timespec nextTime; // next execution time + + unsigned cycleCnt; // count of cycles completed + +} cmAfd_t; + +cmAfd_t* _cmAfdHandleToPtr( cmAfdH_t h ) +{ + cmAfd_t* p = (cmAfd_t*)h.h; + assert(p != NULL ); + return p; +} + +// +void _cmAudioFileDevExec( cmAfd_t* p ) +{ + unsigned iPktCnt = 0; + unsigned oPktCnt = p->oPkt.chCnt!=0; + + // if the input device is enabled + if( p->iPkt.chCnt ) + { + unsigned actualFrmCnt = p->framesPerCycle; + + // if the input file has reached EOF - zero the input buffer + if( p->eofFl ) + memset(p->iPkt.audioBytesPtr,0,p->framesPerCycle*sizeof(cmApSample_t)); + else + { + // otherwise fill the input buffer from the input file + if( cmAudioFileReadSample(p->iAfH, p->framesPerCycle, p->iPkt.begChIdx, p->iPkt.chCnt, p->iChArray, &actualFrmCnt) != kOkAfRC ) + ++p->readErrCnt; + + // if the input file reachged EOF the set p->eofFl + if( (actualFrmCnt < p->framesPerCycle) && cmAudioFileIsEOF(p->iAfH) ) + p->eofFl = true; + } + + iPktCnt = actualFrmCnt>0; + } + + // callback to the client to provde incoming samples and receive outgoing samples + p->callbackPtr(iPktCnt ? &p->iPkt : NULL, iPktCnt, oPktCnt ? &p->oPkt : NULL, oPktCnt ); + + // if the output device is enabled + if( p->oPkt.chCnt ) + { + // write the output samples + if( cmAudioFileWriteSample( p->oAfH, p->framesPerCycle, p->oPkt.chCnt, p->oChArray ) != kOkAfRC ) + ++p->writeErrCnt; + } + + ++p->cycleCnt; + +} + +// incrment p->nextTime to the next execution time +void _cmAfdIncrNextTime( cmAfd_t* p ) +{ + long nsec = p->nextTime.tv_nsec + p->nanosPerCycle; + + if( nsec < cmAfd_Billion ) + p->nextTime.tv_nsec = nsec; + else + { + p->nextTime.tv_sec += 1; + p->nextTime.tv_nsec = nsec - cmAfd_Billion; + } +} + +// calc the time between t1 and t0 - t1 is assummed to come after t0 in order to produce a positive result +long _cmAfdDiffMicros( const struct timespec* t0, const struct timespec* t1 ) +{ + long u0 = t0->tv_sec * cmAfd_Million; + long u1 = t1->tv_sec * cmAfd_Million; + + u0 += t0->tv_nsec / 1000; + u1 += t1->tv_nsec / 1000; + + return u1 - u0; +} + +// thread callback function +bool _cmAudioDevThreadFunc(void* param) +{ + cmAfd_t* p = (cmAfd_t*)param; + struct timespec t0; + + // if this is the first time this callback has been called after a call to cmAudioFileDevStart(). + if( p->cycleCnt == 0 ) + { + // get the baseTime - all other times will be relative to this time + clock_gettime(CLOCK_REALTIME,&p->baseTime); + p->nextTime = p->baseTime; + p->nextTime.tv_sec = 0; + _cmAfdIncrNextTime(p); + } + + // if the thread has not been requested to stop + if( p->runFl ) + { + // get the current time as an offset from baseTime. + clock_gettime(CLOCK_REALTIME,&t0); + t0.tv_sec -= p->baseTime.tv_sec; + + // get length of time to next exec point + long dusec = _cmAfdDiffMicros(&t0, &p->nextTime); + + // if the execution time has not yet arrived + if( dusec > 0 ) + { + usleep(dusec); + } + + // if the thread is still running + if( p->runFl ) + { + // read/callback/write + _cmAudioFileDevExec(p); + + // calc the next exec time + _cmAfdIncrNextTime(p); + } + } + + return p->runFl; +} + +cmAfdRC_t cmAudioFileDevInitialize( + cmAfdH_t* hp, + const cmChar_t* label, + unsigned devIdx, + const cmChar_t* iFn, + const cmChar_t* oFn, + unsigned oBits, + unsigned oChCnt, + cmRpt_t* rpt ) +{ + cmAfdRC_t rc; + cmRC_t afRC; + + if((rc = cmAudioFileDevFinalize(hp)) != kOkAfdRC ) + return rc; + + // allocate the object + cmAfd_t* p = cmMemAllocZ(cmAfd_t,1); + + hp->h = p; + + cmErrSetup(&p->err,rpt,"AudioFileDevice"); + + // create the input audio file handle + if( iFn != NULL ) + { + cmAudioFileInfo_t afInfo; + + // open the input file + if(cmAudioFileIsValid( p->iAfH = cmAudioFileNewOpen(iFn,&afInfo,&afRC,rpt)) == false ) + { + rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"The audio input file '%s' could not be opened.", iFn); + goto errLabel; + } + + + + p->iPkt.devIdx = devIdx; + p->iPkt.begChIdx = 0; + p->iPkt.chCnt = afInfo.chCnt; // setting iPkt.chCnt to a non-zero value marks the input file as active + p->iPkt.audioFramesCnt = 0; + p->iPkt.bitsPerSample = afInfo.bits; + p->iPkt.flags = kFloatApFl; + p->iPkt.audioBytesPtr = NULL; + p->iChArray = cmMemResizeZ( cmApSample_t*, p->iChArray, afInfo.chCnt ); + p->readErrCnt = 0; + p->eofFl = false; + } + + // create the output audio file handle + if(cmAudioFileIsValid( p->oAfH = cmAudioFileNewOpen(NULL,NULL,NULL,rpt)) == false ) + { + rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"The audio output file object allocation failed."); + goto errLabel; + } + + // create the driver thread + if( cmThreadCreate(&p->tH, _cmAudioDevThreadFunc, p, rpt ) != kOkThRC ) + { + rc = cmErrMsg(&p->err,kThreadFailAfdRC,"The internal thread could not be created."); + goto errLabel; + } + + p->runFl = true; + p->devIdx = devIdx; + p->label = cmMemAllocStr(label); + p->oFn = cmMemAllocStr(oFn); + p->oBits = oBits; + p->oChCnt = oChCnt; + + errLabel: + if( rc != kOkAfdRC ) + cmAudioFileDevFinalize(hp); + + return rc; +} + +cmAfdRC_t cmAudioFileDevFinalize( cmAfdH_t* hp ) +{ + if( hp == NULL || cmAudioFileDevIsValid(*hp) == false ) + return kOkAfdRC; + + cmAfd_t* p = _cmAfdHandleToPtr(*hp); + + p->runFl = false; + + if( cmThreadIsValid(p->tH) ) + cmThreadDestroy(&p->tH); + + cmAudioFileDelete(&p->iAfH); + cmAudioFileDelete(&p->oAfH); + cmMemPtrFree(&p->label); + cmMemPtrFree(&p->oFn); + cmMemPtrFree(&p->iPkt.audioBytesPtr); + cmMemPtrFree(&p->oPkt.audioBytesPtr); + cmMemPtrFree(&p->iChArray); + cmMemPtrFree(&p->oChArray); + cmMemPtrFree(&p); + hp->h = NULL; + + return kOkAfdRC; +} + +bool cmAudioFileDevIsValid( cmAfdH_t h ) +{ return h.h != NULL; } + + +cmAfdRC_t cmAudioFileDevSetup( + cmAfdH_t h, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* cbDataPtr ) +{ + cmAfdRC_t rc = kOkAfdRC; + bool restartFl = false; + unsigned i; + + if( cmAudioFileDevIsStarted(h) ) + { + if((rc = cmAudioFileDevStop(h)) != kOkAfdRC ) + return rc; + + restartFl = true; + } + + cmAfd_t* p = _cmAfdHandleToPtr(h); + + /* + // close the existing input file + if(cmAudioFileClose(&p->iAfH) != kOkAfRC ) + rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file close failed on input audio file."); + + p->iPkt.chCnt = 0; // mark the input file as inactive + */ + + if( cmAudioFileIsValid( p->iAfH ) ) + if( cmAudioFileSeek( p->iAfH, 0 ) != kOkAfRC ) + rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file device rewind failed."); + + + // close the existing output file + if(cmAudioFileClose(&p->oAfH) != kOkAfRC ) + rc = cmErrMsg(&p->err,kAudioFileFailAfdRC,"Audio file close failed on output audio file."); + + p->oPkt.chCnt = 0; // mark the output file as inactive + + // if an output audio file was given ... + if( p->oFn != NULL ) + { + // ... then open it + if( cmAudioFileCreate( p->oAfH, p->oFn, srate, p->oBits, p->oChCnt ) != kOkAfRC ) + { + rc = cmErrMsg(&p->err,kAudioFileFailAfdRC, "The audio output file '%s' could not be created.",p->oFn); + goto errLabel; + } + + cmApSample_t* bp = (cmApSample_t*)p->oPkt.audioBytesPtr; + + p->oPkt.devIdx = p->devIdx; + p->oPkt.begChIdx = 0; + p->oPkt.chCnt = p->oChCnt; + p->oPkt.audioFramesCnt = framesPerCycle; + p->oPkt.bitsPerSample = p->oBits; + p->oPkt.flags = kFloatApFl; + p->oPkt.audioBytesPtr = bp = cmMemResizeZ( cmApSample_t, bp, framesPerCycle*p->oChCnt ); + p->oChArray = cmMemResizeZ( cmApSample_t*, p->oChArray, p->oChCnt ); + + for(i=0; ioChCnt; ++i) + p->oChArray[i] = bp + (i*framesPerCycle); + + } + + if( cmAudioFileIsValid( p->iAfH) ) + { + cmApSample_t* bp = (cmApSample_t*)p->iPkt.audioBytesPtr; + + p->iPkt.audioFramesCnt = framesPerCycle; + p->iPkt.audioBytesPtr = bp = cmMemResizeZ( cmApSample_t, bp, framesPerCycle*p->iPkt.chCnt ); ; + for(i=0; iiPkt.chCnt; ++i) + p->iChArray[i] = bp + (i*framesPerCycle); + } + + p->callbackPtr = callbackPtr; + p->cbDataPtr = cbDataPtr; + p->framesPerCycle = framesPerCycle; + p->srate = srate; + p->cycleCnt = 0; + p->nanosPerCycle = floor((double)framesPerCycle / srate * cmAfd_Billion ); + + if( restartFl ) + { + if((rc = cmAudioFileDevStart(h)) != kOkAfdRC ) + { + rc = cmErrMsg(&p->err,kRestartFailAfdRC,"The audio file device could not be restarted."); + } + } + + errLabel: + return rc; + +} + +const char* cmAudioFileDevLabel( cmAfdH_t h ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + return p->label; +} + +unsigned cmAudioFileDevChannelCount( cmAfdH_t h, bool inputFl ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + return inputFl ? p->iPkt.chCnt : p->oPkt.chCnt; +} + +double cmAudioFileDevSampleRate( cmAfdH_t h ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + return p->srate; +} + +unsigned cmAudioFileDevFramesPerCycle( cmAfdH_t h, bool inputFl ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + return inputFl ? p->iPkt.audioFramesCnt : p->oPkt.audioFramesCnt; +} + + +cmAfdRC_t cmAudioFileDevRewind( cmAfdH_t h ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + p->rewindFl = true; + return kOkAfdRC; +} + +cmAfdRC_t cmAudioFileDevStart( cmAfdH_t h ) +{ + cmAfdRC_t rc = kOkAfdRC; + cmAfd_t* p = _cmAfdHandleToPtr(h); + + p->cycleCnt = 0; + + if( cmThreadPause( p->tH, 0 ) != kOkThRC ) + { + rc = cmErrMsg(&p->err,kThreadFailAfdRC,"Thread start failed."); + goto errLabel; + } + + fputs("Start\n",stderr); + + errLabel: + return rc; +} + +cmAfdRC_t cmAudioFileDevStop( cmAfdH_t h ) +{ + cmAfdRC_t rc = kOkAfdRC; + cmAfd_t* p = _cmAfdHandleToPtr(h); + + if( cmThreadPause( p->tH, kPauseThFl | kWaitThFl ) != kOkThRC ) + { + rc = cmErrMsg(&p->err,kThreadFailAfdRC,"Thread stop failed."); + goto errLabel; + } + + fputs("Stop\n",stderr); + + errLabel: + return rc; +} + +bool cmAudioFileDevIsStarted( cmAfdH_t h ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + return cmThreadState(p->tH) == kRunningThId; +} + +void cmAudioFileDevReport( cmAfdH_t h, cmRpt_t* rpt ) +{ + cmAfd_t* p = _cmAfdHandleToPtr(h); + cmRptPrintf(rpt,"label:%s thr state:%i srate:%f\n",p->label,cmThreadState(p->tH),p->srate); + cmRptPrintf(rpt, "in chs:%i %s\n",p->iPkt.chCnt,cmAudioFileName(p->iAfH)); + cmRptPrintf(rpt, "out chs:%i %s\n",p->oPkt.chCnt,p->oFn); +} + +// device callback function used with cmAudioFileDevTest() note that this assumes +// that the packet buffer contain non-interleaved data. +void _cmAfdCallback( + cmApAudioPacket_t* inPktArray, + unsigned inPktCnt, + cmApAudioPacket_t* outPktArray, + unsigned outPktCnt ) +{ + cmApAudioPacket_t* ip = inPktArray; + cmApAudioPacket_t* op = outPktArray; + unsigned opi = 0; + unsigned ipi = 0; + unsigned oci = 0; + unsigned ici = 0; + + while(1) + { + if( ici == ip->chCnt) + { + ici = 0; + if( ++ipi >= inPktCnt ) + break; + + ip = inPktArray + ipi; + } + + + if( oci == op->chCnt ) + { + oci = 0; + if( ++opi >= outPktCnt ) + break; + + ip = outPktArray + opi; + } + + assert( ip->audioFramesCnt == op->audioFramesCnt ); + assert( cmIsFlag(ip->flags,kInterleavedApFl)==false && cmIsFlag(ip->flags,kInterleavedApFl)==false ); + + cmApSample_t* ibp = ((cmApSample_t*)ip->audioBytesPtr) + (ip->audioFramesCnt*ici); + cmApSample_t* obp = ((cmApSample_t*)op->audioBytesPtr) + (op->audioFramesCnt*oci); + + memcpy(obp,ibp,ip->audioFramesCnt*sizeof(cmApSample_t)); + + ++ici; + ++oci; + } +} + +void cmAudioFileDevTest( cmRpt_t* rpt ) +{ + cmAfdH_t afdH = cmAfdNullHandle; + double srate = 44100; + unsigned framesPerCycle = 512; + void* cbDataPtr = NULL; + unsigned devIdx = 0; + const cmChar_t* iFn = "/home/kevin/media/audio/McGill-1/1 Audio Track.aiff"; + const cmChar_t* oFn = "/home/kevin/temp/afd0.aif"; + unsigned oBits = 16; + unsigned oChCnt = 2; + + if( cmAudioFileDevInitialize(&afdH,"file",devIdx,iFn,oFn,oBits,oChCnt,rpt) != kOkAfdRC ) + goto errLabel; + + if( cmAudioFileDevSetup(afdH,srate,framesPerCycle,_cmAfdCallback,cbDataPtr) != kOkAfdRC ) + goto errLabel; + + char c; + fputs("q=quit 1=start 0=stop\n",stderr); + fflush(stderr); + + while((c=getchar()) != 'q') + { + switch(c) + { + case '1': cmAudioFileDevStart(afdH); break; + case '0': cmAudioFileDevStop(afdH); break; + } + + c = 0; + fflush(stdin); + } + + errLabel: + cmAudioFileDevFinalize(&afdH); + + +} + diff --git a/cmAudioFileDev.h b/cmAudioFileDev.h new file mode 100644 index 0000000..1acce13 --- /dev/null +++ b/cmAudioFileDev.h @@ -0,0 +1,74 @@ +#ifndef cmAudioFileDev_h +#define cmAudioFileDev_h + +enum +{ + kOkAfdRC = cmOkRC, + kAudioFileFailAfdRC, + kThreadFailAfdRC, + kRestartFailAfdRC +}; + +typedef unsigned cmAfdRC_t; +typedef cmHandle_t cmAfdH_t; + +extern cmAfdH_t cmAfdNullHandle; + +/// Initialize an audio file device. +/// Called by cmApPortInitialize(). +cmAfdRC_t cmAudioFileDevInitialize( + cmAfdH_t* hp, + const cmChar_t* label, + unsigned devIdx, + const cmChar_t* iFn, + const cmChar_t* oFn, + unsigned oBits, + unsigned oChCnt, + cmRpt_t* rpt ); + +/// Finalize an audio file device. +/// Called by cmApPortFinalize(). +cmAfdRC_t cmAudioFileDevFinalize( cmAfdH_t* hp ); + +/// Return true if 'h' represents a successfully initialized audio file device. +bool cmAudioFileDevIsValid( cmAfdH_t h ); + + +/// Setup the device. This function must be called prior to cmAudioFileDevStart(). +cmAfdRC_t cmAudioFileDevSetup( + cmAfdH_t h, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* cbDataPtr ); + +/// Return a device label. +const char* cmAudioFileDevLabel( cmAfdH_t h ); + +/// Return a channel count. +unsigned cmAudioFileDevChannelCount( cmAfdH_t h, bool inputFl ); + +/// Return the sample rate. +double cmAudioFileDevSampleRate( cmAfdH_t h ); + +/// Return frames per cycle. +unsigned cmAudioFileDevFramesPerCycle( cmAfdH_t h, bool inputFl ); + +/// Rewind the input file. +cmAfdRC_t cmAudioFileDevRewind( cmAfdH_t h ); + +/// Start the file device playing/recording. +cmAfdRC_t cmAudioFileDevStart( cmAfdH_t h ); + +/// Stop the file device playing/recording. +cmAfdRC_t cmAudioFileDevStop( cmAfdH_t h ); + +/// Return true if the file device is currently started. +bool cmAudioFileDevIsStarted( cmAfdH_t h ); + +/// +void cmAudioFileDevReport( cmAfdH_t h, cmRpt_t* rpt ); + +void cmAudioFileDevTest( cmRpt_t* rpt ); + +#endif diff --git a/cmAudioFileMgr.c b/cmAudioFileMgr.c new file mode 100644 index 0000000..0d9f544 --- /dev/null +++ b/cmAudioFileMgr.c @@ -0,0 +1,483 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmAudioFile.h" +#include "cmVectOpsTemplateMain.h" + +#include "cmAudioFileMgr.h" + +struct cmAfm_str; + +typedef struct +{ + cmSample_t* minV; // minV[summN] + cmSample_t* maxV; // maxV[summN] + unsigned summN; // lenght of minV[] and maxV[] +} cmAfmSummary_t; + +typedef struct cmAfmFile_str +{ + unsigned id; + cmAudioFileH_t afH; + cmAudioFileInfo_t afInfo; + unsigned smpPerSummPt; + + cmAfmSummary_t* summArray; // summArray[ afInfo.chCnt ] + cmSample_t* summMem; // memory used by summArray[] vectors + + struct cmAfm_str* p; + struct cmAfmFile_str* next; + struct cmAfmFile_str* prev; +} cmAfmFile_t; + +typedef struct cmAfm_str +{ + cmErr_t err; + cmAfmFile_t* list; +} cmAfm_t; + + +cmAfmH_t cmAfmNullHandle = cmSTATIC_NULL_HANDLE; +cmAfmFileH_t cmAfmFileNullHandle = cmSTATIC_NULL_HANDLE; + +cmAfm_t* _cmAfmHandleToPtr( cmAfmH_t h ) +{ + cmAfm_t* p = (cmAfm_t*)h.h; + assert(p!=NULL); + return p; +} + +cmAfmFile_t* _cmAfmFileHandleToPtr( cmAfmFileH_t fh ) +{ + cmAfmFile_t* fp = (cmAfmFile_t*)fh.h; + assert(fp!=NULL); + return fp; +} + + +cmAfmRC_t _cmAfmFileClose( cmAfmFile_t* fp ) +{ + cmAfmRC_t rc = kOkAfmRC; + + if( cmAudioFileIsValid( fp->afH ) ) + if( cmAudioFileDelete( &fp->afH) != kOkAfRC ) + return cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file close failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH))); + + if( fp->next != NULL ) + fp->next->prev = fp->prev; + + if( fp->prev != NULL ) + fp->prev->next = fp->next; + + if( fp->p->list == fp ) + fp->p->list = fp->next; + + + cmMemFree(fp->summArray); + cmMemFree(fp->summMem); + cmMemFree(fp); + + return rc; +} + +cmAfmRC_t cmAfmFileOpen( cmAfmH_t h, cmAfmFileH_t* fhp, const cmChar_t* audioFn, unsigned id, cmAudioFileInfo_t* afInfo ) +{ + cmAfmRC_t rc; + cmRC_t afRC; + + if((rc = cmAfmFileClose(fhp)) != kOkAfmRC ) + return rc; + + cmAfmFile_t* fp = cmMemAllocZ(cmAfmFile_t,1); + fp->p = _cmAfmHandleToPtr(h); + + // open the audio file + if( cmAudioFileIsValid(fp->afH = cmAudioFileNewOpen(audioFn, &fp->afInfo, &afRC, fp->p->err.rpt )) == false ) + { + rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"The audio file '%s' could not be opened.",cmStringNullGuard(audioFn)); + goto errLabel; + } + + // prepend the new file to the mgr's file list + if( fp->p->list != NULL ) + fp->p->list->prev = fp; + + fp->next = fp->p->list; + fp->p->list = fp; + + fp->id = id; + + fhp->h = fp; + + if( afInfo != NULL ) + *afInfo = fp->afInfo; + + + errLabel: + if( rc != kOkAfmRC ) + _cmAfmFileClose(fp); + + return rc; +} + +cmAfmRC_t cmAfmFileClose( cmAfmFileH_t* fhp ) +{ + cmAfmRC_t rc = kOkAfmRC; + if( fhp==NULL || cmAfmFileIsValid(*fhp)==false) + return rc; + + cmAfmFile_t* fp = _cmAfmFileHandleToPtr( *fhp ); + if((rc = _cmAfmFileClose(fp)) != kOkAfmRC ) + return rc; + + fhp->h = NULL; + + return rc; +} + +bool cmAfmFileIsValid( cmAfmFileH_t fh ) +{ return fh.h != NULL; } + + +unsigned cmAfmFileId( cmAfmFileH_t fh ) +{ + cmAfmFile_t* fp = _cmAfmFileHandleToPtr( fh ); + return fp->id; +} + +cmAudioFileH_t cmAfmFileHandle( cmAfmFileH_t fh ) +{ + cmAfmFile_t* fp = _cmAfmFileHandleToPtr( fh ); + return fp->afH; +} + +const cmAudioFileInfo_t* cmAfmFileInfo( cmAfmFileH_t fh ) +{ + cmAfmFile_t* fp = _cmAfmFileHandleToPtr( fh ); + return &fp->afInfo; +} + +cmAfmRC_t cmAfmFileSummarize( cmAfmFileH_t fh, unsigned smpPerSummPt ) +{ + cmAfmFile_t* fp = _cmAfmFileHandleToPtr(fh); + cmAfmRC_t rc = kOkAfmRC; + unsigned chCnt = fp->afInfo.chCnt; + + // summary points per channel per vector + unsigned summN = (unsigned)ceil((double)fp->afInfo.frameCnt / smpPerSummPt ); + + // total summary points in all channels and vectors + unsigned n = chCnt*2*summN; + + // Calc the number of summary points per audio file read + unsigned ptsPerRd = cmMax(1,cmMax(smpPerSummPt,8192) / smpPerSummPt); + + // Calc the number samples per audio file read as an integer multiple of ptsPerRd. + unsigned frmCnt = ptsPerRd * smpPerSummPt; + + unsigned actualFrmCnt = 0; + cmSample_t* chBuf[ chCnt ]; + cmSample_t buf[ frmCnt * chCnt ]; + unsigned i; + + // allocate the summary record array + if( fp->summArray == NULL ) + fp->summArray = cmMemAllocZ( cmAfmSummary_t, chCnt ); + + // allocate the summary vector memory for all channels + fp->summMem = cmMemResizeZ( cmSample_t, fp->summMem, n); + fp->smpPerSummPt = smpPerSummPt; + + // setup the summary record array and audio file read buffer + for(i=0; isummArray[i].minV = fp->summMem + i * summN * 2; + fp->summArray[i].maxV = fp->summArray[i].minV + summN; + fp->summArray[i].summN = summN; + + // setup the audio file reading channel buffer + chBuf[i] = buf + (i*frmCnt); + } + + // read the entire file and calculate the summary vectors + i = 0; + do + { + unsigned chIdx = 0; + unsigned j,k; + + // read the next frmCnt samples from the + if( cmAudioFileReadSample(fp->afH, frmCnt, chIdx, chCnt, chBuf, &actualFrmCnt ) != kOkAfRC ) + { + rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file read failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH))); + goto errLabel; + } + + // for each summary point + for(k=0; ksummArray[j].minV[i] = cmVOS_Min(chBuf[j]+k,m,1); + fp->summArray[j].maxV[i] = cmVOS_Max(chBuf[j]+k,m,1); + } + } + }while( i= outCnt ); + + double smpPerOut = (double)smpCnt/outCnt; + double summPerOut = smpPerOut/fp->smpPerSummPt; + + unsigned i; + + for(i=0; ismpPerSummPt; // starting summary pt index + double fsei = fsbi + summPerOut; // endiing summary pt index + unsigned si = (unsigned)floor(fsbi); + unsigned sn = (unsigned)floor(fsei - fsbi + 1); + + if( si > fp->summArray[chIdx].summN ) + { + minV[i] = 0; + maxV[i] = 0; + } + else + { + if( si + sn > fp->summArray[chIdx].summN ) + sn = fp->summArray[chIdx].summN - si; + + if( sn == 0 ) + { + minV[i] = 0; + maxV[i] = 0; + } + else + { + minV[i] = cmVOS_Min(fp->summArray[chIdx].minV+si,sn,1); + maxV[i] = cmVOS_Max(fp->summArray[chIdx].maxV+si,sn,1); + } + } + } + + return kOkAfmRC; +} + +// Downsample the audio data to produce the output. +cmAfmRC_t _cmAfmFileGetDownAudio( + cmAfmFile_t* fp, + unsigned chIdx, + unsigned begSmpIdx, + unsigned smpCnt, + cmSample_t* minV, + cmSample_t* maxV, + unsigned outCnt ) +{ + assert( smpCnt >= outCnt ); + + cmAfmRC_t rc = kOkAfmRC; + unsigned actualFrmCnt = 0; + unsigned chCnt = 1; + unsigned i; + cmSample_t buf[ smpCnt ]; + cmSample_t* chBuf[] = { buf }; + + // seek to the read location + if( cmAudioFileSeek( fp->afH, begSmpIdx ) != kOkAfRC ) + { + rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file seek failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH))); + goto errLabel; + } + + // read 'smpCnt' samples into chBuf[][] + if( cmAudioFileReadSample(fp->afH, smpCnt, chIdx, chCnt, chBuf, &actualFrmCnt ) != kOkAfRC ) + { + rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file read failed on '%s' durnig upsample.",cmStringNullGuard(cmAudioFileName(fp->afH))); + goto errLabel; + } + + + double smpPerOut = (double)smpCnt/outCnt; + + for(i=0; i smpCnt ) + { + minV[i] = 0; + maxV[i] = 0; + } + + if( si + sn > smpCnt ) + sn = smpCnt - si; + + minV[i] = cmVOS_Min(chBuf[chIdx]+si,sn,1); + maxV[i] = cmVOS_Max(chBuf[chIdx]+si,sn,1); + } + + errLabel: + return rc; +} + + +// If there is one or less summary points per output +cmAfmRC_t _cmAfmFileGetUpSummary( + cmAfmFile_t* fp, + unsigned chIdx, + unsigned begSmpIdx, + unsigned smpCnt, + cmSample_t* minV, + cmSample_t* maxV, + unsigned outCnt ) +{ + assert( outCnt >= smpCnt ); + cmAfmRC_t rc = kOkAfmRC; + unsigned actualFrmCnt = 0; + unsigned chCnt = 1; + unsigned i; + cmSample_t buf[ smpCnt ]; + cmSample_t* chBuf[] = { buf }; + + if( cmAudioFileSeek( fp->afH, begSmpIdx ) != kOkAfRC ) + { + rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file seek failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH))); + goto errLabel; + } + + if( cmAudioFileReadSample(fp->afH, smpCnt, chIdx, chCnt, chBuf, &actualFrmCnt ) != kOkAfRC ) + { + rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file read failed on '%s' durnig upsample.",cmStringNullGuard(cmAudioFileName(fp->afH))); + goto errLabel; + } + + + for(i=0; iafInfo.srate < maxHiResDurSecs ) + rc = _cmAfmFileGetDownAudio( fp, chIdx, begSmpIdx, smpCnt, minV, maxV, outCnt ); + else + rc = _cmAfmFileGetDownSummary( fp, chIdx, begSmpIdx, smpCnt, minV, maxV, outCnt ); + } + + return rc; +} + +//---------------------------------------------------------------------------- +// Audio File Manager +//---------------------------------------------------------------------------- +cmAfmRC_t _cmAfmDestroy( cmAfm_t* p ) +{ + cmAfmRC_t rc = kOkAfmRC; + + while( p->list != NULL ) + { + if((rc = _cmAfmFileClose(p->list)) != kOkAfmRC ) + goto errLabel; + } + + cmMemFree(p); + + errLabel: + return rc; +} + +cmAfmRC_t cmAfmCreate( cmCtx_t* ctx, cmAfmH_t* hp ) +{ + cmAfmRC_t rc; + if((rc = cmAfmDestroy(hp)) != kOkAfmRC ) + return rc; + + cmAfm_t* p = cmMemAllocZ(cmAfm_t,1); + cmErrSetup(&p->err,&ctx->rpt,"Audio File Mgr"); + + hp->h = p; + + return rc; +} + +cmAfmRC_t cmAfmDestroy( cmAfmH_t* hp ) +{ + cmAfmRC_t rc = kOkAfmRC; + + if( hp==NULL || cmAfmIsValid(*hp)==false) + return rc; + + cmAfm_t* p = _cmAfmHandleToPtr(*hp); + + if((rc = _cmAfmDestroy(p)) != kOkAfmRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmAfmIsValid( cmAfmH_t h ) +{ return h.h != NULL; } + +cmAfmFileH_t cmAfmIdToHandle( cmAfmH_t h, unsigned fileId ) +{ + cmAfm_t* p = _cmAfmHandleToPtr(h); + cmAfmFile_t* fp = p->list; + cmAfmFileH_t fh = cmAfmFileNullHandle; + + for(; fp!=NULL; fp=fp->next) + if( fp->id == fileId ) + { + fh.h = fp; + break; + } + + return fh; +} diff --git a/cmAudioFileMgr.h b/cmAudioFileMgr.h new file mode 100644 index 0000000..5cfb9c1 --- /dev/null +++ b/cmAudioFileMgr.h @@ -0,0 +1,67 @@ +#ifndef cmAudioFileMgr_h +#define cmAudioFileMgr_h + +#ifdef __cplusplus +extern "C" { +#endif + enum + { + kOkAfmRC = cmOkRC, + kAudioFileFailAfmRC + }; + + typedef cmHandle_t cmAfmH_t; + typedef cmHandle_t cmAfmFileH_t; + typedef cmRC_t cmAfmRC_t; + + extern cmAfmH_t cmAfmNullHandle; + extern cmAfmFileH_t cmAfmFileNullHandle; + + + //---------------------------------------------------------------------------- + // Audio Files + //---------------------------------------------------------------------------- + cmAfmRC_t cmAfmFileOpen( cmAfmH_t h, cmAfmFileH_t* fhp, const cmChar_t* audioFn, unsigned fileId, cmAudioFileInfo_t* afInfo ); + cmAfmRC_t cmAfmFileClose( cmAfmFileH_t* fhp ); + bool cmAfmFileIsValid( cmAfmFileH_t fh ); + + // Return the application supplied file id associated with this file. + // This value was set by the 'fileId' argument to cmAfmFileOpen(). + unsigned cmAfmFileId( cmAfmFileH_t fh ); + + // Return the file handle associated with this file. + cmAudioFileH_t cmAfmFileHandle( cmAfmFileH_t fh ); + + // Return a pointer to the information record associated with this file. + const cmAudioFileInfo_t* cmAfmFileInfo( cmAfmFileH_t fh ); + + // Summarize min and max values of the downSampled audio file. + // The summary is kept in an internal cache which is used to + // optimize the time required to complete later calls to cmAfmFileGetSummary(). + // 'downSampleFactor' is the count of samples per summary point. + cmAfmRC_t cmAfmFileSummarize( cmAfmFileH_t fh, unsigned downSampleFactor ); + + // Return a summary of the samples in the range audio file range + // begSmpIdx:begSmpIdx+smpCnt-1 reduced or expanded to 'outCnt' values + // in minV[outCnt] and maxV[outCnt]. + // If 'outCnt' is equal to 'smpCnt' then the actual sample values are returned. + cmAfmRC_t cmAfmFileGetSummary( cmAfmFileH_t fh, unsigned chIdx, unsigned begSmpIdx, unsigned smpCnt, cmSample_t* minV, cmSample_t* maxV, unsigned outCnt ); + + + //---------------------------------------------------------------------------- + // Audio File Manager + //---------------------------------------------------------------------------- + cmAfmRC_t cmAfmCreate( cmCtx_t* ctx, cmAfmH_t* hp ); + cmAfmRC_t cmAfmDestroy( cmAfmH_t* hp ); + bool cmAfmIsValid( cmAfmH_t h ); + cmAfmFileH_t cmAfmIdToHandle( cmAfmH_t h, unsigned fileId ); + + + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/cmAudioNrtDev.c b/cmAudioNrtDev.c new file mode 100644 index 0000000..1b8817b --- /dev/null +++ b/cmAudioNrtDev.c @@ -0,0 +1,355 @@ +#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 "cmThread.h" + +enum +{ + kStartedApNrtFl = 0x01, + kInStateApNrtFl = 0x02 +}; + +typedef struct +{ + unsigned phase; + bool implFl; + double gain; +} cmApNrtCh_t; + +typedef struct cmApNrtDev_str +{ + unsigned flags; + unsigned devIdx; // nrt device index + unsigned baseApDevIdx; // global audio device index for first nrt device + cmChar_t* label; + unsigned iChCnt; + unsigned oChCnt; + double srate; + unsigned fpc; + cmThreadH_t thH; + cmApCallbackPtr_t cbPtr; + void* cbArg; + unsigned cbPeriodMs; + struct cmApNrtDev_str* link; + unsigned curTimeSmp; + cmApNrtCh_t* iChArray; + cmApNrtCh_t* oChArray; +} cmApNrtDev_t; + +typedef struct +{ + cmErr_t err; + unsigned devCnt; + cmApNrtDev_t* devs; + unsigned baseApDevIdx; +} cmApNrt_t; + +cmApNrt_t* _cmNrt = NULL; + +cmApNrtDev_t* _cmApNrtIndexToDev( unsigned idx ) +{ + cmApNrtDev_t* dp = _cmNrt->devs; + unsigned i; + for(i=0; dp!=NULL && ilink; + + assert( dp != NULL ); + return dp; +} + +void cmApNrtGenImpulseCh( cmApNrtDev_t* dp, unsigned chIdx, cmSample_t* buf, unsigned chCnt, unsigned frmCnt ) +{ + double periodMs = 500; // impulse period + double durMs = 50; // impulse length + double loDb = -90.0; + double hiDb = -20.0; + double loLin = pow(10.0,loDb/20.0); + double hiLin = pow(10.0,hiDb/20.0); + unsigned periodSmp = (unsigned)(periodMs * dp->srate / 1000.0); + unsigned durSmp = (unsigned)( durMs * dp->srate / 1000.0); + + assert( chIdx < chCnt ); + + cmApNrtCh_t* cp = dp->iChArray + chIdx; + cmSample_t* sp = buf + chIdx; + + unsigned i; + for(i=0; iphase += 1; + + if( cp->implFl ) + { + gain = cp->gain; + limit = durSmp; + } + + *sp = (gain * 2.0 * rand()/RAND_MAX) - 1.0; + + if( cp->phase > limit ) + { + cp->phase = 0; + cp->implFl = !cp->implFl; + + if( cp->implFl ) + cp->gain = loLin + (hiLin - loLin) * rand()/RAND_MAX ; + + } + + } + +} + +void cmApNrtGenImpulse( cmApNrtDev_t* dp, cmSample_t* buf, unsigned chCnt, unsigned frmCnt ) +{ + unsigned i=0; + for(; icurTimeSmp += frmCnt; +} + +// return 'false' to terminate otherwise return 'true'. +bool cmApNrtThreadFunc(void* param) +{ + cmApNrtDev_t* dp = (cmApNrtDev_t*)param; + + usleep( dp->cbPeriodMs * 1000 ); + + + cmApAudioPacket_t pkt; + bool inFl = cmIsFlag(dp->flags,kInStateApNrtFl); + + pkt.devIdx = dp->devIdx + dp->baseApDevIdx; + pkt.begChIdx = 0; + pkt.audioFramesCnt = dp->fpc; + pkt.bitsPerSample = 32; + pkt.flags = kInterleavedApFl | kFloatApFl; + pkt.userCbPtr = dp->cbArg; + + if( inFl ) + { + unsigned bn = dp->iChCnt * dp->fpc; + cmSample_t b[ bn ]; + + pkt.chCnt = dp->iChCnt; + pkt.audioBytesPtr = b; + + cmApNrtGenSamples(dp,b,dp->iChCnt,dp->fpc); + + dp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application + } + else + { + unsigned bn = dp->oChCnt * dp->fpc; + cmSample_t b[ bn ]; + + pkt.chCnt = dp->oChCnt; + pkt.audioBytesPtr = b; + + dp->cbPtr(NULL,0,&pkt,1 ); // recv the samples from the application + } + + dp->flags = cmTogFlag(dp->flags,kInStateApNrtFl); + + return true; +} + +cmApRC_t cmApNrtAllocate( cmRpt_t* rpt ) +{ + if( _cmNrt != NULL ) + cmApNrtFree(); + + _cmNrt = cmMemAllocZ(cmApNrt_t,1); + + cmErrSetup(&_cmNrt->err,rpt,"cmAudioNrtDev"); + _cmNrt->devCnt = 0; + _cmNrt->devs = NULL; + _cmNrt->baseApDevIdx = 0; + return kOkApRC; +} + +cmApRC_t cmApNrtFree() +{ + cmApRC_t rc = kOkApRC; + cmApNrtDev_t* dp = _cmNrt->devs; + while( dp != NULL ) + { + cmApNrtDev_t* np = dp->link; + + if( cmThreadIsValid(dp->thH) ) + if( cmThreadDestroy(&dp->thH) != kOkThRC ) + rc = cmErrMsg(&_cmNrt->err,kThreadFailApRC,"Thread destroy failed."); + + cmMemFree(dp->label); + cmMemFree(dp->iChArray); + cmMemFree(dp->oChArray); + cmMemFree(dp); + dp = np; + } + + if( rc == kOkApRC ) + { + cmMemPtrFree(&_cmNrt); + } + + return rc; +} + +cmApRC_t cmApNrtCreateDevice( + const cmChar_t* label, + double srate, + unsigned iChCnt, + unsigned oChCnt, + unsigned cbPeriodMs ) +{ + cmApRC_t rc = kOkApRC; + + cmApNrtDev_t* dp = cmMemAllocZ(cmApNrtDev_t,1); + + dp->devIdx = _cmNrt->devCnt; + dp->baseApDevIdx = _cmNrt->baseApDevIdx; + dp->label = cmMemAllocStr(label); + dp->iChCnt = iChCnt; + dp->oChCnt = oChCnt; + dp->srate = srate; + dp->fpc = 0; + dp->cbPeriodMs = cbPeriodMs; + dp->cbPtr = NULL; + dp->cbArg = NULL; + dp->link = NULL; + dp->iChArray = iChCnt==0 ? NULL : cmMemAllocZ(cmApNrtCh_t,iChCnt); + dp->oChArray = oChCnt==0 ? NULL : cmMemAllocZ(cmApNrtCh_t,oChCnt); + + // attach the new recd to the end of the list + cmApNrtDev_t* np = _cmNrt->devs; + while( np != NULL && np->link != NULL ) + np = np->link; + + if( np == NULL ) + _cmNrt->devs = dp; + else + np->link = dp; + + if( cmThreadCreate( &dp->thH, cmApNrtThreadFunc, dp, _cmNrt->err.rpt ) != kOkThRC ) + rc = cmErrMsg(&_cmNrt->err,kThreadFailApRC,"Thread create failed."); + + ++_cmNrt->devCnt; + return rc; +} + +cmApRC_t cmApNrtInitialize( cmRpt_t* rpt, unsigned baseApDevIdx ) +{ + // set the baseApDevIdx for each device + cmApNrtDev_t* dp = _cmNrt->devs; + for(; dp!=NULL; dp=dp->link) + dp->baseApDevIdx = baseApDevIdx; + + // store the baseApDevIdx for any devices that may be created after initialization + _cmNrt->baseApDevIdx = baseApDevIdx; + + return kOkApRC; +} + +cmApRC_t cmApNrtFinalize() +{ + return kOkApRC; +} + +unsigned cmApNrtDeviceCount() +{ return _cmNrt==NULL ? 0 : _cmNrt->devCnt; } + +const char* cmApNrtDeviceLabel( unsigned devIdx ) +{ + assert( devIdx < _cmNrt->devCnt ); + const cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + return dp->label; +} + +unsigned cmApNrtDeviceChannelCount( unsigned devIdx, bool inputFl ) +{ + assert( devIdx < _cmNrt->devCnt ); + const cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + return inputFl ? dp->iChCnt : dp->oChCnt; +} + +double cmApNrtDeviceSampleRate( unsigned devIdx ) +{ + assert( devIdx < _cmNrt->devCnt ); + const cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + return dp->srate; +} + +unsigned cmApNrtDeviceFramesPerCycle( unsigned devIdx, bool inputFl ) +{ + assert( devIdx < _cmNrt->devCnt ); + const cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + return dp->fpc; +} + +cmApRC_t cmApNrtDeviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* userCbPtr ) +{ + assert( devIdx < _cmNrt->devCnt ); + cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + dp->srate = srate; + dp->fpc = framesPerCycle; + dp->cbPtr = callbackPtr; + dp->cbArg = userCbPtr; + return kOkApRC; +} + +cmApRC_t cmApNrtDeviceStart( unsigned devIdx ) +{ + assert( devIdx < _cmNrt->devCnt ); + cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + + dp->curTimeSmp = 0; + + if( cmThreadPause( dp->thH, 0 ) != kOkThRC ) + return cmErrMsg(&_cmNrt->err,kThreadFailApRC,"Thread start failed."); + + dp->flags = cmSetFlag(dp->flags,kStartedApNrtFl); + + return kOkApRC; +} + +cmApRC_t cmApNrtDeviceStop( unsigned devIdx ) +{ + assert( devIdx < _cmNrt->devCnt ); + cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + + if( cmThreadPause( dp->thH, kPauseThFl ) != kOkThRC ) + return cmErrMsg(&_cmNrt->err,kThreadFailApRC,"Thread pause failed."); + + dp->flags = cmClrFlag(dp->flags,kStartedApNrtFl); + + return kOkApRC; +} + +bool cmApNrtDeviceIsStarted( unsigned devIdx ) +{ + assert( devIdx < _cmNrt->devCnt ); + const cmApNrtDev_t* dp = _cmApNrtIndexToDev(devIdx); + return cmIsFlag(dp->flags,kStartedApNrtFl); +} diff --git a/cmAudioNrtDev.h b/cmAudioNrtDev.h new file mode 100644 index 0000000..cadae9c --- /dev/null +++ b/cmAudioNrtDev.h @@ -0,0 +1,65 @@ +#ifndef cmAudioNrtDev_h +#define cmAudioNrtDev_h + +#ifdef __cplusplus +extern "C" { +#endif + + cmApRC_t cmApNrtAllocate( cmRpt_t* rpt ); + + cmApRC_t cmApNrtFree(); + + cmApRC_t cmApNrtCreateDevice( + const cmChar_t* label, + double srate, + unsigned iChCnt, + unsigned oChCnt, + unsigned cbPeriodMs ); + + + /// Setup the audio port management object for this machine. + cmApRC_t cmApNrtInitialize( cmRpt_t* rpt, unsigned baseApDevIdx ); + + /// Stop all audio devices and release any resources held + /// by the audio port management object. + cmApRC_t cmApNrtFinalize(); + + /// Return the count of audio devices attached to this machine. + unsigned cmApNrtDeviceCount(); + + /// Get a textual description of the device at index 'devIdx'. + const char* cmApNrtDeviceLabel( unsigned devIdx ); + + /// Get the count of audio input or output channesl on device at index 'devIdx'. + unsigned cmApNrtDeviceChannelCount( unsigned devIdx, bool inputFl ); + + /// Get the current sample rate of a device. Note that if the device has both + /// input and output capability then the sample rate is the same for both. + double cmApNrtDeviceSampleRate( unsigned devIdx ); + + /// Get the count of samples per callback for the input or output for this device. + unsigned cmApNrtDeviceFramesPerCycle( unsigned devIdx, bool inputFl ); + + /// Configure a device. + cmApRC_t cmApNrtDeviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* userCbPtr ); + + /// Start a device. Note that the callback may be made prior to this function returning. + cmApRC_t cmApNrtDeviceStart( unsigned devIdx ); + + /// Stop a device. + cmApRC_t cmApNrtDeviceStop( unsigned devIdx ); + + /// Return true if the device is currently started. + bool cmApNrtDeviceIsStarted( unsigned devIdx ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmAudioPort.c b/cmAudioPort.c new file mode 100644 index 0000000..57080cf --- /dev/null +++ b/cmAudioPort.c @@ -0,0 +1,799 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmAudioPort.h" +#include "cmApBuf.h" // only needed for cmApBufTest(). +#include "cmAudioPortFile.h" +#include "cmAudioAggDev.h" +#include "cmAudioNrtDev.h" + +#ifdef OS_LINUX +#include "linux/cmAudioPortAlsa.h" +#endif + +#ifdef OS_OSX +#include "osx/cmAudioPortOsx.h" +#endif + + +typedef struct +{ + unsigned begDevIdx; + unsigned endDevIdx; + + cmApRC_t (*initialize)( cmRpt_t* rpt, unsigned baseApDevIdx ); + cmApRC_t (*finalize)(); + cmApRC_t (*deviceCount)(); + const char* (*deviceLabel)( unsigned devIdx ); + unsigned (*deviceChannelCount)( unsigned devIdx, bool inputFl ); + double (*deviceSampleRate)( unsigned devIdx ); + unsigned (*deviceFramesPerCycle)( unsigned devIdx, bool inputFl ); + cmApRC_t (*deviceSetup)( unsigned devIdx, double sr, unsigned frmPerCycle, cmApCallbackPtr_t cb, void* cbData ); + cmApRC_t (*deviceStart)( unsigned devIdx ); + cmApRC_t (*deviceStop)( unsigned devIdx ); + bool (*deviceIsStarted)( unsigned devIdx ); + +} cmApDriver_t; + +typedef struct +{ + cmErr_t err; + cmApDriver_t* drvArray; + unsigned drvCnt; + unsigned devCnt; +} cmAp_t; + +cmAp_t* _ap = NULL; + +cmApRC_t _cmApIndexToDev( unsigned devIdx, cmApDriver_t** drvPtrPtr, unsigned* devIdxPtr ) +{ + unsigned i; + for(i=0; i<_ap->drvCnt; ++i) + if( _ap->drvArray[i].begDevIdx != cmInvalidIdx ) + if( (_ap->drvArray[i].begDevIdx <= devIdx) && (devIdx <= _ap->drvArray[i].endDevIdx) ) + { + *drvPtrPtr = _ap->drvArray + i; + *devIdxPtr = devIdx - _ap->drvArray[i].begDevIdx; + return kOkApRC; + } + + return cmErrMsg(&_ap->err,kInvalidDevIdApRC,"The audio port device index %i is not valid.",devIdx); +} + +cmApRC_t cmApInitialize( cmRpt_t* rpt ) +{ + cmApRC_t rc = kOkApRC; + if((rc = cmApFinalize()) != kOkApRC ) + return rc; + + _ap = cmMemAllocZ(cmAp_t,1); + cmErrSetup(&_ap->err,rpt,"Audio Port Driver"); + + _ap->drvCnt = 4; + _ap->drvArray = cmMemAllocZ(cmApDriver_t,_ap->drvCnt); + cmApDriver_t* dp = _ap->drvArray; + +#ifdef OS_OSX + dp->initialize = cmApOsxInitialize; + dp->finalize = cmApOsxFinalize; + dp->deviceCount = cmApOsxDeviceCount; + dp->deviceLabel = cmApOsxDeviceLabel; + dp->deviceChannelCount = cmApOsxDeviceChannelCount; + dp->deviceSampleRate = cmApOsxDeviceSampleRate; + dp->deviceFramesPerCycle = cmApOsxDeviceFramesPerCycle; + dp->deviceSetup = cmApOsxDeviceSetup; + dp->deviceStart = cmApOsxDeviceStart; + dp->deviceStop = cmApOsxDeviceStop; + dp->deviceIsStarted = cmApOsxDeviceIsStarted; +#endif + +#ifdef OS_LINUX + dp->initialize = cmApAlsaInitialize; + dp->finalize = cmApAlsaFinalize; + dp->deviceCount = cmApAlsaDeviceCount; + dp->deviceLabel = cmApAlsaDeviceLabel; + dp->deviceChannelCount = cmApAlsaDeviceChannelCount; + dp->deviceSampleRate = cmApAlsaDeviceSampleRate; + dp->deviceFramesPerCycle = cmApAlsaDeviceFramesPerCycle; + dp->deviceSetup = cmApAlsaDeviceSetup; + dp->deviceStart = cmApAlsaDeviceStart; + dp->deviceStop = cmApAlsaDeviceStop; + dp->deviceIsStarted = cmApAlsaDeviceIsStarted; +#endif + + dp = _ap->drvArray + 1; + + dp->initialize = cmApFileInitialize; + dp->finalize = cmApFileFinalize; + dp->deviceCount = cmApFileDeviceCount; + dp->deviceLabel = cmApFileDeviceLabel; + dp->deviceChannelCount = cmApFileDeviceChannelCount; + dp->deviceSampleRate = cmApFileDeviceSampleRate; + dp->deviceFramesPerCycle = cmApFileDeviceFramesPerCycle; + dp->deviceSetup = cmApFileDeviceSetup; + dp->deviceStart = cmApFileDeviceStart; + dp->deviceStop = cmApFileDeviceStop; + dp->deviceIsStarted = cmApFileDeviceIsStarted; + + dp = _ap->drvArray + 2; + + dp->initialize = cmApAggInitialize; + dp->finalize = cmApAggFinalize; + dp->deviceCount = cmApAggDeviceCount; + dp->deviceLabel = cmApAggDeviceLabel; + dp->deviceChannelCount = cmApAggDeviceChannelCount; + dp->deviceSampleRate = cmApAggDeviceSampleRate; + dp->deviceFramesPerCycle = cmApAggDeviceFramesPerCycle; + dp->deviceSetup = cmApAggDeviceSetup; + dp->deviceStart = cmApAggDeviceStart; + dp->deviceStop = cmApAggDeviceStop; + dp->deviceIsStarted = cmApAggDeviceIsStarted; + + dp = _ap->drvArray + 3; + + dp->initialize = cmApNrtInitialize; + dp->finalize = cmApNrtFinalize; + dp->deviceCount = cmApNrtDeviceCount; + dp->deviceLabel = cmApNrtDeviceLabel; + dp->deviceChannelCount = cmApNrtDeviceChannelCount; + dp->deviceSampleRate = cmApNrtDeviceSampleRate; + dp->deviceFramesPerCycle = cmApNrtDeviceFramesPerCycle; + dp->deviceSetup = cmApNrtDeviceSetup; + dp->deviceStart = cmApNrtDeviceStart; + dp->deviceStop = cmApNrtDeviceStop; + dp->deviceIsStarted = cmApNrtDeviceIsStarted; + + _ap->devCnt = 0; + + unsigned i; + for(i=0; i<_ap->drvCnt; ++i) + { + unsigned dn; + cmApRC_t rc0; + + _ap->drvArray[i].begDevIdx = cmInvalidIdx; + _ap->drvArray[i].endDevIdx = cmInvalidIdx; + + if((rc0 = _ap->drvArray[i].initialize(rpt,_ap->devCnt)) != kOkApRC ) + { + rc = rc0; + continue; + } + + if((dn = _ap->drvArray[i].deviceCount()) > 0) + { + _ap->drvArray[i].begDevIdx = _ap->devCnt; + _ap->drvArray[i].endDevIdx = _ap->devCnt + dn - 1; + _ap->devCnt += dn; + } + } + + if( rc != kOkApRC ) + cmApFinalize(); + + return rc; +} + +cmApRC_t cmApFinalize() +{ + cmApRC_t rc=kOkApRC; + cmApRC_t rc0 = kOkApRC; + unsigned i; + + if( _ap == NULL ) + return kOkApRC; + + for(i=0; i<_ap->drvCnt; ++i) + { + if((rc0 = _ap->drvArray[i].finalize()) != kOkApRC ) + rc = rc0; + } + + cmMemPtrFree(&_ap->drvArray); + cmMemPtrFree(&_ap); + return rc; +} + + +unsigned cmApDeviceCount() +{ return _ap->devCnt; } + +const char* cmApDeviceLabel( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return NULL; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return cmStringNullGuard(NULL); + + return dp->deviceLabel(di); +} + +unsigned cmApDeviceChannelCount( unsigned devIdx, bool inputFl ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return 0; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceChannelCount(di,inputFl); +} + +double cmApDeviceSampleRate( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceSampleRate(di); +} + +unsigned cmApDeviceFramesPerCycle( unsigned devIdx, bool inputFl ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return 0; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceFramesPerCycle(di,inputFl); +} + +cmApRC_t cmApDeviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* userCbPtr ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return kOkApRC; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceSetup(di,srate,framesPerCycle,callbackPtr,userCbPtr); +} + +cmApRC_t cmApDeviceStart( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return kOkApRC; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceStart(di); +} + +cmApRC_t cmApDeviceStop( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return kOkApRC; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceStop(di); +} + +bool cmApDeviceIsStarted( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + cmApRC_t rc; + + if( devIdx == cmInvalidIdx ) + return false; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkApRC ) + return rc; + + return dp->deviceIsStarted(di); +} + + + +void cmApReport( cmRpt_t* rpt ) +{ + unsigned i,j,k; + for(j=0,k=0; j<_ap->drvCnt; ++j) + { + cmApDriver_t* drvPtr = _ap->drvArray + j; + unsigned n = drvPtr->deviceCount(); + + for(i=0; ideviceSampleRate(i), + drvPtr->deviceChannelCount(i,true), drvPtr->deviceFramesPerCycle(i,true), + drvPtr->deviceChannelCount(i,false), drvPtr->deviceFramesPerCycle(i,false), + drvPtr->deviceLabel(i)); + + } + } +} + +/// [cmAudioPortExample] + +// See cmApPortTest() below for the main point of entry. + +// Data structure used to hold the parameters for cpApPortTest() +// and the user defined data record passed to the host from the +// audio port callback functions. +typedef struct +{ + unsigned bufCnt; // 2=double buffering 3=triple buffering + unsigned chIdx; // first test channel + unsigned chCnt; // count of channels to test + unsigned framesPerCycle; // DSP frames per cycle + unsigned bufFrmCnt; // count of DSP frames used by the audio buffer (bufCnt * framesPerCycle) + unsigned bufSmpCnt; // count of samples used by the audio buffer (chCnt * bufFrmCnt) + unsigned inDevIdx; // input device index + unsigned outDevIdx; // output device index + double srate; // audio sample rate + unsigned meterMs; // audio meter buffer length + + // param's and state for cmApSynthSine() + unsigned phase; // sine synth phase + double frqHz; // sine synth frequency in Hz + + // buffer and state for cmApCopyIn/Out() + cmApSample_t* buf; // buf[bufSmpCnt] - circular interleaved audio buffer + unsigned bufInIdx; // next input buffer index + unsigned bufOutIdx; // next output buffer index + unsigned bufFullCnt; // count of full samples + + // debugging log data arrays + unsigned logCnt; // count of elements in log[] and ilong[] + char* log; // log[logCnt] + unsigned* ilog; // ilog[logCnt] + unsigned logIdx; // current log index + + unsigned cbCnt; // count the callback +} cmApPortTestRecd; + + +#ifdef NOT_DEF +// The application can request any block of channels from the device. The packets are provided with the starting +// device channel and channel count. This function converts device channels and channel counts to buffer +// channel indexes and counts. +// +// Example: +// input output +// i,n i n +// App: 0,4 0 1 2 3 -> 2 2 +// Pkt 2,8 2 3 4 5 6 7 8 -> 0 2 +// +// The return value is the count of application requested channels located in this packet. +// +// input: *appChIdxPtr and appChCnt describe a block of device channels requested by the application. +// *pktChIdxPtr and pktChCnt describe a block of device channels provided to the application +// +// output:*appChIdxPtr and describe a block of app buffer channels which will send/recv samples. +// *pktChIdxPtr and describe a block of pkt buffer channels which will send/recv samples +// +unsigned _cmApDeviceToBuffer( unsigned* appChIdxPtr, unsigned appChCnt, unsigned* pktChIdxPtr, unsigned pktChCnt ) +{ + unsigned abi = *appChIdxPtr; + unsigned aei = abi+appChCnt-1; + + unsigned pbi = *pktChIdxPtr; + unsigned pei = pbi+pktChCnt-1; + + // if the ch's rqstd by the app do not overlap with this packet - return false. + if( aei < pbi || abi > pei ) + return 0; + + // if the ch's rqstd by the app overlap with the beginning of the pkt channel block + if( abi < pbi ) + { + appChCnt -= pbi - abi; + *appChIdxPtr = pbi - abi; + *pktChIdxPtr = 0; + } + else + { + // the rqstd ch's begin inside the pkt channel block + pktChCnt -= abi - pbi; + *pktChIdxPtr = abi - pbi; + *appChIdxPtr = 0; + } + + // if the pkt channels extend beyond the rqstd ch block + if( aei < pei ) + pktChCnt -= pei - aei; + else + appChCnt -= aei - pei; // the rqstd ch's extend beyond or coincide with the pkt block + + // the returned channel count must always be the same for both the rqstd and pkt + return cmMin(appChCnt,pktChCnt); + +} + + +// synthesize a sine signal into an interleaved audio buffer +unsigned _cmApSynthSine( cmApPortTestRecd* r, float* p, unsigned chIdx, unsigned chCnt, unsigned frmCnt, unsigned phs, double hz ) +{ + long ph = 0; + unsigned i; + unsigned bufIdx = r->chIdx; + unsigned bufChCnt; + + if( (bufChCnt = _cmApDeviceToBuffer( &bufIdx, r->chCnt, &chIdx, chCnt )) == 0) + return phs; + + + //if( r->cbCnt < 50 ) + // printf("ch:%i cnt:%i ch:%i cnt:%i bi:%i bcn:%i\n",r->chIdx,r->chCnt,chIdx,chCnt,bufIdx,bufChCnt); + + + for(i=bufIdx; israte )); + } + } + + return ph; +} + +// Copy the audio samples in the interleaved audio buffer sp[srcChCnt*srcFrameCnt] +// to the internal record buffer. +void _cmApCopyIn( cmApPortTestRecd* r, const cmApSample_t* sp, unsigned srcChIdx, unsigned srcChCnt, unsigned srcFrameCnt ) +{ + unsigned i,j; + + unsigned chCnt = cmMin(r->chCnt,srcChCnt); + + for(i=0; ibuf[ r->bufInIdx + j ] = sp[ (i*srcChCnt) + j ]; + + for(; jchCnt; ++j) + r->buf[ r->bufInIdx + j ] = 0; + + r->bufInIdx = (r->bufInIdx+r->chCnt) % r->bufFrmCnt; + } + + //r->bufFullCnt = (r->bufFullCnt + srcFrameCnt) % r->bufFrmCnt; + r->bufFullCnt += srcFrameCnt; +} + +// Copy audio samples out of the internal record buffer into dp[dstChCnt*dstFrameCnt]. +void _cmApCopyOut( cmApPortTestRecd* r, cmApSample_t* dp, unsigned dstChIdx, unsigned dstChCnt, unsigned dstFrameCnt ) +{ + // if there are not enough samples available to fill the destination buffer then zero the dst buf. + if( r->bufFullCnt < dstFrameCnt ) + { + printf("Empty Output Buffer\n"); + memset( dp, 0, dstFrameCnt*dstChCnt*sizeof(cmApSample_t) ); + } + else + { + unsigned i,j; + unsigned chCnt = cmMin(dstChCnt, r->chCnt); + + // for each output frame + for(i=0; ibuf[ r->bufOutIdx + j ]; + + // zero any output ch's for which there is no internal buf channel + for(; jbufOutIdx = (r->bufOutIdx + r->chCnt) % r->bufFrmCnt; + } + + r->bufFullCnt -= dstFrameCnt; + } +} + +// Audio port callback function called from the audio device thread. +void _cmApPortCb( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) +{ + unsigned i; + + // for each incoming audio packet + for(i=0; iinDevIdx ) + { + // copy the incoming audio into an internal buffer where it can be picked up by _cpApCopyOut(). + _cmApCopyIn( r, (cmApSample_t*)inPktArray[i].audioBytesPtr, inPktArray[i].begChIdx, inPktArray[i].chCnt, inPktArray[i].audioFramesCnt ); + } + ++r->cbCnt; + + //printf("i %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); + } + + unsigned hold_phase = 0; + + // for each outgoing audio packet + for(i=0; ioutDevIdx ) + { + // zero the output buffer + memset(outPktArray[i].audioBytesPtr,0,outPktArray[i].chCnt * outPktArray[i].audioFramesCnt * sizeof(cmApSample_t) ); + + // if the synth is enabled + if( r->synthFl ) + { + unsigned tmp_phase = _cmApSynthSine( r, outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt, r->phase, r->frqHz ); + + // the phase will only change on packets that are actually used + if( tmp_phase != r->phase ) + hold_phase = tmp_phase; + } + else + { + // copy the any audio in the internal record buffer to the playback device + _cmApCopyOut( r, (cmApSample_t*)outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt ); + } + } + + r->phase = hold_phase; + + //printf("o %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); + // count callbacks + ++r->cbCnt; + } +} +#endif + +// print the usage message for cmAudioPortTest.c +void _cmApPrintUsage( cmRpt_t* rpt ) +{ +char msg[] = + "cmApPortTest() command switches\n" + "-r -c -b -f -i -o -t -p -h \n" + "\n" + "-r = sample rate\n" + "-a = first channel\n" + "-c = audio channels\n" + "-b = count of buffers\n" + "-f = count of samples per buffer\n" + "-i = input device index\n" + "-o = output device index\n" + "-p = print report but do not start audio devices\n" + "-h = print this usage message\n"; + + cmRptPrintf(rpt,msg); +} + +// Get a command line option. +int _cmApGetOpt( int argc, const char* argv[], const char* label, int defaultVal, bool boolFl ) +{ + int i = 0; + for(; ierr,rpt,"Audio Port File"); + + return rc; +} + +cmApRC_t cmApFileFinalize() +{ + cmApRC_t rc = kOkApRC; + + if( _cmApf == NULL ) + return kOkApRC; + + unsigned i; + for(i=0; i<_cmApf->devCnt; ++i) + { + cmApRC_t rc0; + if((rc0 = cmApFileDeviceDestroy(i)) != kOkApRC ) + rc = rc0; + } + + cmMemPtrFree(&_cmApf->devArray); + _cmApf->devCnt = 0; + + cmMemPtrFree(&_cmApf); + + return rc; +} + +unsigned cmApFileDeviceCreate( + const cmChar_t* devLabel, + const cmChar_t* iFn, + const cmChar_t* oFn, + unsigned oBits, + unsigned oChCnt ) +{ + unsigned i; + + // find an available device slot + for(i=0; i<_cmApf->devCnt; ++i) + if( cmAudioFileDevIsValid( _cmApf->devArray[i].devH ) == false ) + break; + + // if no device slot is availd ... + if( i == _cmApf->devCnt ) + { + // ... create a new one + _cmApf->devArray = cmMemResizePZ(cmApDev_t, _cmApf->devArray, _cmApf->devCnt+1); + ++_cmApf->devCnt; + } + + // open the file device + if( cmAudioFileDevInitialize( &_cmApf->devArray[i].devH, devLabel, i, iFn, oFn, oBits, oChCnt, _cmApf->err.rpt ) != kOkAfdRC ) + { + cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device initialization failed."); + i = cmInvalidIdx; + } + + return i; +} + +cmApRC_t cmApFileDeviceDestroy( unsigned devIdx ) +{ + if( cmAudioFileDevFinalize( &_cmApf->devArray[devIdx].devH ) != kOkAfdRC ) + return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device finalize failed."); + + return kOkApRC; +} + +unsigned cmApFileDeviceCount() +{ return _cmApf->devCnt; } + +const char* cmApFileDeviceLabel( unsigned devIdx ) +{ + assert( devIdx < cmApFileDeviceCount()); + return cmAudioFileDevLabel( _cmApf->devArray[devIdx].devH ); +} + +unsigned cmApFileDeviceChannelCount( unsigned devIdx, bool inputFl ) +{ + assert( devIdx < cmApFileDeviceCount()); + return cmAudioFileDevChannelCount( _cmApf->devArray[devIdx].devH, inputFl ); +} + +double cmApFileDeviceSampleRate( unsigned devIdx ) +{ + assert( devIdx < cmApFileDeviceCount()); + return cmAudioFileDevSampleRate( _cmApf->devArray[devIdx].devH ); +} + +unsigned cmApFileDeviceFramesPerCycle( unsigned devIdx, bool inputFl ) +{ + assert( devIdx < cmApFileDeviceCount()); + return cmAudioFileDevFramesPerCycle( _cmApf->devArray[devIdx].devH, inputFl ); +} + +cmApRC_t cmApFileDeviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* userCbPtr ) +{ + assert( devIdx < cmApFileDeviceCount()); + + if( cmAudioFileDevSetup( _cmApf->devArray[devIdx].devH,srate,framesPerCycle,callbackPtr,userCbPtr) != kOkAfdRC ) + return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device setup failed."); + + return kOkApRC; +} + +cmApRC_t cmApFileDeviceStart( unsigned devIdx ) +{ + assert( devIdx < cmApFileDeviceCount()); + + if( cmAudioFileDevStart( _cmApf->devArray[devIdx].devH ) != kOkAfdRC ) + return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device setup failed."); + + return kOkApRC; +} + +cmApRC_t cmApFileDeviceStop( unsigned devIdx ) +{ + assert( devIdx < cmApFileDeviceCount()); + + if( cmAudioFileDevStop( _cmApf->devArray[devIdx].devH ) != kOkAfdRC ) + return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device setup failed."); + + return kOkApRC; +} + +bool cmApFileDeviceIsStarted( unsigned devIdx ) +{ + assert( devIdx < cmApFileDeviceCount()); + return cmAudioFileDevIsStarted( _cmApf->devArray[devIdx].devH ); +} + +void cmApFileReport( cmRpt_t* rpt ) +{ + unsigned i; + for(i=0; _cmApf->devCnt; ++i) + { + cmRptPrintf(rpt,"%i: ",i); + cmAudioFileDevReport( _cmApf->devArray[i].devH, rpt ); + cmRptPrintf(rpt,"\n"); + } +} + + +// device callback function used with cmAudioPortFileTest() note that this assumes +// that the packet buffer contain non-interleaved data. +void _cmApFileTestCb( + cmApAudioPacket_t* inPktArray, + unsigned inPktCnt, + cmApAudioPacket_t* outPktArray, + unsigned outPktCnt ) +{ + cmApAudioPacket_t* ip = inPktArray; + cmApAudioPacket_t* op = outPktArray; + unsigned opi = 0; + unsigned ipi = 0; + unsigned oci = 0; + unsigned ici = 0; + + while(1) + { + if( ici == ip->chCnt) + { + ici = 0; + if( ++ipi >= inPktCnt ) + break; + + ip = inPktArray + ipi; + } + + + if( oci == op->chCnt ) + { + oci = 0; + if( ++opi >= outPktCnt ) + break; + + ip = outPktArray + opi; + } + + assert( ip->audioFramesCnt == op->audioFramesCnt ); + assert( cmIsFlag(ip->flags,kInterleavedApFl)==false && cmIsFlag(ip->flags,kInterleavedApFl)==false ); + + cmApSample_t* ibp = ((cmApSample_t*)ip->audioBytesPtr) + (ip->audioFramesCnt*ici); + cmApSample_t* obp = ((cmApSample_t*)op->audioBytesPtr) + (op->audioFramesCnt*oci); + + memcpy(obp,ibp,ip->audioFramesCnt*sizeof(cmApSample_t)); + + ++ici; + ++oci; + } +} + + +void cmApFileTest( cmRpt_t* rpt ) +{ + unsigned dev0Idx; + + const cmChar_t* promptStr = "apf> q=quit 1=start 0=stop\n"; + const cmChar_t* label0 = "file0"; + const cmChar_t* i0Fn = "/home/kevin/media/audio/McGill-1/1 Audio Track.aiff"; + const cmChar_t* o0Fn = "/home/kevin/temp/afd1.aif"; + unsigned o0Bits = 16; + unsigned o0ChCnt = 2; + double srate = 44100; + unsigned framesPerCycle = 512; + + // initialize audio port file API + if( cmApFileInitialize(rpt,0) != kOkApRC ) + return; + + // create an audio port file + if((dev0Idx = cmApFileDeviceCreate(label0,i0Fn,o0Fn,o0Bits,o0ChCnt)) == cmInvalidIdx ) + goto errLabel; + + // configure an audio port file + if( cmApFileDeviceSetup( dev0Idx, srate, framesPerCycle, _cmApFileTestCb, NULL ) != kOkAfdRC ) + goto errLabel; + + char c; + fputs(promptStr,stderr); + fflush(stdin); + + while((c=getchar()) != 'q') + { + switch(c) + { + case '0': cmApFileDeviceStart(dev0Idx); break; + case '1': cmApFileDeviceStop(dev0Idx); break; + } + + fputs(promptStr,stderr); + fflush(stdin); + c = 0; + } + + + + errLabel: + //cmApFileDeviceDestroy(dev0Idx); + + cmApFileFinalize(); + +} diff --git a/cmAudioPortFile.h b/cmAudioPortFile.h new file mode 100644 index 0000000..b2d1a9b --- /dev/null +++ b/cmAudioPortFile.h @@ -0,0 +1,47 @@ +#ifndef cmAudioPortFile_h +#define cmAudioPortFile_h + +#ifdef __cplusplus +extern "C" { +#endif + + + cmApRC_t cmApFileInitialize( cmRpt_t* rpt, unsigned baseApDevIdx ); + cmApRC_t cmApFileFinalize(); + bool cmApFileIsValid(); + + unsigned cmApFileDeviceCreate( + const cmChar_t* devLabel, + const cmChar_t* iFn, + const cmChar_t* oFn, + unsigned oBits, + unsigned oChCnt ); + + cmApRC_t cmApFileDeviceDestroy( unsigned devIdx ); + + unsigned cmApFileDeviceCount(); + const char* cmApFileDeviceLabel( unsigned devIdx ); + unsigned cmApFileDeviceChannelCount( unsigned devIdx, bool inputFl ); + double cmApFileDeviceSampleRate( unsigned devIdx ); + unsigned cmApFileDeviceFramesPerCycle( unsigned devIdx, bool inputFl ); + + cmApRC_t cmApFileDeviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + cmApCallbackPtr_t callbackPtr, + void* userCbPtr ); + + + cmApRC_t cmApFileDeviceStart( unsigned devIdx ); + cmApRC_t cmApFileDeviceStop( unsigned devIdx ); + bool cmApFileDeviceIsStarted( unsigned devIdx ); + void cmApFileReport( cmRpt_t* rpt ); + void cmApFileTest( cmRpt_t* rpt ); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/cmAudioSys.c b/cmAudioSys.c new file mode 100644 index 0000000..03adbbc --- /dev/null +++ b/cmAudioSys.c @@ -0,0 +1,1415 @@ +#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 "cmAudioPortFile.h" +#include "cmApBuf.h" + +#include "cmJson.h" // these files are +#include "dsp/cmDspValue.h" // only required for +#include "dsp/cmDspUi.h" // UI building + +#include "cmThread.h" +#include "cmUdpPort.h" +#include "cmUdpNet.h" +#include "cmMsgProtocol.h" +#include "cmAudioSys.h" +#include "cmMidi.h" +#include "cmMidiPort.h" + +#include "cmMath.h" + +#include // usleep + +cmAudioSysH_t cmAudioSysNullHandle = { NULL }; + +struct cmAs_str; + +typedef struct +{ + struct cmAs_str* p; // pointer to the audio system instance which owns this sub-system + cmAudioSysSubSys_t ss; // sub-system configuration record + cmAudioSysCtx_t ctx; // DSP context + cmAudioSysStatus_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 enableFl; // application controlled pause flag + bool runFl; // false during finalization otherwise true + bool statusFl; // true if regular status notifications should be sent + unsigned audCbLock; // + bool syncInputFl; + + double* iMeterArray; // + double* oMeterArray; // + + unsigned statusUpdateSmpCnt; // transmit a state update msg every statusUpdateSmpCnt samples + unsigned statusUpdateSmpIdx; // state update phase + +} _cmAsCfg_t; + +typedef struct cmAs_str +{ + cmErr_t err; + _cmAsCfg_t* ssArray; + unsigned ssCnt; + unsigned waitAsSubIdx; // index of the next sub-system to try with cmAudioSysIsMsgWaiting(). + cmTsMp1cH_t dthQueH; + bool initFl; // true if the audio system is initialized +} cmAs_t; + + +cmAs_t* _cmAsHandleToPtr( cmAudioSysH_t h ) +{ + cmAs_t* p = (cmAs_t*)h.h; + assert(p != NULL); + return p; +} + +cmAsRC_t _cmAsError( cmAs_t* p, cmAsRC_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. +cmAsRC_t _cmAsEnqueueMsg( cmAs_t* p, cmTsMp1cH_t qH, const void* msgDataPtrArray[], unsigned msgCntArray[], unsigned segCnt, const char* queueLabel ) +{ + cmAsRC_t rc = kOkAsRC; + + 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 _cmAsDspExecCallback(). +cmAsRC_t _cmAsDspToHostMsgCallback(struct cmAudioSysCtx_str* ctx, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt) +{ + cmAs_t* p = (cmAs_t*)ctx->reserved; + assert( ctx->asSubIdx < p->ssCnt ); + //return _cmAsEnqueueMsg(p,p->ssArray[ctx->asSubIdx].dthQueueH,msgDataPtrArray,msgByteCntArray,msgSegCnt,"DSP-to-Host"); + return _cmAsEnqueueMsg(p,p->dthQueH,msgDataPtrArray,msgByteCntArray,msgSegCnt,"DSP-to-Host"); +} + +cmAsRC_t _cmAsHostInitNotify( cmAs_t* p ) +{ + cmAsRC_t rc = kOkAsRC; + + unsigned i; + + for(i=0; issCnt; ++i) + { + cmAudioSysSsInitMsg_t m; + _cmAsCfg_t* cp = p->ssArray + i; + const char* inDevLabel = cp->ss.args.inDevIdx == cmInvalidIdx ? "" : cmApDeviceLabel( cp->ss.args.inDevIdx ); + const char* outDevLabel = cp->ss.args.outDevIdx == cmInvalidIdx ? "" : cmApDeviceLabel( cp->ss.args.outDevIdx ); + + m.asSubIdx = i; + m.selId = kSsInitSelAsId; + m.asSubCnt = p->ssCnt; + m.inDevIdx = cp->ss.args.inDevIdx; + m.outDevIdx = cp->ss.args.outDevIdx; + m.inChCnt = cp->status.iMeterCnt; + m.outChCnt = cp->status.oMeterCnt; + + unsigned segCnt = 3; + const void* msgDataPtrArray[] = { &m, inDevLabel, outDevLabel }; + unsigned msgByteCntArray[] = { sizeof(m), strlen(cmStringNullGuard(inDevLabel))+1, strlen(cmStringNullGuard(outDevLabel))+1 }; + + assert( sizeof(msgDataPtrArray)/sizeof(void*) == segCnt); + assert( sizeof(msgByteCntArray)/sizeof(unsigned) == segCnt); + + if((rc = _cmAsDspToHostMsgCallback(&cp->ctx, msgDataPtrArray, msgByteCntArray, segCnt)) != kOkAsRC ) + return rc; + } + + return rc; +} + +cmAsRC_t _cmAsDispatchNonSubSysMsg( cmAs_t* p, const void* msg, unsigned msgByteCnt ) +{ + cmAsRC_t rc = kOkAsRC; + cmDspUiHdr_t* h = (cmDspUiHdr_t*)msg; + unsigned devIdx = cmAudioSysUiInstIdToDevIndex(h->instId); + unsigned chIdx = cmAudioSysUiInstIdToChIndex(h->instId); + unsigned inFl = cmAudioSysUiInstIdToInFlag(h->instId); + unsigned ctlId = cmAudioSysUiInstIdToCtlId(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 = inFl ? kInApFl : kOutApFl; + + switch( ctlId ) + { + + case kSliderUiAsId: // slider + cmApBufSetGain(devIdx,chIdx, flags, cmDsvGetDouble(&h->value)); + break; + + case kMeterUiAsId: // meter + break; + + case kMuteUiAsId: // mute + flags += cmDsvGetDouble(&h->value) == 0 ? kEnableApFl : 0; + cmApBufEnableChannel(devIdx,chIdx,flags); + break; + + case kToneUiAsId: // tone + flags += cmDsvGetDouble(&h->value) > 0 ? kEnableApFl : 0; + cmApBufEnableTone(devIdx,chIdx,flags); + break; + + case kPassUiAsId: // pass + flags += cmDsvGetDouble(&h->value) > 0 ? kEnableApFl : 0; + cmApBufEnablePass(devIdx,chIdx,flags); + break; + + default: + { assert(0); } + } + + return rc; +} + +// Process a UI msg sent from the host to the audio system +cmAsRC_t _cmAsHandleNonSubSysMsg( cmAs_t* p, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt ) +{ + cmAsRC_t rc = kOkAsRC; + + // if the message is contained in a single segment it can be dispatched immediately ... + if( msgSegCnt == 1 ) + rc = _cmAsDispatchNonSubSysMsg(p,msgDataPtrArray[0],msgByteCntArray[0]); + else + { + // ... otherwise deserialize the message into contiguous memory .... + unsigned byteCnt = 0; + unsigned i; + + for(i=0; ictx.asSubIdx, kStatusSelAsId }; + + 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[] = { &hdr, &cp->status, cp->iMeterArray, cp->oMeterArray }; + unsigned msgByteCntArray[] = { sizeof(hdr), sizeof(cp->status), iMeterByteCnt, oMeterByteCnt }; + unsigned segCnt = sizeof(msgByteCntArray)/sizeof(unsigned); + + _cmAsDspToHostMsgCallback(&cp->ctx,msgDataPtrArray,msgByteCntArray, segCnt ); + + return rc; +} + + +// The DSP execution callback happens through this function. +// This function is only called from inside _cmAsThreadCallback() with the engine mutex locked. +void _cmAsDspExecCallback( _cmAsCfg_t* cp ) +{ + /* + unsigned i; + + // get pointers to a set of audio out buffers - pointers to disabled channels will be set to NULL + cmApBufGet( cp->ss.args.outDevIdx, kOutApFl, cp->ctx.oChArray, cp->ctx.oChCnt ); + cmApBufGet( cp->ss.args.inDevIdx, kInApFl, cp->ctx.iChArray, cp->ctx.iChCnt ); + + // zero the output buffers on all enabled channels + for(i=0; ictx.oChCnt; ++i) + if( cp->ctx.oChArray[i] != NULL ) + memset( cp->ctx.oChArray[i], 0, cp->ss.args.dspFramesPerCycle * sizeof(cmSample_t)); + */ + + // 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 + cp->ctx.audioRateFl = true; + cp->ss.cbFunc( &cp->ctx, 0, NULL ); + cp->ctx.audioRateFl = false; + + // 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 ) + _cmAsSendStateStatusToHost(cp); + } + +} + +// Returns true if audio buffer is has waiting incoming samples and +// available outgoing space. +bool _cmAsBufIsReady( const _cmAsCfg_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 _cmAsRecd.engMutexH locked +cmAsRC_t _cmAsDeliverMsgsWithLock( _cmAsCfg_t* cp ) +{ + int i; + cmAsRC_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 cmAsDeliverMsg(). +// 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 _cmAsThreadCallback(void* arg) +{ + cmAsRC_t rc; + _cmAsCfg_t* cp = (_cmAsCfg_t*)arg; + + // lock the cmAudioSys mutex + if((rc = cmThreadMutexLock(cp->engMutexH)) != kOkAsRC ) + { + _cmAsError(cp->p,rc,"The cmAudioSys thread mutex lock failed."); + return false; + } + + // runFl is always set except during finalization + while( cp->runFl ) + { + + // if the buffer is NOT ready or the cmAudioSys is disabled + if(_cmAsBufIsReady(cp) == false || cp->enableFl==false ) + { + // block on the cond var and unlock the mutex + if( cmThreadMutexWaitOnCondVar(cp->engMutexH,false) != kOkAsRC ) + { + cmThreadMutexUnlock(cp->engMutexH); + _cmAsError(cp->p,rc,"The cmAudioSys 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( cp->enableFl && cp->runFl ) + { + while( _cmAsBufIsReady(cp) ) + { + ++cp->status.audioCbCnt; + + // calling this function results in callbacks to cmAudDsp.c:_cmAdUdpNetCallback() + // which in turn calls cmAudioSysDeliverMsg() which queues any incoming messages + // which are then transferred to the DSP processes by the the call to + // _cmAsDeliverMsgWithLock() below. + cmUdpNetReceive(cp->netH,NULL); + + // if there are msgs waiting to be sent to the DSP process send them. + if( cmTsMp1cMsgWaiting(cp->htdQueueH) ) + _cmAsDeliverMsgsWithLock(cp); + + // make the cmAudioSys callback + _cmAsDspExecCallback( cp ); + + // update the signal time + cp->ctx.begSmpIdx += cp->ss.args.dspFramesPerCycle; + } + } + + } + + // unlock the mutex + cmThreadMutexUnlock(cp->engMutexH); + + return true; +} + + +// This is the audio port callback function. +// +// _cmAudioSysAudioUpdate() 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->audCbLock 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. + +unsigned phase = 0; + +void _cmAudioSysAudioUpdate( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) +{ + _cmAsCfg_t* cp = (_cmAsCfg_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); + + + /* + //fill output with noise + unsigned i = 0,j =0, k = 0, phs = 0; + for(; iaudioBytesPtr; + + phs = a->audioFramesCnt; + + for(j=0; jaudioFramesCnt; ++j) + { + cmApSample_t v = (cmApSample_t)(0.7 * sin(2*M_PI/44100.0 * phase + j )); + + for(k=0; kchCnt; ++k,++dp) + *dp = v; + } + //for(j=0; jaudioFramesCnt*a->chCnt; ++j,++dp) + // *dp = (cmApSample_t)(rand() - (RAND_MAX/2))/(RAND_MAX/2); + + } + + phase += phs; + + return; + */ + + //++p->audCbLock; + + 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 && _cmAsBufIsReady(cp) ) + { + if( cmThreadMutexSignalCondVar(cp->engMutexH) != kOkThRC ) + _cmAsError(cp->p,kMutexErrAsRC,"CmAudioSys signal cond. var. failed."); + + } + //--p->audCbLock; + } + +} + +// Called when MIDI messages arrive from external MIDI ports. +void _cmAudioSysMidiCallback( const cmMidiPacket_t* pktArray, unsigned pktCnt ) +{ + unsigned i; + for(i=0; icbDataPtr); + + if( !cp->runFl ) + continue; + + cmAudioSysH_t asH; + asH.h = cp->p; + + unsigned selId = kMidiMsgArraySelAsId; + const void* msgPtrArray[] = { &cp->ctx.asSubIdx, &selId, &pkt->devIdx, &pkt->portIdx, &pkt->msgCnt, pkt->msgArray }; + unsigned msgByteCntArray[] = { sizeof(cp->ctx.asSubIdx), sizeof(selId), sizeof(pkt->devIdx), sizeof(pkt->portIdx), sizeof(pkt->msgCnt), pkt->msgCnt*sizeof(cmMidiMsg) }; + unsigned msgSegCnt = sizeof(msgByteCntArray)/sizeof(unsigned); + + cmAudioSysDeliverSegMsg(asH,msgPtrArray,msgByteCntArray,msgSegCnt,cmInvalidId); + } + +} + +cmAsRC_t cmAudioSysAllocate( cmAudioSysH_t* hp, cmRpt_t* rpt, const cmAudioSysCfg_t* cfg ) +{ + cmAsRC_t rc; + + if((rc = cmAudioSysFree(hp)) != kOkAsRC ) + return rc; + + cmAs_t* p = cmMemAllocZ( cmAs_t, 1 ); + + cmErrSetup(&p->err,rpt,"Audio System"); + + hp->h = p; + + if( cfg != NULL ) + if((rc = cmAudioSysInitialize( *hp, cfg )) != kOkAsRC ) + cmAudioSysFree(hp); + + return rc; +} + +cmAsRC_t cmAudioSysFree( cmAudioSysH_t* hp ) +{ + cmAsRC_t rc; + + if( hp == NULL || hp->h == NULL ) + return kOkAsRC; + + if((rc = cmAudioSysFinalize(*hp)) != kOkAsRC ) + return rc; + + cmAs_t* p = _cmAsHandleToPtr(*hp); + + cmMemFree(p); + + hp->h = NULL; + + return rc; +} + +cmAsRC_t _cmAudioSysEnable( cmAs_t* p, bool enableFl ) +{ + cmAsRC_t rc; + + unsigned i; + + for(i=0; issCnt; ++i) + { + _cmAsCfg_t* cp = p->ssArray + i; + + if( enableFl ) + { + + //cmApBufPrimeOutput( cp->ss.args.outDevIdx, 2 ); + + // start the input device + if((rc = cmApDeviceStart( cp->ss.args.inDevIdx )) != kOkAsRC ) + return _cmAsError(p,kAudioDevStartFailAsRC,"The audio input device start failed."); + + // start the output device + if( cmApDeviceStart( cp->ss.args.outDevIdx ) != kOkAsRC ) + return _cmAsError(p,kAudioDevStartFailAsRC,"The audio ouput device start failed."); + } + else + { + // stop the input device + if((rc = cmApDeviceStop( cp->ss.args.inDevIdx )) != kOkAsRC ) + return _cmAsError(p,kAudioDevStopFailAsRC,"The audio input device stop failed."); + + // stop the output device + if((rc = cmApDeviceStop( cp->ss.args.outDevIdx )) != kOkAsRC ) + return _cmAsError(p,kAudioDevStopFailAsRC,"The audio output device stop failed."); + + } + + cp->enableFl = enableFl; + } + return kOkAsRC; +} + +cmAsRC_t _cmAudioSysFinalize( cmAs_t* p ) +{ + cmAsRC_t rc = kOkAsRC; + unsigned i; + + // mark the audio system as NOT initialized + p->initFl = false; + + // be sure all audio callbacks are disabled before continuing. + if((rc = _cmAudioSysEnable(p,false)) != kOkAsRC ) + return _cmAsError(p,rc,"Audio system finalize failed because device halting failed."); + + for(i=0; issCnt; ++i) + { + _cmAsCfg_t* cp = p->ssArray + i; + + if( cmThreadIsValid( cp->threadH )) + { + // inform the thread that it should exit + cp->enableFl = false; + cp->runFl = false; + cp->statusFl = false; + + // WARNING: be sure that the audio thread cannot simultaneously signal the + // cond variable from _cmAsAudioUpdate() otherwise the system may crash + + while( cp->audCbLock != 0 ) + { usleep(100000); } + + // signal the cond var to cause the thread to run + if((rc = cmThreadMutexSignalCondVar(cp->engMutexH)) != kOkThRC ) + _cmAsError(p,kMutexErrAsRC,"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 ) + _cmAsError(p,kMutexErrAsRC,"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 ) + _cmAsError(p,kMutexErrAsRC,"Finalize unlock failed."); + + // destroy the thread + if((rc = cmThreadDestroy( &cp->threadH )) != kOkThRC ) + _cmAsError(p,kThreadErrAsRC,"Thread destroy failed."); + + } + + // destroy the mutex + if( cmThreadMutexIsValid(cp->engMutexH) ) + if((rc = cmThreadMutexDestroy( &cp->engMutexH )) != kOkThRC ) + _cmAsError(p,kMutexErrAsRC,"Mutex destroy failed."); + + + // remove the MIDI callback + if( cmMpIsInitialized() && cmMpUsesCallback(-1,-1, _cmAudioSysMidiCallback, cp) ) + if( cmMpRemoveCallback( -1, -1, _cmAudioSysMidiCallback, cp ) != kOkMpRC ) + _cmAsError(p,kMidiSysFailAsRC,"MIDI callback removal failed."); + + // destroy the host-to-dsp msg queue + if( cmTsMp1cIsValid(cp->htdQueueH ) ) + if((rc = cmTsMp1cDestroy( &cp->htdQueueH )) != kOkThRC ) + _cmAsError(p,kTsQueueErrAsRC,"Host-to-DSP msg queue destroy failed."); + + // destroy the dsp-to-host msg queue + if( cmTsMp1cIsValid(p->dthQueH) ) + if((rc = cmTsMp1cDestroy( &p->dthQueH )) != kOkThRC ) + _cmAsError(p,kTsQueueErrAsRC,"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. +cmAsRC_t _cmAsSysValidate( cmErr_t* err, const cmAudioSysCfg_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) + { + cmAudioSysArgs_t* s0 = &cfg->ssArray[j].args; + unsigned devIdx = inputFl ? s0->inDevIdx : s0->outDevIdx; + + for(k=0; kssCnt && devIdx != cmInvalidIdx; ++k) + if( k != j ) + { + cmAudioSysArgs_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,kInvalidArgAsRC,"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,kInvalidArgAsRC,"The device %i is used by different sub-system with different audio buffer parameters.",devIdx); + } + } + } + } + + return kOkAsRC; +} + +cmAsRC_t cmAudioSysInitialize( cmAudioSysH_t h, const cmAudioSysCfg_t* cfg ) +{ + cmAsRC_t rc; + unsigned i; + cmAs_t* p = _cmAsHandleToPtr(h); + + // validate the device setup + if((rc =_cmAsSysValidate(&p->err, cfg )) != kOkAsRC ) + return rc; + + // always finalize before iniitalize + if((rc = cmAudioSysFinalize(h)) != kOkAsRC ) + return rc; + + // create the audio file devices + for(i=0; iafpCnt; ++i) + { + const cmAudioSysFilePort_t* afp = cfg->afpArray + i; + cmApFileDeviceCreate( afp->devLabel, afp->inAudioFn, afp->outAudioFn, afp->oBits, afp->oChCnt ); + } + + p->ssArray = cmMemAllocZ( _cmAsCfg_t, cfg->ssCnt ); + p->ssCnt = cfg->ssCnt; + + for(i=0; issCnt; ++i) + { + _cmAsCfg_t* cp = p->ssArray + i; + const cmAudioSysSubSys_t* ss = cfg->ssArray + i; + + cp->p = p; + cp->ss = *ss; // copy the cfg into the internal audio system state + cp->runFl = false; + cp->enableFl = false; + cp->statusFl = false; + cp->ctx.reserved = p; + cp->ctx.asSubIdx = i; + cp->ctx.ss = &cp->ss; + cp->ctx.begSmpIdx = 0; + cp->ctx.dspToHostFunc = _cmAsDspToHostMsgCallback; + + // validate the input device index + if( ss->args.inDevIdx != cmInvalidIdx && ss->args.inDevIdx >= cmApDeviceCount() ) + { + rc = _cmAsError(p,kAudioDevSetupErrAsRC,"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 = _cmAsError(p,kAudioDevSetupErrAsRC,"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, _cmAudioSysAudioUpdate, cp )) != kOkAsRC ) + { + rc = _cmAsError(p,kAudioDevSetupErrAsRC,"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, _cmAudioSysAudioUpdate, cp )) != kOkAsRC ) + { + rc = _cmAsError(p,kAudioDevSetupErrAsRC,"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 )) != kOkAsRC ) + { + rc = _cmAsError(p,kAudioBufSetupErrAsRC,"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 _cmAsDspExecCallback() + 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 )) != kOkAsRC ) + return _cmAsError(p,kAudioBufSetupErrAsRC,"Audio buffer ouput device setup failed."); + + // setup the output audio buffer ptr array - used to recv output audio from the DSP system in _cmAsDspExecCallback() + 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.asSubIdx = cp->ctx.asSubIdx; + 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, _cmAsThreadCallback, cp, ss->args.rpt )) != kOkThRC ) + { + rc = _cmAsError(p,kThreadErrAsRC,"Thread create failed."); + goto errLabel; + } + + // create the audio System mutex + if((rc = cmThreadMutexCreate( &cp->engMutexH, ss->args.rpt )) != kOkThRC ) + { + rc = _cmAsError(p,kMutexErrAsRC,"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 = _cmAsError(p,kTsQueueErrAsRC,"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 = _cmAsError(p,kTsQueueErrAsRC,"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, _cmAudioSysMidiCallback, cp ) != kOkMpRC ) + { + rc = _cmAsError(p,kMidiSysFailAsRC,"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 = _cmAsError(p,kThreadErrAsRC,"Thread start failed."); + goto errLabel; + } + } + + + _cmAsHostInitNotify(p); + p->initFl = true; + + errLabel: + if( rc != kOkAsRC ) + _cmAudioSysFinalize(p); + + return rc; +} + + +cmAsRC_t cmAudioSysFinalize(cmAudioSysH_t h ) +{ + cmAsRC_t rc = kOkAsRC; + + if( cmAudioSysHandleIsValid(h) == false ) + return rc; + + cmAs_t* p = _cmAsHandleToPtr(h); + + rc = _cmAudioSysFinalize(p); + + h.h = NULL; + + return rc; +} + + +bool cmAudioSysIsInitialized( cmAudioSysH_t h ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + return p->initFl; +} + +cmAsRC_t _cmAudioSysVerifyInit( cmAs_t* p ) +{ + 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( cmErrLastRC(&p->err) != kNotInitAsRC ) + cmErrMsg(&p->err,kNotInitAsRC,"The audio system is not initialized."); + return kNotInitAsRC; + } + + return kOkAsRC; +} + + +bool cmAudioSysIsEnabled( cmAudioSysH_t h ) +{ + if( cmAudioSysIsInitialized(h) == false ) + return false; + + cmAs_t* p = _cmAsHandleToPtr(h); + unsigned i; + for(i=0; issCnt; ++i) + if( p->ssArray[i].enableFl ) + return true; + + return false; +} + + +cmAsRC_t cmAudioSysEnable( cmAudioSysH_t h, bool enableFl ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + return _cmAudioSysEnable(p,enableFl); +} + +cmAsRC_t cmAudioSysDeliverSegMsg( cmAudioSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt, unsigned srcNetNodeId ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + cmAsRC_t rc; + + // the system must be initialized to use this function + if((rc = _cmAudioSysVerifyInit(p)) != kOkAsRC ) + return rc; + + if( msgSegCnt == 0 ) + return kOkAsRC; + + // BUG BUG BUG - there is no reason that both the asSubIdx and the selId must + // be in the first segment but it would be nice. + assert( msgByteCntArray[0] >= 2*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 asSubIdx = array[0]; + unsigned selId = array[1]; + + if( selId == kUiMstrSelAsId ) + return _cmAsHandleNonSubSysMsg( p, msgDataPtrArray, msgByteCntArray, msgSegCnt ); + + if( selId == kNetSyncSelAsId ) + { + assert( msgSegCnt==1); + assert( asSubIdx < p->ssCnt ); + p->ssArray[asSubIdx].ctx.srcNetNodeId = srcNetNodeId; + p->ssArray[asSubIdx].ss.cbFunc(&p->ssArray[asSubIdx].ctx,msgByteCntArray[0],msgDataPtrArray[0]); + return kOkAsRC; + } + + return _cmAsEnqueueMsg(p,p->ssArray[asSubIdx].htdQueueH,msgDataPtrArray,msgByteCntArray,msgSegCnt,"Host-to-DSP"); +} + +cmAsRC_t cmAudioSysDeliverMsg( cmAudioSysH_t h, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ) +{ + const void* msgDataPtrArray[] = { msgPtr }; + unsigned msgByteCntArray[] = { msgByteCnt }; + return cmAudioSysDeliverSegMsg(h,msgDataPtrArray,msgByteCntArray,1,srcNetNodeId); +} + +cmAsRC_t cmAudioSysDeliverIdMsg( cmAudioSysH_t h, unsigned asSubIdx, unsigned id, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ) +{ + cmAsRC_t rc; + cmAs_t* p = _cmAsHandleToPtr(h); + + // the system must be initialized to use this function + if((rc = _cmAudioSysVerifyInit(p)) != kOkAsRC ) + return rc; + + const void* msgDataPtrArray[] = { &asSubIdx, &id, msgPtr }; + unsigned msgByteCntArray[] = { sizeof(asSubIdx), sizeof(id), msgByteCnt }; + return cmAudioSysDeliverSegMsg(h,msgDataPtrArray,msgByteCntArray,3,srcNetNodeId); +} + +unsigned cmAudioSysIsMsgWaiting( cmAudioSysH_t h ) +{ + cmAsRC_t rc; + cmAs_t* p = _cmAsHandleToPtr(h); + + // the system must be initialized to use this function + if((rc = _cmAudioSysVerifyInit(p)) != kOkAsRC ) + return 0; + + unsigned n = 0; + unsigned retByteCnt; + + for(n=0; n < p->ssCnt; ++n ) + { + //if( (retByteCnt = cmTsMp1cDequeueMsgByteCount(p->ssArray[p->waitAsSubIdx].dthQueueH)) > 0 ) + if( (retByteCnt = cmTsMp1cDequeueMsgByteCount(p->dthQueH)) > 0 ) + return retByteCnt; + + p->waitAsSubIdx = (p->waitAsSubIdx + 1) % p->ssCnt; + } + + return 0; +} + +cmAsRC_t cmAudioSysReceiveMsg( cmAudioSysH_t h, void* msgDataPtr, unsigned msgByteCnt ) +{ + cmAsRC_t rc; + cmAs_t* p = _cmAsHandleToPtr(h); + + // the system must be initialized to use this function + if((rc = _cmAudioSysVerifyInit(p)) != kOkAsRC ) + return rc; + + //switch( cmTsMp1cDequeueMsg(p->ssArray[p->waitAsSubIdx].dthQueueH,msgDataPtr,msgByteCnt) ) + switch( cmTsMp1cDequeueMsg(p->dthQueH,msgDataPtr,msgByteCnt) ) + { + case kOkThRC: + p->waitAsSubIdx = (p->waitAsSubIdx + 1) % p->ssCnt; + return kOkAsRC; + + case kBufTooSmallThRC: + return kBufTooSmallAsRC; + + case kBufEmptyThRC: + return kNoMsgWaitingAsRC; + } + + return _cmAsError(p,kTsQueueErrAsRC,"A deque operation failed on the DSP-to-Host message queue."); +} + + +void cmAudioSysStatus( cmAudioSysH_t h, unsigned asSubIdx, cmAudioSysStatus_t* statusPtr ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + + // the system must be initialized to use this function + if( _cmAudioSysVerifyInit(p) != kOkAsRC ) + return; + + if( asSubIdx < p->ssCnt ) + *statusPtr = p->ssArray[asSubIdx].status; +} + +void cmAudioSysStatusNotifyEnable( cmAudioSysH_t h, unsigned asSubIdx, bool enableFl ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + + // the system must be initialized to use this function + if( _cmAudioSysVerifyInit(p) != kOkAsRC ) + return; + + unsigned i = asSubIdx == cmInvalidIdx ? 0 : asSubIdx; + unsigned n = asSubIdx == cmInvalidIdx ? p->ssCnt : asSubIdx+1; + for(; issArray[i].statusFl = enableFl; +} + +bool cmAudioSysHandleIsValid( cmAudioSysH_t h ) +{ return h.h != NULL; } + +cmAudioSysCtx_t* cmAudioSysContext( cmAudioSysH_t h, unsigned asSubIdx ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + + if( _cmAudioSysVerifyInit(p) != kOkAsRC ) + return NULL; + + return &p->ssArray[asSubIdx].ctx; +} + +unsigned cmAudioSysSubSystemCount( cmAudioSysH_t h ) +{ + cmAs_t* p = _cmAsHandleToPtr(h); + if( _cmAudioSysVerifyInit(p) != kOkAsRC ) + return 0; + + return p->ssCnt; +} + +//=========================================================================================================================== +// +// cmAsTest() +// + +/// [cmAudioSysTest] + +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 +} _cmAsTestCbRecd; + +typedef struct +{ + unsigned asSubIdx; // asSubIdx must always be the first field in the msg + unsigned id; // 0 = set DSP Hz, 1 = report cbCount to host + double hz; + unsigned uint; +} _cmAsTestMsg; + + +long _cmAsSynthSine( _cmAsTestCbRecd* 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 _cmAsTestChIdx = 0; + +cmRC_t _cmAsTestCb( void* cbPtr, unsigned msgByteCnt, const void* msgDataPtr ) +{ + cmRC_t rc = cmOkRC; + cmAudioSysCtx_t* ctx = (cmAudioSysCtx_t*)cbPtr; + cmAudioSysSubSys_t* ss = ctx->ss; + _cmAsTestCbRecd* r = (_cmAsTestCbRecd*)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 = _cmAsSynthSine(r, ctx->oChArray[i], 1, ss->args.dspFramesPerCycle ); + } + else + { + if( _cmAsTestChIdx < ctx->oChCnt ) + phs = _cmAsSynthSine(r, ctx->oChArray[_cmAsTestChIdx], 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 + { + _cmAsTestMsg* msg = (_cmAsTestMsg*)msgDataPtr; + + msg->asSubIdx = ctx->asSubIdx; + + switch(msg->id) + { + case 0: + r->hz = msg->hz; + break; + + case 1: + msg->uint = r->cbCnt; + msgByteCnt = sizeof(_cmAsTestMsg); + rc = ctx->dspToHostFunc(ctx,(const void **)&msg,&msgByteCnt,1); + break; + } + + } + + return rc; +} + +// print the usage message for cmAudioPortTest.c +void _cmAsPrintUsage( cmRpt_t* rpt ) +{ +char msg[] = + "cmAudioSysTest() 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 _cmAsGetOpt( 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( cmAudioSysReceiveMsg(h,buf,msgByteCnt) == kOkAsRC ) + { + _cmAsTestMsg* msg = (_cmAsTestMsg*)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 + cmAudioSysEnable(h,false); + + + goto exitLabel; + + errLabel: + printf("AUDIO SYSTEM TEST ERROR\n"); + + exitLabel: + + cmAudioSysFree(&h); + cmApFinalize(); + cmApBufFinalize(); + +} + +/// [cmAudioSysTest] diff --git a/cmAudioSys.h b/cmAudioSys.h new file mode 100644 index 0000000..cd62915 --- /dev/null +++ b/cmAudioSys.h @@ -0,0 +1,298 @@ +/// \file cmAudioSys.h +/// \brief 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 +/// cmAsDspToHostFunc_t provided by cmAudioSysCtx_t. These +/// calls are always made from within an audio system call to +/// audio or control update within cmAsCallback_t. cmAsDspToHostFunc_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 cmAudioSysIsMsgWaiting(). +/// +/// 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 cmAsCallback(). +/// +/// 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 cmAsCallback(). +/// +/// 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 cmAudioSysTest(). +/// \snippet cmAudioSys.c cmAudioSysTest + +#ifndef cmAudioSys_h +#define cmAudioSys_h + +#ifdef __cplusplus +extern "C" { +#endif + + /// Audio system result codes + enum + { + kOkAsRC = cmOkRC, + kThreadErrAsRC, + kMutexErrAsRC, + kTsQueueErrAsRC, + kMsgEnqueueFailAsRC, + kAudioDevSetupErrAsRC, + kAudioBufSetupErrAsRC, + kAudioDevStartFailAsRC, + kAudioDevStopFailAsRC, + kBufTooSmallAsRC, + kNoMsgWaitingAsRC, + kMidiSysFailAsRC, + kMsgSerializeFailAsRC, + kStateBufFailAsRC, + kInvalidArgAsRC, + kNotInitAsRC + }; + + typedef cmHandle_t cmAudioSysH_t; ///< Audio system handle type + typedef unsigned cmAsRC_t; ///< Audio system result code + + struct cmAudioSysCtx_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 cmAudioSysCtx_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. + /// The msgByteCnt argument is set to zero to indicate this type of call. + /// + /// 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 cmAudioSysDeliverMsg() 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 (*cmAsCallback_t)(void* ctxPtr, unsigned msgByteCnt, const void* msgDataPtr ); + + + /// Audio device sub-sytem configuration record + typedef struct + { + 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 cmAudioSysDeliverMsg(). + 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. + } cmAudioSysArgs_t; + + /// Audio sub-system configuration record. + /// This record is provided by the host to configure the audio system + /// via cmAudioSystemAllocate() or cmAudioSystemInitialize(). + typedef struct cmAudioSysSubSys_str + { + cmAudioSysArgs_t args; ///< Audio device configuration + cmAsCallback_t cbFunc; ///< DSP system entry point function. + void* cbDataPtr; ///< Host provided data for the DSP system callback. + } cmAudioSysSubSys_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 cmAsRC_t (*cmAsDspToHostFunc_t)(struct cmAudioSysCtx_str* p, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt); + + /// Informational record passed with each call to the DSP callback function cmAsCallback_t + typedef struct cmAudioSysCtx_str + { + void* reserved; ///< used internally by the system + + bool audioRateFl; + + unsigned srcNetNodeId; ///< + unsigned asSubIdx; ///< index of the sub-system this DSP process is serving + + cmAudioSysSubSys_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 + + cmAsDspToHostFunc_t dspToHostFunc; ///< Callback used by the DSP process to send messages to the host + ///< via the audio system. Returns a cmAsRC_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[]) + + } cmAudioSysCtx_t; + + typedef struct + { + const cmChar_t* devLabel; + const cmChar_t* inAudioFn; + const cmChar_t* outAudioFn; + unsigned oBits; + unsigned oChCnt; + } cmAudioSysFilePort_t; + + + /// Audio system configuration record used by cmAudioSysAllocate(). + typedef struct cmAudioSysCfg_str + { + cmAudioSysSubSys_t* ssArray; ///< sub-system cfg record array + unsigned ssCnt; ///< count of sub-systems + cmAudioSysFilePort_t* afpArray; ///< audio port file cfg recd array + unsigned afpCnt; ///< audio port file cnt + unsigned meterMs; ///< Meter sample period in milliseconds + void* clientCbData; ///< User arg. for clientCbFunc(). + cmTsQueueCb_t clientCbFunc; ///< Called by cmAudioSysReceiveMsg() to deliver internally generated msg's to the host. + /// Set to NULL if msg's will be directly returned by buffers passed to cmAudioSysReceiveMsg(). + cmUdpNetH_t netH; + } cmAudioSysCfg_t; + + extern cmAudioSysH_t cmAudioSysNullHandle; + + /// Allocate and initialize an audio system as a collection of 'cfgCnt' sub-systems. + /// Notes: + /// The audio ports system must be initalized (via cmApInitialize()) prior to calling cmAudioSysAllocate(). + /// The MIDI port system must be initialized (via cmMpInitialize()) prior to calling cmAudioSysAllocate(). + /// Furthermore cmApFinalize() and cmMpFinalize() cannot be called prior to cmAudioSysFree(). + /// See cmAudioSystemTest() for a complete example. + cmAsRC_t cmAudioSysAllocate( cmAudioSysH_t* hp, cmRpt_t* rpt, const cmAudioSysCfg_t* cfg ); + + /// Finalize and release any resources held by the audio system. + cmAsRC_t cmAudioSysFree( cmAudioSysH_t* hp ); + + /// Returns true if 'h' is a handle which was successfully allocated by cmAudioSysAllocate(). + bool cmAudioSysHandleIsValid( cmAudioSysH_t h ); + + /// Reinitialize a previously allocated audio system. This function + /// begins with a call to cmAudioSysFinalize(). + /// Use cmAudioSysEnable(h,true) to begin processing audio following this call. + cmAsRC_t cmAudioSysInitialize( cmAudioSysH_t h, const cmAudioSysCfg_t* cfg ); + + /// Complements cmAudioSysInitialize(). In general there is no need to call this function + /// since calls to cmAudioSysInitialize() and cmAudioSysFree() automaticatically call it. + cmAsRC_t cmAudioSysFinalize( cmAudioSysH_t h ); + + /// Returns true if the audio system has been successfully initialized. + bool cmAudioSysIsInitialized( cmAudioSysH_t ); + + /// Returns true if the audio system is enabled. + bool cmAudioSysIsEnabled( cmAudioSysH_t h ); + + /// Enable/disable the audio system. Enabling the starts audio stream + /// in/out of the system. + cmAsRC_t cmAudioSysEnable( cmAudioSysH_t h, bool enableFl ); + + /// \name 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. + cmAsRC_t cmAudioSysDeliverSegMsg( cmAudioSysH_t h, const void* msgDataPtrArray[], unsigned msgByteCntArray[], unsigned msgSegCnt, unsigned srcNetNodeId ); + + /// Deliver a single message from the host to the DSP system. + cmAsRC_t cmAudioSysDeliverMsg( cmAudioSysH_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. + cmAsRC_t cmAudioSysDeliverIdMsg( cmAudioSysH_t h, unsigned asSubIdx, unsigned id, const void* msgPtr, unsigned msgByteCnt, unsigned srcNetNodeId ); + ///@} + + /// \name 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 + /// cmAudioSysReceiveMsg(). + unsigned cmAudioSysIsMsgWaiting( cmAudioSysH_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 cmAudioSysCfg_t.clientCbFunc. + /// Returns kBufTooSmallAsRC if msgDataPtr[msgByteCnt] is too small to hold the msg. + /// Returns kNoMsgWaitingAsRC if no messages are waiting for delivery or the msg queue is locked by the DSP process. + /// Returns kOkAsRC if a msg was delivered. + /// Call cmAudioSysIsMsgWaiting() prior to calling this function to get + /// the size of the data buffer required to hold the next message. + cmAsRC_t cmAudioSysReceiveMsg( cmAudioSysH_t h, void* msgDataPtr, unsigned msgByteCnt ); + /// @} + + + /// Fill an audio system status record. + void cmAudioSysStatus( cmAudioSysH_t h, unsigned asSubIdx, cmAudioSysStatus_t* statusPtr ); + + /// Enable cmAudioSysStatus_t notifications to be sent periodically to the host. + /// Set asSubIdx to cmInvalidIdx to enable/disable all sub-systems. + /// The notifications occur approximately every cmAudioSysCfg_t.meterMs milliseconds. + void cmAudioSysStatusNotifyEnable( cmAudioSysH_t, unsigned asSubIdx, bool enableFl ); + + /// Return a pointer the context record associated with a sub-system + cmAudioSysCtx_t* cmAudioSysContext( cmAudioSysH_t h, unsigned asSubIdx ); + + /// Return the count of audio sub-systems. + /// This is the same as the count of cfg recds passed to cmAudioSystemInitialize(). + unsigned cmAudioSysSubSystemCount( cmAudioSysH_t h ); + + /// Audio system test and example function. + void cmAudioSysTest( cmRpt_t* rpt, int argc, const char* argv[] ); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmComplexTypes.h b/cmComplexTypes.h new file mode 100644 index 0000000..e35b0db --- /dev/null +++ b/cmComplexTypes.h @@ -0,0 +1,87 @@ +#ifndef cmComplexTypes_h +#define cmComplexTypes_h + +#include +#include + +#if CM_FLOAT_SMP == 1 + +#define cmCabsS cabsf +#define cmCatanS catanf +#define cmCrealS crealf +#define cmCimagS cimagf +#define cmCargS cargf + +#define cmFftPlanAllocS fftwf_plan_dft_r2c_1d +#define cmFft1dPlanAllocS fftwf_plan_dft_1d +#define cmIFftPlanAllocS fftwf_plan_dft_c2r_1d +#define cmFftPlanFreeS fftwf_destroy_plan +#define cmFftMallocS fftwf_malloc +#define cmFftFreeMemS fftwf_free +#define cmFftExecuteS fftwf_execute + + typedef fftwf_plan cmFftPlanS_t; + +#else + +#define cmCabsS cabs +#define cmCatanS catan +#define cmCrealS creal +#define cmCimagS cimag +#define cmCargS carg + +#define cmFftPlanAllocS fftw_plan_dft_r2c_1d +#define cmFft1dPlanAllocS fftw_plan_dft_1d +#define cmIFftPlanAllocS fftw_plan_dft_c2r_1d +#define cmFftPlanFreeS fftw_destroy_plan +#define cmFftMallocS fftw_malloc +#define cmFftFreeMemS fftw_free +#define cmFftExecuteS fftw_execute + + typedef fftw_plan cmFftPlanS_t; + +#endif + +//----------------------------------------------------------------- +//----------------------------------------------------------------- +//----------------------------------------------------------------- + +#if CM_FLOAT_REAL == 1 + +#define cmCabsR cabsf +#define cmCatanR catanf +#define cmCrealR crealf +#define cmCimagR cimagf +#define cmCargR cargf + +#define cmFftPlanAllocR fftwf_plan_dft_r2c_1d +#define cmFft1dPlanAllocR fftwf_plan_dft_1d +#define cmIFftPlanAllocR fftwf_plan_dft_c2r_1d +#define cmFftPlanFreeR fftwf_destroy_plan +#define cmFftMallocR fftwf_malloc +#define cmFftFreeMemR fftwf_free +#define cmFftExecuteR fftwf_execute + + typedef fftwf_plan cmFftPlanR_t; + +#else + +#define cmCabsR cabs +#define cmCatanR catan +#define cmCrealR creal +#define cmCimagR cimag +#define cmCargR carg + +#define cmFftPlanAllocR fftw_plan_dft_r2c_1d +#define cmFft1dPlanAllocR fftw_plan_dft_1d +#define cmIFftPlanAllocR fftw_plan_dft_c2r_1d +#define cmFftPlanFreeR fftw_destroy_plan +#define cmFftMallocR fftw_malloc +#define cmFftFreeMemR fftw_free +#define cmFftExecuteR fftw_execute + + typedef fftw_plan cmFftPlanR_t; + +#endif + +#endif diff --git a/cmCsv.c b/cmCsv.c new file mode 100644 index 0000000..2ef3069 --- /dev/null +++ b/cmCsv.c @@ -0,0 +1,1146 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmLex.h" +#include "cmLinkedHeap.h" +#include "cmSymTbl.h" +#include "cmCsv.h" +#include "cmText.h" + +enum +{ + kCommaLexTId = kUserLexTId+1, + + kMaxLexTId // always last in the lex id list +}; + +typedef struct +{ + const char* text; + unsigned id; +} cmCsvToken_t; + + +cmCsvToken_t _cmCsvTokenArray[] = +{ + { ",", kCommaLexTId }, + { "", kErrorLexTId } +}; + +// The binder linked list contains a list of records which tracks the first +// (left-most) non-blank column in each row. +typedef struct cmCsvBind_str +{ + struct cmCsvBind_str* linkPtr; + cmCsvCell_t* rowPtr; +} cmCsvBind_t; + +typedef struct cmCsvUdef_str +{ + struct cmCsvUdef_str* linkPtr; + unsigned id; +} cmCsvUdef_t; + +typedef struct +{ + cmErr_t err; + void* rptDataPtr; // + cmLexH lexH; // parsing lexer + cmSymTblH_t symTblH; // all XML identifiers and data is stored as a symbol in this table + cmLHeapH_t heapH; + cmCsvBind_t* bindPtr; // base of the binder linked list + cmCsvCell_t* curRowPtr; // used by the parser to track the row being filled + cmCsvUdef_t* udefList; +} cmCsv_t; + +cmCsvH_t cmCsvNullHandle = { NULL }; + +cmCsv_t* _cmCsvHandleToPtr( cmCsvH_t h ) +{ + cmCsv_t* p = (cmCsv_t*)h.h; + assert( p != NULL ); + cmErrClearRC(&p->err); + return p; +} + +cmCsvRC_t _cmCsvVError( cmCsv_t* p, cmCsvRC_t rc, const char* fmt, va_list vl ) +{ return cmErrVMsg(&p->err,rc,fmt,vl); } + +cmCsvRC_t _cmCsvError( cmCsv_t* p, cmCsvRC_t rc, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = _cmCsvVError(p,rc,fmt,vl); + va_end(vl); + return rc; +} + + +cmCsvRC_t cmCsvInitialize( cmCsvH_t *hp, cmCtx_t* ctx ) +{ + cmCsvRC_t rc; + cmCsv_t* p = NULL; + unsigned i; + + if((rc = cmCsvFinalize(hp)) != kOkCsvRC ) + return rc; + + // create the base object + p = cmMemAllocZ( cmCsv_t, 1 ); + assert(p != NULL); + + cmErrSetup(&p->err,&ctx->rpt,"CSV"); + + // create the symbol table + if( cmSymTblIsValid(p->symTblH = cmSymTblCreate(cmSymTblNullHandle,0,ctx)) == false ) + { + rc = _cmCsvError(p,kSymTblErrCsvRC,"Symbol table creation failed."); + goto errLabel; + } + + // allocate the linked heap mgr + if( cmLHeapIsValid(p->heapH = cmLHeapCreate(1024,ctx)) == false ) + { + rc = _cmCsvError(p,kMemAllocErrCsvRC,"Linked heap object allocation failed."); + goto errLabel; + } + + // allocate the lexer + if(cmLexIsValid(p->lexH = cmLexInit(NULL,0,0,&ctx->rpt)) == false ) + { + rc = _cmCsvError(p,kLexErrCsvRC,"Lex allocation failed."); + goto errLabel; + } + + // register CSV specific tokens with the lexer + for(i=0; _cmCsvTokenArray[i].id != kErrorLexTId; ++i) + { + cmRC_t lexRC; + if( (lexRC = cmLexRegisterToken(p->lexH, _cmCsvTokenArray[i].id, _cmCsvTokenArray[i].text )) != kOkLexRC ) + { + rc = _cmCsvError(p,kLexErrCsvRC,"Lex token registration failed for:'%s'.\nLexer Error:%s",_cmCsvTokenArray[i].text, cmLexRcToMsg(lexRC) ); + goto errLabel; + } + } + + hp->h = p; + + return kOkCsvRC; + + errLabel: + + cmMemPtrFree(&p); + + if( cmLHeapIsValid(p->heapH) ) + cmLHeapDestroy(&p->heapH); + + if( cmLexIsValid(p->lexH) ) + cmLexFinal(&p->lexH); + + return rc; + +} +cmCsvRC_t cmCsvFinalize( cmCsvH_t *hp ) +{ + + cmRC_t lexRC; + + if( hp == NULL || hp->h == NULL ) + return kOkCsvRC; + + cmCsv_t* p = _cmCsvHandleToPtr(*hp); + + // free the internal heap object + cmLHeapDestroy( &p->heapH ); + + // free the lexer + if((lexRC = cmLexFinal(&p->lexH)) != kOkLexRC ) + return _cmCsvError(p,kLexErrCsvRC,"Lexer finalization failed.\nLexer Error:%s",cmLexRcToMsg(lexRC)); + + // free the symbol table + cmSymTblDestroy(&p->symTblH); + + // free the handle + cmMemPtrFree(&hp->h); + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvInitializeFromFile( cmCsvH_t *hp, const char* fn, unsigned maxRowCnt, cmCtx_t* ctx ) +{ + cmCsvRC_t rc; + + if((rc = cmCsvInitialize(hp,ctx)) != kOkCsvRC ) + return rc; + + return cmCsvParseFile( *hp, fn, maxRowCnt); +} + + +bool cmCsvIsValid( cmCsvH_t h) +{ return h.h != NULL; } + +cmCsvRC_t cmCsvLastRC( cmCsvH_t h ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + return cmErrLastRC(&p->err); +} + +void cmCsvClearRC( cmCsvH_t h ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + cmErrClearRC(&p->err); +} + + +cmCsvRC_t cmCsvParseFile( cmCsvH_t h, const char* fn, unsigned maxRowCnt ) +{ + cmCsvRC_t rc = kOkCsvRC; + FILE* fp = NULL; + cmCsv_t* p = _cmCsvHandleToPtr(h); + unsigned n = 0; + char* textBuf = NULL; + + assert( fn != NULL && p != NULL ); + + // open the file + if((fp = fopen(fn,"rb")) == NULL ) + return _cmCsvError(p,kFileOpenErrCsvRC,"Unable to open the file:'%s'.",fn); + + // seek to the end + if( fseek(fp,0,SEEK_END) != 0 ) + { + rc= _cmCsvError(p,kFileSeekErrCsvRC,"Unable to seek to the end of '%s'.",fn); + goto errLabel; + } + + // get the length of the file + if( (n=ftell(fp)) == 0 ) + { + rc = _cmCsvError(p,kFileOpenErrCsvRC,"The file '%s' appears to be empty.",fn); + goto errLabel; + } + + // rewind the file + if( fseek(fp,0,SEEK_SET) != 0 ) + { + rc = _cmCsvError(p,kFileSeekErrCsvRC,"Unable to seek to the beginning of '%s'.",fn); + goto errLabel; + } + + // allocate the text buffer + if((textBuf = cmMemAllocZ( char, n+1)) == NULL ) + { + rc = _cmCsvError(p,kMemAllocErrCsvRC,"Unable to allocate the text file buffer for:'%s'.",fn); + goto errLabel; + } + + // read the file into the text buffer + if( fread(textBuf,n,1,fp) != 1 ) + { + rc = _cmCsvError(p,kFileReadErrCsvRC,"File read failed on:'%s'.",fn); + goto errLabel; + } + + rc = cmCsvParse(h,textBuf,n,maxRowCnt); + + errLabel: + + // close the file + if( fclose(fp) != 0 ) + { + rc = _cmCsvError(p,kFileCloseErrCsvRC,"File close failed on:'%s'.",fn); + goto errLabel; + } + + // free the buffer + if( textBuf != NULL ) + cmMemFree(textBuf); + + return rc; + +} + +cmCsvRC_t _cmCsvAppendNewBindRecd( cmCsv_t* p, cmCsvBind_t** newBindPtrPtr, unsigned* newRowIdxPtr ) +{ + cmCsvBind_t* nbp; + cmCsvBind_t* bp = p->bindPtr; + unsigned newRowIdx = 0; + + if( newBindPtrPtr != NULL ) + *newBindPtrPtr = NULL; + + if( newRowIdxPtr != NULL ) + *newRowIdxPtr = cmInvalidIdx; + + // allocate the binder record + if((nbp = cmLHeapAllocZ( p->heapH, sizeof(cmCsvBind_t))) == NULL ) + return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed."); + + // if this is the first binder record + if( p->bindPtr == NULL ) + { + p->bindPtr = nbp; + bp = nbp; + } + else + { + newRowIdx = 1; + + // iterate to the bottom of the binding + while( bp->linkPtr != NULL ) + { + bp = bp->linkPtr; + + ++newRowIdx; + } + + bp->linkPtr = nbp; + } + + if( newBindPtrPtr != NULL ) + *newBindPtrPtr = nbp; + + if( newRowIdxPtr != NULL ) + *newRowIdxPtr = newRowIdx; + + + return kOkCsvRC; +} + +cmCsvRC_t _cmCsvCreateBind( cmCsv_t* p, cmCsvCell_t* cp, const char* tokenText, unsigned lexRow, unsigned lexCol ) +{ + cmCsvRC_t rc; + cmCsvBind_t* nbp; + if((rc = _cmCsvAppendNewBindRecd(p,&nbp,NULL)) != kOkCsvRC ) + return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed for '%s' on line %i column %i.",tokenText,lexRow,lexCol); + + nbp->rowPtr = cp; + + return rc; +} + +cmCsvRC_t _cmCsvAllocCell( cmCsv_t* p, unsigned symId, unsigned flags, unsigned cellRow, unsigned cellCol, cmCsvCell_t** cpp, unsigned lexTId ) +{ + cmCsvCell_t* cp; + + // allocate cell memory + if(( cp = cmLHeapAllocZ(p->heapH, sizeof(cmCsvCell_t) )) == NULL ) + return _cmCsvError(p,kMemAllocErrCsvRC,"Cell allocation failed. for row: %i column %i.",cellRow,cellCol); + + cp->row = cellRow; + cp->col = cellCol; + cp->symId = symId; + cp->flags = flags; + cp->lexTId= lexTId; + + *cpp = cp; + + return kOkCsvRC; +} + +cmCsvRC_t _cmCsvCreateCell( cmCsv_t* p, const char* tokenText, unsigned flags, unsigned cellRow, unsigned cellCol, unsigned lexTId ) +{ + unsigned symId = cmInvalidId; + cmCsvCell_t* cp = NULL; + unsigned lexRow = cmLexCurrentLineNumber(p->lexH); + unsigned lexCol = cmLexCurrentColumnNumber(p->lexH); + cmCsvRC_t rc = kOkCsvRC; + + // register the token text as a symbol + if((symId = cmSymTblRegisterSymbol(p->symTblH,tokenText)) == cmInvalidId ) + return _cmCsvError(p,kSymTblErrCsvRC,"Symbol registration failed. for '%s' on line %i column %i.",tokenText,lexRow,lexCol); + + // allocate a cell + if((rc = _cmCsvAllocCell(p,symId,flags,cellRow,cellCol,&cp,lexTId)) != kOkCsvRC ) + return rc; + + // if this is the first cell in the row + if( p->curRowPtr == NULL ) + { + // link in a new binding record + if((rc = _cmCsvCreateBind( p, cp,tokenText,lexRow,lexCol)) != kOkCsvRC ) + return rc; // this return will result in a leak from the lheap but it is a fatal error so ignore it + + // update the current row ptr + p->curRowPtr = cp; + } + else + { + // iterate to the end of the row + cmCsvCell_t* rp = p->curRowPtr; + while( rp->rowPtr != NULL ) + rp = rp->rowPtr; + + rp->rowPtr = cp; + } + + return rc; + +} + +const cmCsvUdef_t* _cmCsvFindUdef( cmCsv_t* p, unsigned id ) +{ + const cmCsvUdef_t* u = p->udefList; + while( u != NULL ) + { + if( u->id == id ) + return u; + + u = u->linkPtr; + } + + return NULL; +} + +cmCsvRC_t _cmCsvRegUdef( cmCsv_t* p, unsigned id ) +{ + if( _cmCsvFindUdef(p,id) != NULL ) + return _cmCsvError(p,kDuplicateLexCsvId,"%i has already been used as a user defined id.",id); + + cmCsvUdef_t* u = cmLhAllocZ(p->heapH,cmCsvUdef_t,1); + u->id = id; + u->linkPtr = p->udefList; + p->udefList = u; + return kOkCsvRC; +} + +cmCsvRC_t cmCsvLexRegisterToken( cmCsvH_t h, unsigned id, const cmChar_t* token ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + if(cmLexRegisterToken(p->lexH,id,token) != kOkLexRC ) + return _cmCsvError(p, kLexErrCsvRC,"Error registering user defined token '%s'.",cmStringNullGuard(token)); + + return _cmCsvRegUdef(p,id); +} + +cmCsvRC_t cmCsvLexRegisterMatcher( cmCsvH_t h, unsigned id, cmLexUserMatcherPtr_t matchFunc ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + if( cmLexRegisterMatcher(p->lexH,id,matchFunc) != kOkLexRC ) + return _cmCsvError(p, kLexErrCsvRC,"Error registering user defined matching function."); + + return _cmCsvRegUdef(p,id); +} + +unsigned cmCsvLexNextAvailId( cmCsvH_t h ) +{ return kMaxLexTId; } + +cmCsvRC_t cmCsvParse( cmCsvH_t h, const char* buf, unsigned bufCharCnt, unsigned maxRowCnt ) +{ + cmCsvRC_t rc = kOkCsvRC; + unsigned prvLexRow = 1; + unsigned csvRowIdx = 0; + unsigned csvColIdx = 0; + unsigned lexTId = kErrorLexTId; // cur lex token type id + cmCsv_t* p = _cmCsvHandleToPtr(h); + + // assign the text buffer and reset the lexer + if((rc = cmLexSetTextBuffer( p->lexH, buf, bufCharCnt )) != kOkLexRC ) + return _cmCsvError( p, kLexErrCsvRC, "Error setting lexer buffer.\nLexer Error:%s",cmLexRcToMsg(rc)); + + // get the next token + while( (lexTId = cmLexGetNextToken( p->lexH )) != kErrorLexTId && (lexTId != kEofLexTId ) && (rc==kOkCsvRC) && (maxRowCnt==0 || csvRowIdxlexH); + + // if we are starting a new row + if( curLexRow != prvLexRow ) + { + prvLexRow = curLexRow; + ++csvRowIdx; + csvColIdx = 0; + p->curRowPtr = NULL; // force a new binding record + } + + + // copy the token text in a buffer + unsigned n = cmLexTokenCharCount(p->lexH); + char buf[n+1]; + strncpy(buf, cmLexTokenText(p->lexH), n ); + buf[n]=0; + + // do cell data type specific processing + switch( lexTId ) + { + case kCommaLexTId: + ++csvColIdx; + break; + + case kRealLexTId: flags = kRealCsvTFl; break; + case kIntLexTId: flags = kIntCsvTFl; break; + case kHexLexTId: flags = kHexCsvTFl; break; + case kIdentLexTId:flags = kIdentCsvTFl; break; + case kQStrLexTId: flags = kStrCsvTFl; break; + + default: + { + const cmCsvUdef_t* u; + if((u = _cmCsvFindUdef(p,lexTId)) == NULL ) + rc = _cmCsvError(p, kSyntaxErrCsvRC, "Unrecognized token type: '%s' for token: '%s'.",cmLexIdToLabel(p->lexH,lexTId),buf); + else + flags = kStrCsvTFl | kUdefCsvTFl; + } + } + + // if no error occurred and the token is not a comma and the cell is not empty + if( rc == kOkCsvRC && lexTId != kCommaLexTId && strlen(buf) > 0 ) + rc = _cmCsvCreateCell( p, buf,flags, csvRowIdx, csvColIdx, lexTId ); + } + + + return rc; +} + +unsigned cmCsvRowCount( cmCsvH_t h ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + + unsigned rowCnt = 0; + cmCsvBind_t* bp = p->bindPtr; + + for(; bp != NULL; ++rowCnt ) + bp = bp->linkPtr; + + return rowCnt; +} + +cmCsvCell_t* cmCsvRowPtr( cmCsvH_t h, unsigned row ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + cmCsvBind_t* bp = p->bindPtr; + + while( bp != NULL ) + { + // note: bp->rowPtr of blank rows will be NULL + if( bp->rowPtr!=NULL && bp->rowPtr->row == row ) + return bp->rowPtr; + + bp = bp->linkPtr; + } + + return NULL; +} + +cmCsvCell_t* cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + + if((cp = cmCsvRowPtr(h,row)) == NULL ) + return NULL; + + while( cp != NULL ) + { + if( cp->col == col ) + return cp; + + cp = cp->rowPtr; + } + + return NULL; +} + +cmCsvCell_t* _cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + if((cp = cmCsvCellPtr(h,row,col)) == NULL ) + _cmCsvError(_cmCsvHandleToPtr(h),kCellNotFoundCsvRC,"Cell at row:%i col:%i not found.",row,col); + return cp; +} + +const char* cmCsvCellSymText( cmCsvH_t h, unsigned symId ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + const char* cp; + + if((cp = cmSymTblLabel(p->symTblH,symId)) == NULL ) + _cmCsvError(p,kSymTblErrCsvRC,"The text associated with the symbol '%i' was not found.",symId); + + return cp; +} + +cmCsvRC_t cmCsvCellSymInt( cmCsvH_t h, unsigned symId, int* vp ) +{ + const char* cp; + cmCsv_t* p = _cmCsvHandleToPtr(h); + + if((cp = cmCsvCellSymText(h,symId)) == NULL ) + return kSymTblErrCsvRC; + + if( cmTextToInt(cp,vp,&p->err) != kOkTxRC ) + return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to int value failed."); + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvCellSymUInt( cmCsvH_t h, unsigned symId, unsigned* vp ) +{ + const char* cp; + cmCsv_t* p = _cmCsvHandleToPtr(h); + + if((cp = cmCsvCellSymText(h,symId)) == NULL ) + return kSymTblErrCsvRC; + + if( cmTextToUInt(cp,vp,&p->err) != kOkTxRC ) + return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to uint value failed."); + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvCellSymFloat( cmCsvH_t h, unsigned symId, float* vp ) +{ + const char* cp; + cmCsv_t* p = _cmCsvHandleToPtr(h); + + if((cp = cmCsvCellSymText(h,symId)) == NULL ) + return kSymTblErrCsvRC; + + if( cmTextToFloat(cp,vp,&p->err) != kOkTxRC ) + return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to float value failed."); + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvCellSymDouble( cmCsvH_t h, unsigned symId, double* vp ) +{ + const char* cp; + cmCsv_t* p = _cmCsvHandleToPtr(h); + + if((cp = cmCsvCellSymText(h,symId)) == NULL ) + return kSymTblErrCsvRC; + + if( cmTextToDouble(cp,vp,&p->err) != kOkTxRC ) + return _cmCsvError(p,kDataCvtErrCsvRC,"CSV text to double value failed."); + + return kOkCsvRC; +} + +const char* cmCsvCellText( cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + if((cp = cmCsvCellPtr(h,row,col)) == NULL ) + return NULL; + + return cmCsvCellSymText(h,cp->symId); +} + +int cmCsvCellInt( cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + int v; + + if((cp = cmCsvCellPtr(h,row,col)) == NULL ) + return INT_MAX; + + if(cmCsvCellSymInt(h,cp->symId,&v) != kOkCsvRC ) + return INT_MAX; + + return v; +} + +unsigned cmCsvCellUInt( cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + unsigned v; + + if((cp = cmCsvCellPtr(h,row,col)) == NULL ) + return UINT_MAX; + + if(cmCsvCellSymUInt(h,cp->symId,&v) != kOkCsvRC ) + return UINT_MAX; + + return v; +} + +float cmCsvCellFloat( cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + float v; + + if((cp = cmCsvCellPtr(h,row,col)) == NULL ) + return FLT_MAX; + + if(cmCsvCellSymFloat(h,cp->symId,&v) != kOkCsvRC ) + return FLT_MAX; + + return v; +} + +double cmCsvCellDouble(cmCsvH_t h, unsigned row, unsigned col ) +{ + cmCsvCell_t* cp; + double v; + + if((cp = cmCsvCellPtr(h,row,col)) == NULL ) + return DBL_MAX; + + if(cmCsvCellSymDouble(h,cp->symId,&v) != kOkCsvRC ) + return DBL_MAX; + + return v; +} + + + +unsigned cmCsvInsertSymText( cmCsvH_t h, const char* text ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + unsigned symId; + + if((symId = cmSymTblRegisterSymbol(p->symTblH,text)) == cmInvalidId ) + _cmCsvError(p,kSymTblErrCsvRC,"'%s' could not be inserted into the symbol table.",text); + + return symId; +} + +unsigned cmCsvInsertSymInt( cmCsvH_t h, int v ) +{ + const char* fmt = "%i"; + unsigned n = snprintf(NULL,0,fmt,v)+1; + char buf[n]; + + buf[0]= 0; + if( snprintf(buf,n,fmt,v) == n-1 ) + return cmCsvInsertSymText(h,buf); + + _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The integer %i could not be converted to text.",v); + return cmInvalidId; +} + +unsigned cmCsvInsertSymUInt( cmCsvH_t h, unsigned v ) +{ + const char* fmt = "%i"; + unsigned n = snprintf(NULL,0,fmt,v)+1; + char buf[n]; + + buf[0]= 0; + if( snprintf(buf,n,fmt,v) == n-1 ) + return cmCsvInsertSymText(h,buf); + + _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The unsigned int %i could not be converted to text.",v); + return cmInvalidId; +} + +unsigned cmCsvInsertSymFloat( cmCsvH_t h, float v ) +{ + const char* fmt = "%f"; + unsigned n = snprintf(NULL,0,fmt,v)+1; + char buf[n]; + + buf[0] = 0; + if( snprintf(buf,n,fmt,v) == n-1 ) + return cmCsvInsertSymText(h,buf); + + _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The float %f could not be converted to text.",v); + return cmInvalidId; +} + +unsigned cmCsvInsertSymDouble( cmCsvH_t h, double v ) +{ + const char* fmt = "%f"; + unsigned n = snprintf(NULL,0,fmt,v)+1; + char buf[n]; + + buf[0]= 0; + if( snprintf(buf,n,fmt,v) == n-1 ) + return cmCsvInsertSymText(h,buf); + + _cmCsvError(_cmCsvHandleToPtr(h),kDataCvtErrCsvRC,"The double %f could not be converted to text.",v); + return cmInvalidId; + +} + +cmCsvRC_t cmCsvSetCellText( cmCsvH_t h, unsigned row, unsigned col, const char* text ) +{ + cmCsvCell_t* cp; + unsigned symId; + + if((cp = _cmCsvCellPtr(h,row,col)) == NULL ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + if((symId = cmCsvInsertSymText(h,text)) == cmInvalidId ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + cp->symId = symId; + cp->flags &= !kTypeTMask; + cp->flags |= kStrCsvTFl; + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvSetCellInt( cmCsvH_t h, unsigned row, unsigned col, int v ) +{ + cmCsvCell_t* cp; + unsigned symId; + + if((cp = _cmCsvCellPtr(h,row,col)) == NULL ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + if((symId = cmCsvInsertSymInt(h,v)) == cmInvalidId ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + cp->symId = symId; + cp->flags &= !kTypeTMask; + cp->flags |= kIntCsvTFl; + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvSetCellUInt( cmCsvH_t h, unsigned row, unsigned col, unsigned v ) +{ + cmCsvCell_t* cp; + unsigned symId; + + if((cp = _cmCsvCellPtr(h,row,col)) == NULL ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + if((symId = cmCsvInsertSymUInt(h,v)) == cmInvalidId ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + cp->symId = symId; + cp->flags &= !kTypeTMask; + cp->flags |= kIntCsvTFl; + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvSetCellFloat( cmCsvH_t h, unsigned row, unsigned col, float v ) +{ + cmCsvCell_t* cp; + unsigned symId; + + if((cp = _cmCsvCellPtr(h,row,col)) == NULL ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + if((symId = cmCsvInsertSymFloat(h,v)) == cmInvalidId ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + cp->symId = symId; + cp->flags &= !kTypeTMask; + cp->flags |= kRealCsvTFl; + + return kOkCsvRC; +} + +cmCsvRC_t cmCsvSetCellDouble( cmCsvH_t h, unsigned row, unsigned col, double v ) +{ + cmCsvCell_t* cp; + unsigned symId; + + if((cp = _cmCsvCellPtr(h,row,col)) == NULL ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + if((symId = cmCsvInsertSymDouble(h,v)) == cmInvalidId ) + return cmErrLastRC(&_cmCsvHandleToPtr(h)->err); + + cp->symId = symId; + cp->flags &= !kTypeTMask; + cp->flags |= kRealCsvTFl; + + return kOkCsvRC; +} + + +cmCsvRC_t cmCsvInsertRowBefore( cmCsvH_t h, unsigned row, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId ) +{ + cmCsvRC_t rc = kOkCsvRC; + cmCsv_t* p = _cmCsvHandleToPtr(h); + cmCsvBind_t* bp = p->bindPtr; + cmCsvBind_t* nbp = NULL; + unsigned i = 0; + + if( cellPtrPtr != NULL ) + *cellPtrPtr = NULL; + + // allocate the binder record + if((nbp = cmLHeapAllocZ( p->heapH, sizeof(cmCsvBind_t))) == NULL ) + return _cmCsvError(p,kMemAllocErrCsvRC,"Binding record allocation failed row %i column %i.",row,0); + + // if a new first row is being inserted + if( row == 0 ) + { + bp = p->bindPtr; + nbp->linkPtr = p->bindPtr; + p->bindPtr = nbp; + } + else + { + bp = p->bindPtr; + + // iterate to the row before the new row + for(i=0; bp != NULL; ++i ) + { + if( i == (row-1) || bp->linkPtr == NULL ) + break; + + bp = bp->linkPtr; + } + + assert( bp != NULL ); + + nbp->linkPtr = bp->linkPtr; + bp->linkPtr = nbp; + bp = bp->linkPtr; + } + + // update the row numbers in all cells below the new row + while( bp != NULL ) + { + cmCsvCell_t* cp = bp->rowPtr; + while( cp != NULL ) + { + cp->row += 1; + cp = cp->rowPtr; + } + bp = bp->linkPtr; + } + + // allocate the first cell in the new row + if(cellPtrPtr != NULL && symId != cmInvalidId ) + { + if((rc = _cmCsvAllocCell(p, symId, flags, row, 0, cellPtrPtr, lexTId )) != kOkCsvRC ) + return rc; + + nbp->rowPtr = *cellPtrPtr; + } + return rc; +} + +cmCsvRC_t cmCsvAppendRow( cmCsvH_t h, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId ) +{ + cmCsvRC_t rc = kOkCsvRC; + cmCsv_t* p = _cmCsvHandleToPtr(h); + cmCsvBind_t* nbp = NULL; + unsigned newRowIdx = cmInvalidIdx; + + if( cellPtrPtr != NULL ) + *cellPtrPtr = NULL; + + if((rc = _cmCsvAppendNewBindRecd(p,&nbp,&newRowIdx)) != kOkCsvRC ) + return rc; + + + // allocate the first cell in the new row + if(cellPtrPtr != NULL && symId != cmInvalidId ) + { + if((rc = _cmCsvAllocCell(p, symId, flags, newRowIdx, 0, cellPtrPtr, lexTId )) != kOkCsvRC ) + return rc; + + nbp->rowPtr = *cellPtrPtr; + } + + return rc; +} + +cmCsvRC_t cmCsvInsertColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId ) +{ + cmCsvRC_t rc = kOkCsvRC; + cmCsvCell_t* ncp = NULL; + cmCsvCell_t* cp = NULL; + cmCsv_t* p = _cmCsvHandleToPtr(h); + unsigned col; + + if(cellPtrPtr != NULL ) + *cellPtrPtr = NULL; + + // allocate the new cell + if((rc = _cmCsvAllocCell(p, symId, flags, leftCellPtr->row, leftCellPtr->col+1, &ncp, lexTId )) != kOkCsvRC ) + return rc; + + // update the col values of cells to right of new cell + cp = leftCellPtr->rowPtr; + col = leftCellPtr->col + 1; + + for(; cp != NULL; ++col ) + { + // don't update any col numbers after a blank (missing) column + if( cp->col == col ) + cp->col += 1; + + cp = cp->rowPtr; + } + + // link in the new cell + ncp->rowPtr = leftCellPtr->rowPtr; + leftCellPtr->rowPtr = ncp; + + if(cellPtrPtr != NULL ) + *cellPtrPtr = ncp; + + return rc; + +} + +cmCsvRC_t cmCsvInsertTextColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, const char* text, unsigned lexTId ) +{ + cmCsvRC_t rc; + cmCsvCell_t* ncp; + + if( cellPtrPtr != NULL ) + cellPtrPtr = NULL; + + if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC ) + if((rc = cmCsvSetCellText(h, ncp->row, ncp->col, text )) == kOkCsvRC ) + if( cellPtrPtr != NULL ) + *cellPtrPtr = ncp; + + return rc; +} + +cmCsvRC_t cmCsvInsertIntColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, int val, unsigned lexTId ) +{ + cmCsvRC_t rc; + cmCsvCell_t* ncp; + + if( cellPtrPtr != NULL ) + cellPtrPtr = NULL; + + if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC ) + if((rc = cmCsvSetCellInt(h, ncp->row, ncp->col, val )) == kOkCsvRC ) + if( cellPtrPtr != NULL ) + *cellPtrPtr = ncp; + + return rc; +} + +cmCsvRC_t cmCsvInsertUIntColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned val, unsigned lexTId ) +{ + cmCsvRC_t rc; + cmCsvCell_t* ncp; + + if( cellPtrPtr != NULL ) + cellPtrPtr = NULL; + + if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC ) + if((rc = cmCsvSetCellUInt(h, ncp->row, ncp->col, val )) == kOkCsvRC ) + if( cellPtrPtr != NULL ) + *cellPtrPtr = ncp; + + return rc; +} + +cmCsvRC_t cmCsvInsertFloatColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, float val, unsigned lexTId ) +{ + cmCsvRC_t rc; + cmCsvCell_t* ncp; + + if( cellPtrPtr != NULL ) + cellPtrPtr = NULL; + + if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC ) + if((rc = cmCsvSetCellFloat(h, ncp->row, ncp->col, val )) == kOkCsvRC ) + if( cellPtrPtr != NULL ) + *cellPtrPtr = ncp; + + return rc; +} + +cmCsvRC_t cmCsvInsertDoubleColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, double val, unsigned lexTId ) +{ + cmCsvRC_t rc; + cmCsvCell_t* ncp; + + if( cellPtrPtr != NULL ) + cellPtrPtr = NULL; + + if((rc = cmCsvInsertColAfter(h, leftCellPtr, &ncp, cmInvalidId, 0, lexTId )) == kOkCsvRC ) + if((rc = cmCsvSetCellDouble(h, ncp->row, ncp->col, val )) == kOkCsvRC ) + if( cellPtrPtr != NULL ) + *cellPtrPtr = ncp; + + return rc; +} + + +cmCsvRC_t cmCsvWrite( cmCsvH_t h, const char* fn ) +{ + FILE* fp = NULL; + cmCsvRC_t rc = kOkCsvRC; + cmCsv_t* p = _cmCsvHandleToPtr(h); + cmCsvBind_t* bp = p->bindPtr; + + if((fp = fopen(fn,"wt")) == NULL ) + return _cmCsvError(p,kFileCreateErrCsvRC,"Unable to create the output CSV file:'%s'.",fn); + + bp = p->bindPtr; + + // for each row + while( bp != NULL ) + { + cmCsvCell_t* cp = bp->rowPtr; + unsigned col = 0; + + // for each column + for(; cp != NULL; ++col ) + { + // skip blank columns + if( cp->col == col ) + { + const char* tp; + + if((tp = cmSymTblLabel(p->symTblH,cp->symId)) == NULL ) + return _cmCsvError(p,kSymTblErrCsvRC,"Unable to locate the symbol text for cell at row:%i col:%i.",cp->row,cp->col); + + if( cmIsFlag(cp->flags,kTextTMask) ) + fprintf(fp,"\""); + + fputs(tp,fp); + + if( cmIsFlag(cp->flags,kTextTMask) ) + fprintf(fp,"\""); + + cp = cp->rowPtr; + } + + if( cp == NULL ) + fprintf(fp,"\n"); // end of row + else + fprintf(fp,","); // between columns + } + + bp = bp->linkPtr; + } + + fclose(fp); + + return rc; +} + +cmCsvRC_t cmCsvPrint( cmCsvH_t h, unsigned rowCnt ) +{ + cmCsv_t* p = _cmCsvHandleToPtr(h); + cmCsvBind_t* bp = p->bindPtr; + unsigned i; + + for(i=0; bp!=NULL && ilinkPtr) + { + cmCsvCell_t* cp = bp->rowPtr; + unsigned j; + + for(j=0; cp!=NULL; ++j) + { + if( cp->col == j ) + { + const char* tp; + + if((tp = cmSymTblLabel(p->symTblH,cp->symId)) == NULL ) + _cmCsvError(p,kSymTblErrCsvRC,"The text associated with the symbol '%i' was not found.",cp->symId); + + fputs(tp,stdin); + } + + cp=cp->rowPtr; + if( cp == NULL ) + printf("\n"); + else + printf(","); + + } + } + return kOkCsvRC; +} diff --git a/cmCsv.h b/cmCsv.h new file mode 100644 index 0000000..d3b7b06 --- /dev/null +++ b/cmCsv.h @@ -0,0 +1,149 @@ +#ifndef cmCsv_h +#define cmCsv_h + + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkCsvRC = 0, + kMemAllocErrCsvRC, + kLexErrCsvRC, + kSymTblErrCsvRC, + kSyntaxErrCsvRC, + kFileOpenErrCsvRC, + kFileCreateErrCsvRC, + kFileReadErrCsvRC, + kFileSeekErrCsvRC, + kFileCloseErrCsvRC, + kDataCvtErrCsvRC, + kCellNotFoundCsvRC, + kDuplicateLexCsvId + }; + + typedef unsigned cmCsvRC_t; + typedef cmHandle_t cmCsvH_t; + + enum + { + kIntCsvTFl = 0x01, + kHexCsvTFl = 0x02, + kRealCsvTFl = 0x04, + kIdentCsvTFl = 0x08, + kStrCsvTFl = 0x10, + kUdefCsvTFl = 0x20, + + kNumberTMask = kIntCsvTFl | kHexCsvTFl | kRealCsvTFl, + kTextTMask = kIdentCsvTFl | kStrCsvTFl, + kTypeTMask = kNumberTMask | kTextTMask + }; + + // Each non-blank CSV cell is represented by a cmCsvCell_t record. + // All the non-blank cells in a given row are organized as a linked + // list throught 'rowPtr'. + typedef struct cmCsvCell_str + { + unsigned row; // CSV row number + unsigned col; // CSV column number + struct cmCsvCell_str* rowPtr; // links together cells in this row + + unsigned symId; // symbol id for this cell + unsigned flags; // cell flags (see kXXXCsvTFl) + unsigned lexTId; + + } cmCsvCell_t; + + extern cmCsvH_t cmCsvNullHandle; + + cmCsvRC_t cmCsvInitialize( cmCsvH_t *hp, cmCtx_t* ctx ); + cmCsvRC_t cmCsvFinalize( cmCsvH_t *hp ); + + cmCsvRC_t cmCsvInitializeFromFile( cmCsvH_t *hp, const char* fn, unsigned maxRowCnt, cmCtx_t* ctx ); + + bool cmCsvIsValid( cmCsvH_t h); + cmCsvRC_t cmCsvLastRC( cmCsvH_t h); + void cmCsvClearRC( cmCsvH_t h); + + // Register token matchers. See cmLexRegisterToken and cmLexRegisterMatcher + // for details. + cmCsvRC_t cmCsvLexRegisterToken( cmCsvH_t h, unsigned id, const cmChar_t* token ); + cmCsvRC_t cmCsvLexRegisterMatcher( cmCsvH_t h, unsigned id, cmLexUserMatcherPtr_t funcPtr ); + + // Return the next available lexer token id above the token id's used internally + // by the object. This value is fixed after cmCsvInitialize() + // and does not change for the life of the CSV object. The application is + // therefore free to choose any lexer id values equal to or above the + // returned value. + unsigned cmCsvLexNextAvailId( cmCsvH_t h ); + + // Set 'maxRowCnt' to 0 if there is no row limit on the file. + cmCsvRC_t cmCsvParse( cmCsvH_t h, const char* buf, unsigned bufCharCnt, unsigned maxRowCnt ); + cmCsvRC_t cmCsvParseFile( cmCsvH_t h, const char* fn, unsigned maxRowCnt ); + + unsigned cmCsvRowCount( cmCsvH_t h ); + + // Return a pointer to a given cell. + cmCsvCell_t* cmCsvCellPtr( cmCsvH_t h, unsigned row, unsigned col ); + + // Return a pointer to the first cell in a given row + cmCsvCell_t* cmCsvRowPtr( cmCsvH_t h, unsigned row ); + + + // Convert a cell symbold id to a value. + const char* cmCsvCellSymText( cmCsvH_t h, unsigned symId ); + cmCsvRC_t cmCsvCellSymInt( cmCsvH_t h, unsigned symId, int* vp ); + cmCsvRC_t cmCsvCellSymUInt( cmCsvH_t h, unsigned symId, unsigned* vp ); + cmCsvRC_t cmCsvCellSymFloat( cmCsvH_t h, unsigned symId, float* vp ); + cmCsvRC_t cmCsvCellSymDouble( cmCsvH_t h, unsigned symId, double* vp ); + + // Return the value associated with a cell. + const char* cmCsvCellText( cmCsvH_t h, unsigned row, unsigned col ); // Returns NULL on error. + int cmCsvCellInt( cmCsvH_t h, unsigned row, unsigned col ); // Returns INT_MAX on error. + unsigned cmCsvCellUInt( cmCsvH_t h, unsigned row, unsigned col ); // Returns UINT_MAX on error. + float cmCsvCellFloat( cmCsvH_t h, unsigned row, unsigned col ); // Returns FLT_MAX on error. + double cmCsvCellDouble(cmCsvH_t h, unsigned row, unsigned col ); // Returns DBL_MAX on error. + + // Insert a value into the internal symbol table. + unsigned cmCsvInsertSymText( cmCsvH_t h, const char* text ); + unsigned cmCsvInsertSymInt( cmCsvH_t h, int v ); + unsigned cmCsvInsertSymUInt( cmCsvH_t h, unsigned v ); + unsigned cmCsvInsertSymFloat( cmCsvH_t h, float v ); + unsigned cmCsvInsertSymDouble( cmCsvH_t h, double v ); + + // Set the value associated with a cell. + cmCsvRC_t cmCsvSetCellText( cmCsvH_t h, unsigned row, unsigned col, const char* text ); + cmCsvRC_t cmCsvSetCellInt( cmCsvH_t h, unsigned row, unsigned col, int v ); + cmCsvRC_t cmCsvSetCellUInt( cmCsvH_t h, unsigned row, unsigned col, unsigned v ); + cmCsvRC_t cmCsvSetCellFloat( cmCsvH_t h, unsigned row, unsigned col, float v ); + cmCsvRC_t cmCsvSetCellDouble( cmCsvH_t h, unsigned row, unsigned col, double v ); + + // Insert a new row and column 0 cell just above the row assigned to 'row'. + // lexTId is an arbitrary id used by the application to set the value of + // cmCsvCell_t.lexTId in the new cell. There are no constraints on its value. + //cmCsvRC_t cmCsvInsertRowBefore( cmCsvH_t h, unsigned row, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId ); + + cmCsvRC_t cmCsvAppendRow( cmCsvH_t h, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId ); + + // Insert a new cell to the right of leftCellPtr. + // lexTId is an arbitrary id used by the application to set the value of + // cmCsvCell_t.lexTId in the new cell. There are no constraints on its value. + cmCsvRC_t cmCsvInsertColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned symId, unsigned flags, unsigned lexTId ); + + cmCsvRC_t cmCsvInsertTextColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, const char* val, unsigned lexTId ); + cmCsvRC_t cmCsvInsertIntColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, int val, unsigned lexTId ); + cmCsvRC_t cmCsvInsertUIntColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, unsigned val, unsigned lexTId ); + cmCsvRC_t cmCsvInsertFloatColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, float val, unsigned lexTId ); + cmCsvRC_t cmCsvInsertDoubleColAfter( cmCsvH_t h, cmCsvCell_t* leftCellPtr, cmCsvCell_t** cellPtrPtr, double val, unsigned lexTId ); + + // Write the CSV object out to a file. + cmCsvRC_t cmCsvWrite( cmCsvH_t h, const char* fn ); + + cmCsvRC_t cmCsvPrint( cmCsvH_t h, unsigned rowCnt ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmCtx.c b/cmCtx.c new file mode 100644 index 0000000..acdfc7c --- /dev/null +++ b/cmCtx.c @@ -0,0 +1,22 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" + + void cmCtxSetup( + cmCtx_t* ctx, + const cmChar_t* title, + cmRptPrintFunc_t prtFunc, + cmRptPrintFunc_t errFunc, + void* cbPtr, + unsigned guardByteCnt, + unsigned alignByteCnt, + unsigned mmFlags ) + { + cmRptSetup(&ctx->rpt,prtFunc,errFunc,cbPtr); + cmErrSetup(&ctx->err,&ctx->rpt,title); + ctx->guardByteCnt = guardByteCnt; + ctx->alignByteCnt = alignByteCnt; + ctx->mmFlags = mmFlags; + } diff --git a/cmCtx.h b/cmCtx.h new file mode 100644 index 0000000..bc41128 --- /dev/null +++ b/cmCtx.h @@ -0,0 +1,53 @@ +//{ +//( +// cmCtx_t is used to hold application supplied cmRpt_t, cmErr_t and +// other global values for easy distribution throughtout a cm based application. +// +// Most the cm components need at least an application supplied cmRpt_t function +// to initialize their own internal cmErr_t error class. Likewise classes which +// use a cmLHeapH_t based internal heap manager require application wide memory +// manager configuration information. The cmCtx_t packages this information and +// allows it to be easily distributed. The applicaton and its constituent objects +// then need only maintain and pass pointers to a single cmCtx_t object to have access to +// this all the global program information. +//) + +#ifndef cmCtx_h +#define cmCtx_h + +#ifdef __cplusplus +extern "C" { +#endif + + //( + + // cmCtx_t data type. +typedef struct +{ + cmRpt_t rpt; //< Application supplied global reporter. This reporter is also use by \ref err. + cmErr_t err; //< Application error reporter which can be used to report errors prior to the client object being initialized to the point where it can use it's own cmErr_t. + unsigned guardByteCnt; //< Guard byte count in use by \ref cmMallocDebug.h . + unsigned alignByteCnt; //< Align byte count used by the \ref cmMallocDebug.h + unsigned mmFlags; //< Initialization flags used by \ref cmMallocDebug.h. + void* userDefPtr; //< Application defined pointer. +} cmCtx_t; + + // cmCtx_t initialization function. + void cmCtxSetup( + cmCtx_t* ctx, //< The cmCtx_t to initialize. + const cmChar_t* title, //< The cmCtx_t error label. See cmErrSetup(). + cmRptPrintFunc_t prtFunc, //< The printFunc() to assign to the cmCtx_t.rpt. + cmRptPrintFunc_t errFunc, //< The errFunc() to assign to cmCtx_t.rpt. + void* cbPtr, //< Callback data to use with prtFunc() and errFunc(). + unsigned guardByteCnt,//< Guard byte count used to configure \ref cmMallocDebug.h + unsigned alignByteCnt,//< Align byte count used to configure \ref cmMallocDebug.h + unsigned mmFlags //< Initialization flags used to configure \ref cmMallocDebug.h + ); + //) + //} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmDocMain.h b/cmDocMain.h new file mode 100644 index 0000000..78f9263 --- /dev/null +++ b/cmDocMain.h @@ -0,0 +1,54 @@ +/*! \mainpage cm Manual + + To modify this page edit cmDocMain.h + + \section building Building + \subsection debug_mode Debug/Release Compile Mode + By default the project builds in debug mode. To build in release mode define NDEBUG + on the compiler command line. The existence of NDEBUG is tested in cmGlobal.h and + the value of the preprocessor variable #cmDEBUG_FL is set to 0 if NDEBUG was defined + and 1 otherwise. Code which depends on the debug/release mode then tests the value of + #cmDEBUG_FL. + + + The cm library is a set of C routines for working audio signals. + + \section foundation Foundation + \subsection mem Memory Management + \subsection output Output and Error Reporting + \subsection files File Management + \subsection cfg Program Configuration and Data + + + + \subsection step1 Step 1: Opening the box + + */ + + +/*! + + +\defgroup base Base +@{ + +@} + +\defgroup rt Real-time +@{ +@} + +\defgroup audio Audio +@{ + +@} + +\defgroup dsp Signal Processing +@{ +@} + +\defgroup gr Graphics +@{ +@} + + */ diff --git a/cmErr.c b/cmErr.c new file mode 100644 index 0000000..27c1a25 --- /dev/null +++ b/cmErr.c @@ -0,0 +1,137 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" + +void cmErrSetup( cmErr_t* err, cmRpt_t* rpt, const cmChar_t* label ) +{ + err->rpt = rpt; + err->label = label; + err->rc = cmOkRC; +} + +void cmErrClone( cmErr_t* dstErr, const cmErr_t* srcErr ) +{ memcpy(dstErr,srcErr,sizeof(*dstErr)); } + +void _cmErrVMsg(cmErr_t* err, bool warnFl, cmRC_t rc, const cmChar_t* fmt, va_list vl ) +{ + if( err->rpt == NULL ) + return; + + const cmChar_t* hdrFmt = warnFl ? "%s warning: " : "%s error: "; + const cmChar_t* codeFmt = " (RC:%i)"; + + int n0 = snprintf( NULL,0,hdrFmt,cmStringNullGuard(err->label)); + int n1 = vsnprintf(NULL,0,fmt,vl); + int n2 = snprintf( NULL,0,codeFmt,rc); + int n = n0+n1+n2+1; + cmChar_t s[n]; + n0 = snprintf(s,n,hdrFmt,cmStringNullGuard(err->label)); + n0 += vsnprintf(s+n0,n-n0,fmt,vl); + n0 += snprintf(s+n0,n-n0,codeFmt,rc); + assert(n0 <= n ); + cmRptErrorf(err->rpt,"%s\n",s); +} + +void _cmErrMsg( cmErr_t* err, bool warnFl, cmRC_t rc, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + _cmErrVMsg(err,warnFl,rc,fmt,vl); + va_end(vl); +} + +cmRC_t cmErrVMsg(cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, va_list vl ) +{ + cmErrSetRC(err,rc); + _cmErrVMsg(err,false,rc,fmt,vl); + return rc; +} + + +cmRC_t cmErrMsg( cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = cmErrVMsg(err,rc,fmt,vl); + va_end(vl); + return rc; +} + + +void _cmErrSysVMsg(cmErr_t* err, bool warnFl, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, va_list vl ) +{ + const char* sysFmt = "\n System Error: (code:%i) %s."; + int n0 = snprintf(NULL,0,sysFmt,sysErrCode,strerror(sysErrCode)); + int n1 = vsnprintf(NULL,0,fmt,vl); + int n = n0 + n1 + 1; + cmChar_t s[n0+n1+1]; + + n0 = snprintf(s,n,sysFmt,sysErrCode,strerror(sysErrCode)); + n0 += vsnprintf(s+n0,n-n0,fmt,vl); + assert( n0 <= n ); + _cmErrMsg(err,warnFl,rc,s); +} + +cmRC_t cmErrVSysMsg(cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, va_list vl ) +{ + cmErrSetRC(err,rc); + _cmErrSysVMsg(err,false,rc,sysErrCode,fmt,vl); + return rc; +} + +cmRC_t cmErrSysMsg( cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = cmErrVSysMsg(err,rc,sysErrCode,fmt,vl); + va_end(vl); + return rc; +} + +cmRC_t cmErrWarnVMsg(cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, va_list vl ) +{ + _cmErrVMsg(err,true,rc,fmt,vl); + err->warnRC = rc; + return rc; +} + +cmRC_t cmErrWarnMsg( cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = cmErrWarnVMsg(err,rc,fmt,vl); + va_end(vl); + return rc; +} + +cmRC_t cmErrWarnVSysMsg(cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, va_list vl ) +{ + _cmErrSysVMsg(err,true,rc,sysErrCode,fmt,vl); + err->warnRC = rc; + return rc; +} + + +cmRC_t cmErrWarnSysMsg( cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = cmErrWarnVSysMsg(err,rc,sysErrCode,fmt,vl); + va_end(vl); + return rc; +} + + +cmRC_t cmErrLastRC( cmErr_t* err ) +{ return err->rc; } + +cmRC_t cmErrSetRC( cmErr_t* err, cmRC_t rc ) +{ + cmRC_t retVal = err->rc; + err->rc = rc; + return retVal; +} + +cmRC_t cmErrClearRC( cmErr_t* err ) +{ return cmErrSetRC(err,cmOkRC); } diff --git a/cmErr.h b/cmErr.h new file mode 100644 index 0000000..b23ef98 --- /dev/null +++ b/cmErr.h @@ -0,0 +1,84 @@ +//{ +//( +// This class is used to format error messages and track the last error generated. +// +// Most of the cmHandle_t based classes use cmErr_t to format error messages with a +// title, maintain the last result code which indicated an error, and to hold +// a cmRpt_t object to manage application supplied text printing callbacks. +// +//) +// + +#ifndef cmErr_h +#define cmErr_h + +#ifdef __cplusplus +extern "C" { +#endif + + //( + + typedef struct + { + cmRpt_t* rpt; //< Pointer to a cmRpt_t object which is used to direct error messages to an application supplied console. + const cmChar_t* label; //< This field contains a pointer to a text label used to form the error message title. + cmRC_t rc; //< This is the last result code passed via one of the cmErrXXXMsg() functions. + cmRC_t warnRC; //< Last warning RC + } cmErr_t; + + // Setup a cmErr_t record. + // + // Note that rpt and staticLabelStr must point to client supplied objects + // whose lifetime is at least that of this cmErr_t object. + void cmErrSetup( cmErr_t* err, cmRpt_t* rpt, const cmChar_t* staticLabelStr ); + + // Duplicate a cmErr_t record. + void cmErrClone( cmErr_t* dstErr, const cmErr_t* srcErr ); + + // Error Reporting functions: + // Functions to signal an error. The rc argument is generally specific to the + // client class using the error. See the kXXXRC enumerations in the handle based + // classes for examples of result codes. + cmRC_t cmErrMsg( cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, ... ); + cmRC_t cmErrVMsg(cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, va_list vl ); + + + // Report Errors which contain accompanying system error codes. + // Use these functions when a system error (e.g. Unix errno) gives additional information + // about the source of the error. + cmRC_t cmErrSysMsg( cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, ... ); + cmRC_t cmErrVSysMsg(cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, va_list vl ); + + // Warning Reporting functions: + // Errors generally result in a task aborting. Warnings are informative but the task is + // expected to continue. + // Functions to signal a warning. The rc argument is generally specific to the + // client class using the error. See the kXXXRC enumerations in the handle based + // classes for examples of result codes. + cmRC_t cmErrWarnMsg( cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, ... ); + cmRC_t cmErrWarnVMsg(cmErr_t* err, cmRC_t rc, const cmChar_t* fmt, va_list vl ); + + + // Report warnings which contain accompanying system error codes. + // Use these functions when a system error (e.g. Unix errno) gives additional information + // about the source of the error. + cmRC_t cmErrWarnSysMsg( cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, ... ); + cmRC_t cmErrWarnVSysMsg(cmErr_t* err, cmRC_t rc, cmSysErrCode_t sysErrCode, const cmChar_t* fmt, va_list vl ); + + // Return the last recorded RC. + cmRC_t cmErrLastRC( cmErr_t* err ); + + // Return the last recorded RC and set it to a new value. + cmRC_t cmErrSetRC( cmErr_t* err, cmRC_t rc ); + + // Return the last recorded RC and set it to cmOkRC. + cmRC_t cmErrClearRC( cmErr_t* err ); + + //) + //} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmFeatFile.c b/cmFeatFile.c new file mode 100644 index 0000000..41230a8 --- /dev/null +++ b/cmFeatFile.c @@ -0,0 +1,2455 @@ +#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 "cmSymTbl.h" +#include "cmMath.h" +#include "cmFile.h" +#include "cmAudioFile.h" +#include "cmJson.h" +#include "cmFileSys.h" +#include "cmMidi.h" +#include "cmProcObj.h" +#include "cmProcTemplateMain.h" +#include "cmProc.h" +#include "cmProc2.h" +#include "cmVectOps.h" +#include "cmFrameFile.h" +#include "cmFeatFile.h" +#include "cmSerialize.h" + +#define kConstQThresh (0.0054) + +enum +{ + kFrameTypeFtId = 1, + kFrameStreamFtId = 1, + kStatRecdVectCnt = 8 // count of vectors in cmFtSumm_t +}; + +// master control record +typedef struct +{ + cmErr_t err; + cmCtx_t ctx; + + cmJsonH_t jsH; // + cmSrH_t srH; // file header serializer + + + cmFtAttr_t* attrArray; // + unsigned attrCnt; // + + cmFtParam_t* paramArray; // + unsigned paramCnt; // + + cmCtx* ctxPtr; // process context object + cmAudioFileRd* afRdPtr; // audio file reader object + cmPvAnl* pvocPtr; // phase vocoder object + cmBfcc* bfccPtr; // BFCC generator + cmMfcc* mfccPtr; // MFCC generator + cmCeps* cepsPtr; // Cepstrum generator + cmConstQ* constqPtr; // Const Q generator + + unsigned progParamIdx; + unsigned progPassIdx; + unsigned progSmpCnt; + unsigned progSmpIdx; + + +} _cmFt_t; + + +// static feature values +typedef struct +{ + unsigned order; // determines the order the feature extractors are init'd and exec'd + const char* label; // feature label + unsigned id; // feature id + unsigned ffMtxId; // cmFrameFile matrix type id + unsigned ffUnitsId; // cmFrameFile data unit id + unsigned srcId; // id of the source vector for this secondary feature (or kInvalidFtId if no src) + bool bipolarFl; // this feature is bipolar + unsigned maxCnt; // maximum feature vector element count (scalar==1 no max==0) +} _cmFtLabel_t; + + + +// analysis control record - one recd per feature +typedef struct _cmFtAttr_str +{ + cmFtAttr_t* ap; // user supplied feature parameters + const _cmFtLabel_t* lp; // static feature parameters + cmFtSumm_t* sr; // summary record assoc'd with this feature + + struct _cmFtAttr_str* sp; // sourcePtr (used by secondary feats to locate primary feature) + cmReal_t* v; // v[ap->cnt*2] feature vector memory used by cvp and pvp + cmReal_t* cvp; // current feat vect + cmReal_t* pvp; // previous feat vect +} _cmFtAnl_t; + + +// internal feature desc record +typedef struct +{ + const cmFtAttr_t* ap; + const _cmFtLabel_t* lp; + cmFtSumm_t* sr; +} _cmFtDesc_t; + +// internal feature file control record - file handle record +typedef struct +{ + cmFtH_t h; // feat file library handle + cmFrameFileH_t ffH; // handle for the frame file + cmFtInfo_t info; // file hdr recd + _cmFtDesc_t* descArray; // descArray[infoPtr->attrCnt] internal feature desc data + void* hdrBuf; // memory used to hold the serialized header +} _cmFtFile_t; + + +cmFtH_t cmFtNullHandle = { NULL }; +cmFtFileH_t cmFtFileNullHandle = { NULL }; + +_cmFtLabel_t _cmFtLabelArray[] = +{ + { 0, "ampl", kAmplFtId, kMagMId, kAmplUId, kInvalidFtId, false, 0 }, + { 1, "db_ampl", kDbAmplFtId, kMagMId, k20DbUId, kAmplFtId, false, 0 }, + { 2, "pow", kPowFtId, kMagMId, kPowUId, kInvalidFtId, false, 0 }, + { 3, "db_pow", kDbPowFtId, kMagMId, k10DbUId, kPowFtId, false, 0 }, + { 4, "phase", kPhaseFtId, kPhsMId, kRadsUId, kInvalidFtId, false, 0 }, + { 5, "bfcc", kBfccFtId, kBfccMId, kBfccUId, kInvalidFtId, false, kDefaultBarkBandCnt }, + { 6, "mfcc", kMfccFtId, kMfccMId, kMfccUId, kInvalidFtId, false, kDefaultMelBandCnt }, + { 7, "ceps", kCepsFtId, kCepsMId, kCepsUId, kInvalidFtId, false, 0 }, + { 8, "constq", kConstQFtId, kConstqMId, kAmplUId, kInvalidFtId, false, 0 }, + { 9, "log_constq", kLogConstQFtId, kConstqMId, k20DbUId, kConstQFtId, false, 0 }, + { 10, "rms", kRmsFtId, kRmsMId, kAmplUId, kInvalidFtId, false, 1 }, + { 11, "db_rms", kDbRmsFtId, kRmsMId, k20DbUId, kRmsFtId, false, 1 }, + { 12, "d1_ampl", kD1AmplFtId, kMagMId, kAmplUId | kD1UFl, kAmplFtId, true, 0 }, + { 13, "d1_db_ampl", kD1DbAmplFtId, kMagMId, k20DbUId | kD1UFl, kDbAmplFtId, true, 0 }, + { 14, "d1_pow", kD1PowFtId, kMagMId, kPowUId | kD1UFl, kPowFtId, true, 0 }, + { 15, "d1_db_pow", kD1DbPowFtId, kMagMId, k10DbUId | kD1UFl, kDbPowFtId, true, 0 }, + { 16, "d1_phase", kD1PhaseFtId, kPhsMId, kRadsUId | kD1UFl, kPhaseFtId, true, 0 }, + { 17, "d1_bfcc", kD1BfccFtId, kBfccMId, kBfccUId | kD1UFl, kBfccFtId, true, kDefaultBarkBandCnt }, + { 18, "d1_mfcc", kD1MfccFtId, kMfccMId, kMfccUId | kD1UFl, kMfccFtId, true, kDefaultMelBandCnt }, + { 19, "d1_ceps", kD1CepsFtId, kCepsMId, kCepsUId | kD1UFl, kCepsFtId, true, 0 }, + { 20, "d1_constq", kD1ConstQFtId, kConstqMId, kAmplUId | kD1UFl, kConstQFtId, true, 0 }, + { 21, "d1_log_constq", kD1LogConstQFtId,kConstqMId, k20DbUId | kD1UFl, kLogConstQFtId, true, 0 }, + { 22, "d1_rms", kD1RmsFtId, kRmsMId, kAmplUId | kD1UFl, kRmsFtId, true, 1 }, + { 23, "d1_db_rms", kD1DbRmsFtId, kRmsMId, k20DbUId | kD1UFl, kDbRmsFtId, true, 1 }, + { 24, "", kInvalidFtId, kInvalidMId,kInvalidUId, kInvalidFtId, true, 0 } + +}; + +void _cmFtPrint( _cmFt_t* p, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + cmRptVPrintf(&p->ctx.rpt,fmt,vl); + va_end(vl); +} + +cmFtRC_t _cmFtErrorV( cmFtRC_t rc, _cmFt_t* p, const char* fmt, va_list vl ) +{ return cmErrVMsg(&p->err,rc,fmt,vl); } + + +cmFtRC_t _cmFtError( cmFtRC_t rc, _cmFt_t* p, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + _cmFtErrorV(rc,p,fmt,vl); + va_end(vl); + return rc; +} + +_cmFt_t* _cmFtHandleToPtr( cmFtH_t h ) +{ + assert( h.h != NULL ); + return (_cmFt_t*)h.h; +} + +_cmFtFile_t* _cmFtFileHandleToPtr( cmFtFileH_t h ) +{ + assert( h.h != NULL ); + return (_cmFtFile_t*)h.h; +} + +_cmFtLabel_t* _cmFtIdToLabelPtr( unsigned id ) +{ + unsigned i=0; + for(i=0; _cmFtLabelArray[i].id != kInvalidFtId; ++i) + if( _cmFtLabelArray[i].id == id ) + return _cmFtLabelArray + i; + + assert(0); + return NULL; +} + + +enum +{ + kInfoSrFtId = kStructSrId, + kParamSrFtId, + kSkipSrFtId, + kAttrSrFtId, + kStatSrFtId, + kHdrSrFtId, + + kSkipVSrFtId = kSkipSrFtId + kArraySrFl, + kAttrVSrFtId = kAttrSrFtId + kArraySrFl, + kStatVSrFtId = kStatSrFtId + kArraySrFl + +}; + + +cmFtRC_t _cmFtFormatFileHdr( _cmFt_t* p ) +{ + cmFtRC_t rc = kOkFtRC; + cmSrH_t h = p->srH; + + cmSrGetAndClearLastErrorCode(h); + + if( cmSrFmtReset( h ) != kOkSrRC ) + { + rc = _cmFtError( kSerialFailFtRC, p, "Serializer format reset failed."); + goto errLabel; + } + + // cmFtSkip_t smpIdx smpCnt + cmSrDefFmt( h, kSkipSrFtId, kUIntSrId, kUIntSrId, kInvalidSrId ); + + // cmFtAttr_t featId vect cnt normFl + cmSrDefFmt( h, kAttrSrFtId, kUIntSrId, kUIntSrId, kBoolSrId, kInvalidSrId ); + + // cmFtParam_t + cmSrDefFmt( h, kParamSrFtId, + // audioFn featFn chIdx + kCharVSrId, kCharVSrId, kUIntSrId, + // wndMs hopFact normAudFl cqMinPitch cqMaxPitch + kRealSrId, kUIntSrId, kBoolSrId, kUCharSrId, kUCharSrId, + // cqBins minDb skipV attrV + kUIntSrId, kRealSrId, kSkipVSrFtId, kAttrVSrFtId, kInvalidSrId ); + + + // cmFtInfo_t + cmSrDefFmt( h, kInfoSrFtId, + // frmCnt srate fftSmpCnt hopSmpCnt binCnt skipFrmCnt floorFrmCnt param + kUIntSrId, kRealSrId, kUIntSrId, kUIntSrId, kUIntSrId, kUIntSrId, kUIntSrId, kParamSrFtId, kInvalidSrId ); + + // cmFtSumm_t + cmSrDefFmt( h, kStatSrFtId, + // id cnt + kUIntSrId, kUIntSrId, + // raw minV maxV avgV std-dev + kRealVSrId, kRealVSrId, kRealVSrId, kRealVSrId, + // raw min max + kRealSrId, kRealSrId, + // norm minV maxV avgV std-dev + kRealVSrId, kRealVSrId, kRealVSrId, kRealVSrId, + // raw min max + kRealSrId, kRealSrId, kInvalidSrId ); + + + // master header record info stat array + cmSrDefFmt( h, kHdrSrFtId, kInfoSrFtId, kStatVSrFtId, kInvalidSrId ); + + if( cmSrLastErrorCode(h) != kOkSrRC ) + rc = _cmFtError( kSerialFailFtRC,p, "Serializer formatting failed."); + + + errLabel: + return rc; +} + + +cmFtRC_t _cmFtSerializeFileHdr( _cmFt_t* p, cmFtInfo_t* f, cmFtParam_t* pp, cmFtSumm_t* summArray, void** bufPtrPtr, unsigned* bufByteCntPtr ) +{ + cmFtRC_t rc = kOkFtRC; + cmSrH_t h = p->srH; + unsigned i; + + cmSrWrReset(h); + + cmSrWrStructBegin(h, kHdrSrFtId ); + + + // info record + cmSrWrStruct( h, kInfoSrFtId, 1 ); + cmSrWrStructBegin(h, kInfoSrFtId ); + + cmSrWrUInt( h, f->frmCnt ); + cmSrWrReal( h, f->srate ); + cmSrWrUInt( h, f->fftSmpCnt ); + cmSrWrUInt( h, f->hopSmpCnt ); + cmSrWrUInt( h, f->binCnt ); + cmSrWrUInt( h, f->skipFrmCnt ); + cmSrWrUInt( h, f->floorFrmCnt); + + // param recd + cmSrWrStruct( h, kParamSrFtId, 1 ); + cmSrWrStructBegin(h, kParamSrFtId ); + cmSrWrCharV( h, pp->audioFn, strlen(pp->audioFn)+1); + cmSrWrCharV( h, pp->featFn, strlen(pp->featFn)+1); + cmSrWrUInt( h, pp->chIdx ); + cmSrWrReal( h, pp->wndMs ); + cmSrWrUInt( h, pp->hopFact); + cmSrWrBool( h, pp->normAudioFl); + cmSrWrUChar( h, pp->constQMinPitch); + cmSrWrUChar( h, pp->constQMaxPitch); + cmSrWrUInt( h, pp->constQBinsPerOctave); + cmSrWrReal( h, pp->minDb ); + + // skip array + cmSrWrStruct(h, kSkipSrFtId, pp->skipCnt ); + for(i=0; iskipCnt; ++i) + { + cmSrWrStructBegin(h, kSkipSrFtId ); + cmSrWrUInt( h, pp->skipArray[i].smpIdx); + cmSrWrUInt( h, pp->skipArray[i].smpCnt); + cmSrWrStructEnd(h); + } + + // attr array + cmSrWrStruct(h, kAttrSrFtId, pp->attrCnt ); + for(i=0; iattrCnt; ++i) + { + cmSrWrStructBegin( h, kAttrSrFtId ); + cmSrWrUInt( h, pp->attrArray[i].id ); + cmSrWrUInt( h, pp->attrArray[i].cnt ); + cmSrWrBool( h, pp->attrArray[i].normFl); + cmSrWrStructEnd(h); + } + + cmSrWrStructEnd(h); // end param + cmSrWrStructEnd(h); // end info + + + // write the status array + cmSrWrStruct(h, kStatSrFtId, pp->attrCnt ); + + for(i=0; iattrCnt; ++i) + { + assert( summArray[i].id == pp->attrArray[i].id ); + + cmSrWrStructBegin(h,kStatSrFtId); + + cmSrWrUInt( h, summArray[i].id); + cmSrWrUInt( h, summArray[i].cnt); + + cmSrWrRealV( h, summArray[i].rawMinV, pp->attrArray[i].cnt); + cmSrWrRealV( h, summArray[i].rawMaxV, pp->attrArray[i].cnt); + cmSrWrRealV( h, summArray[i].rawAvgV, pp->attrArray[i].cnt); + cmSrWrRealV( h, summArray[i].rawSdvV, pp->attrArray[i].cnt); + cmSrWrReal( h, summArray[i].rawMin ); + cmSrWrReal( h, summArray[i].rawMax ); + + cmSrWrRealV( h, summArray[i].normMinV, pp->attrArray[i].cnt); + cmSrWrRealV( h, summArray[i].normMaxV, pp->attrArray[i].cnt); + cmSrWrRealV( h, summArray[i].normAvgV, pp->attrArray[i].cnt); + cmSrWrRealV( h, summArray[i].normSdvV, pp->attrArray[i].cnt); + cmSrWrReal( h, summArray[i].normMin ); + cmSrWrReal( h, summArray[i].normMax ); + + cmSrWrStructEnd(h); + } + + if( cmSrLastErrorCode(h) != kOkSrRC ) + { + rc = _cmFtError( kSerialFailFtRC, p, "Header serialization failed."); + goto errLabel; + } + + if((*bufPtrPtr = cmSrWrAllocBuf(h,bufByteCntPtr)) == NULL ) + { + rc = _cmFtError( kSerialFailFtRC, p, "Header serializer failed on write buffer allocation."); + goto errLabel; + } + + errLabel: + return rc; +} + +cmFtRC_t _cmDeserializeFileHdr( _cmFt_t* p, _cmFtFile_t* fp, void* buf, unsigned bufByteCnt ) +{ + cmFtRC_t rc = kOkFtRC; + cmSrH_t h = p->srH; + unsigned n,i; + cmFtInfo_t* f = &fp->info; + cmFtParam_t* pp = &fp->info.param; + + // do endian swap + if( cmSrRdProcessBuffer(h, buf, bufByteCnt ) != kOkSrRC ) + { + rc = _cmFtError( kSerialFailFtRC, p, "Deserializatoin buffer pre-process failed."); + goto errLabel; + } + + // duplciate the buffer - this will allow us to use memory in the buffer to hold header objects. + fp->hdrBuf = cmMemResize( char, fp->hdrBuf, bufByteCnt ); + memcpy(fp->hdrBuf,buf,bufByteCnt); + + // setup the serializer reader + if( cmSrRdSetup( h, fp->hdrBuf, bufByteCnt ) != kOkSrRC ) + { + rc = _cmFtError( kSerialFailFtRC, p, "Deserialization buffer setup failed."); + goto errLabel; + } + + cmSrRdStructBegin(h, kHdrSrFtId ); + + // info record + cmSrReadStruct( h, kInfoSrFtId, &n ); assert(n==1); + cmSrRdStructBegin(h, kInfoSrFtId ); + + cmSrReadUInt( h, &f->frmCnt ); + cmSrReadReal( h, &f->srate ); + cmSrReadUInt( h, &f->fftSmpCnt ); + cmSrReadUInt( h, &f->hopSmpCnt ); + cmSrReadUInt( h, &f->binCnt ); + cmSrReadUInt( h, &f->skipFrmCnt ); + cmSrReadUInt( h, &f->floorFrmCnt ); + + // param recd + cmSrReadStruct( h, kParamSrFtId, &n ); assert(n==1); + cmSrRdStructBegin(h, kParamSrFtId ); + cmSrReadCharCV(h, &pp->audioFn, &n ); + cmSrReadCharCV(h, &pp->featFn, &n ); + cmSrReadUInt( h, &pp->chIdx ); + cmSrReadReal( h, &pp->wndMs ); + cmSrReadUInt( h, &pp->hopFact); + cmSrReadBool( h, &pp->normAudioFl); + cmSrReadUChar( h, &pp->constQMinPitch); + cmSrReadUChar( h, &pp->constQMaxPitch); + cmSrReadUInt( h, &pp->constQBinsPerOctave); + cmSrReadReal( h, &pp->minDb ); + + // skip array + cmSrReadStruct(h, kSkipSrFtId, &pp->skipCnt ); + pp->skipArray = cmMemResizeZ( cmFtSkip_t, pp->skipArray, pp->skipCnt ); + for(i=0; iskipCnt; ++i) + { + cmSrRdStructBegin(h, kSkipSrFtId ); + cmSrReadUInt( h, &pp->skipArray[i].smpIdx); + cmSrReadUInt( h, &pp->skipArray[i].smpCnt); + cmSrRdStructEnd(h); + } + + // attr array + cmSrReadStruct(h, kAttrSrFtId, &pp->attrCnt ); + pp->attrArray = cmMemResizeZ( cmFtAttr_t, pp->attrArray, pp->attrCnt ); + for(i=0; iattrCnt; ++i) + { + cmSrRdStructBegin( h, kAttrSrFtId ); + cmSrReadUInt( h, &pp->attrArray[i].id ); + cmSrReadUInt( h, &pp->attrArray[i].cnt ); + cmSrReadBool( h, &pp->attrArray[i].normFl); + cmSrRdStructEnd(h); + } + + cmSrRdStructEnd(h); // end param + cmSrRdStructEnd(h); // end info + + + // read the status array + cmSrReadStruct(h, kStatSrFtId, &n ); + assert( n == pp->attrCnt ); + + fp->info.summArray = cmMemResizeZ( cmFtSumm_t, fp->info.summArray, pp->attrCnt ); + + for(i=0; iattrCnt; ++i) + { + cmSrRdStructBegin(h,kStatSrFtId); + + cmSrReadUInt( h, &fp->info.summArray[i].id); + + assert( fp->info.summArray[i].id == pp->attrArray[i].id ); + cmSrReadUInt( h, &fp->info.summArray[i].cnt); + + cmSrReadRealV( h, &fp->info.summArray[i].rawMinV, &pp->attrArray[i].cnt); + cmSrReadRealV( h, &fp->info.summArray[i].rawMaxV, &pp->attrArray[i].cnt); + cmSrReadRealV( h, &fp->info.summArray[i].rawAvgV, &pp->attrArray[i].cnt); + cmSrReadRealV( h, &fp->info.summArray[i].rawSdvV, &pp->attrArray[i].cnt); + cmSrReadReal( h, &fp->info.summArray[i].rawMin ); + cmSrReadReal( h, &fp->info.summArray[i].rawMax ); + + cmSrReadRealV( h, &fp->info.summArray[i].normMinV, &pp->attrArray[i].cnt); + cmSrReadRealV( h, &fp->info.summArray[i].normMaxV, &pp->attrArray[i].cnt); + cmSrReadRealV( h, &fp->info.summArray[i].normAvgV, &pp->attrArray[i].cnt); + cmSrReadRealV( h, &fp->info.summArray[i].normSdvV, &pp->attrArray[i].cnt); + cmSrReadReal( h, &fp->info.summArray[i].normMin ); + cmSrReadReal( h, &fp->info.summArray[i].normMax ); + + cmSrRdStructEnd(h); + } + + if( cmSrLastErrorCode(h) != kOkSrRC ) + { + rc = _cmFtError( kSerialFailFtRC, p, "Deserialization failed."); + goto errLabel; + } + + errLabel: + return rc; +} + + +unsigned cmFtFeatLabelToId( const char* label ) +{ + unsigned i=0; + for(i=0; _cmFtLabelArray[i].id != kInvalidFtId; ++i) + if( strcmp(label,_cmFtLabelArray[i].label) == 0 ) + return _cmFtLabelArray[i].id; + + return kInvalidFtId; +} + +const char* cmFtFeatIdToLabel( unsigned id ) +{ + unsigned i=0; + for(i=0; _cmFtLabelArray[i].id != kInvalidFtId; ++i) + if( _cmFtLabelArray[i].id == id ) + return _cmFtLabelArray[i].label; + + return NULL; +} + +cmFtRC_t cmFtInitialize( cmFtH_t* hp, cmCtx_t* ctx ) +{ + cmFtRC_t rc; + + if((rc = cmFtFinalize(hp)) != kOkFtRC ) + return rc; + + _cmFt_t* p = cmMemAllocZ( _cmFt_t, 1 ); + cmErrSetup(&p->err,&ctx->rpt,"Feature file"); + p->ctx = *ctx; + p->jsH = cmJsonNullHandle; + p->progParamIdx = cmInvalidIdx; + p->progPassIdx = 0; + p->progSmpIdx = 0; + p->progSmpCnt = 0; + + // initialize the serializer + if( cmSrAlloc(&p->srH,ctx) != kOkSrRC ) + { + rc = _cmFtError( kSerialFailFtRC, p, "The serializer allocation failed."); + goto errLabel; + } + + // setup the serializer format + if((rc = _cmFtFormatFileHdr(p)) != kOkFtRC ) + goto errLabel; + + // create the proc context object + if((p->ctxPtr = cmCtxAlloc(NULL,&p->ctx.rpt,cmLHeapNullHandle,cmSymTblNullHandle)) == NULL ) + { + rc = _cmFtError(kDspProcFailFtRC,p, "The ctx compoenent allocation failed."); + goto errLabel; + } + + // create the audio file reader + if((p->afRdPtr = cmAudioFileRdAlloc( p->ctxPtr, NULL, 0, NULL, cmInvalidIdx, 0, cmInvalidIdx )) == NULL ) + { + rc = _cmFtError( kDspProcFailFtRC, p, "The audio file reader allocation failed."); + goto errLabel; + } + + // create the phase vocoder + if((p->pvocPtr = cmPvAnlAlloc( p->ctxPtr, NULL, 0, 0, 0, 0, 0 )) == NULL ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"The phase vocoder allocation failed."); + goto errLabel; + } + + // create the BFCC transformer + if((p->bfccPtr = cmBfccAlloc( p->ctxPtr, NULL, 0, 0, 0 )) == NULL ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"The BFCC generator allocation failed."); + goto errLabel; + } + + // create the MFCC generator + if((p->mfccPtr = cmMfccAlloc( p->ctxPtr, NULL, 0, 0, 0, 0)) == NULL ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"The MFCC generator allocation failed."); + goto errLabel; + } + + // create the Cepstrum transformer + if((p->cepsPtr = cmCepsAlloc( p->ctxPtr, NULL, 0, 0 )) == NULL ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"The Cepstrum generator allocation failed."); + goto errLabel; + } + + // create the Constant Q generator + if((p->constqPtr = cmConstQAlloc( p->ctxPtr, NULL, 0, 0, 0, 0,0 )) == NULL ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"The Constant-Q generator allocation failed."); + goto errLabel; + } + + + hp->h = p; + + errLabel: + return rc; + +} + +cmFtRC_t cmFtFinalize( cmFtH_t* hp ) +{ + cmFtRC_t rc = kOkFsRC; + unsigned i; + + assert( hp != NULL ); + + if( hp->h == NULL ) + return kOkFsRC; + + _cmFt_t* p = _cmFtHandleToPtr(*hp); + + for(i=0; iparamCnt; ++i) + cmMemPtrFree(&p->paramArray[i].skipArray); + + cmMemPtrFree(&p->attrArray); + p->attrCnt = 0; + + cmMemPtrFree(&p->paramArray); + p->paramCnt = 0; + + if( cmConstQFree(&p->constqPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"Constant-Q generator free failed."); + goto errLabel; + } + + if( cmCepsFree(&p->cepsPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"Cepstrum generator free failed."); + goto errLabel; + } + + if( cmMfccFree(&p->mfccPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"MFCC generator free failed."); + goto errLabel; + } + + if( cmBfccFree(&p->bfccPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"BFCC generator free failed."); + goto errLabel; + } + + if( cmPvAnlFree(&p->pvocPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"Phase voocoder free failed."); + goto errLabel; + } + + if( cmAudioFileRdFree(&p->afRdPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"Audio file reader failed."); + goto errLabel; + } + + if( cmCtxFree(&p->ctxPtr) != cmOkRC ) + { + rc = _cmFtError( kDspProcFailFtRC,p,"Context proc failed."); + goto errLabel; + } + + if( cmJsonFinalize(&p->jsH) != kOkJsRC ) + { + rc = _cmFtError(kJsonFailFtRC, p, "The JSON system object finalization failed."); + goto errLabel; + } + + if( cmSrFree(&p->srH) != kOkSrRC ) + { + rc = _cmFtError(kSerialFailFtRC, p, "The serializer free failed."); + goto errLabel; + } + + + cmMemPtrFree(&p); + hp->h = NULL; + + errLabel: + return rc; +} + +bool cmFtIsValid( cmFtH_t h ) +{ return h.h != NULL; } + +cmFtRC_t cmFtParse( cmFtH_t h, const char* cfgFn ) +{ + cmFtRC_t rc = kOkFtRC; + cmJsRC_t jsRC = kOkJsRC; + cmJsonNode_t* rootPtr = NULL; + const char* errLabelPtr = NULL; + const char* outDir = NULL; + cmReal_t wndMs = 0; + unsigned hopFact = 0; + bool normAudioFl = false; + const char* constQMinPitchStr = NULL; + const char* constQMaxPitchStr = NULL; + unsigned constQBinsPerOctave = 0; + cmMidiByte_t constQMinPitch = 0; + cmMidiByte_t constQMaxPitch = 0; + cmReal_t minDb = 0; + cmReal_t floorThreshDb = 0; + cmJsonNode_t* featArrayNodePtr = NULL; + cmJsonNode_t* audioFnArrayNodePtr = NULL; + _cmFt_t* p = _cmFtHandleToPtr(h); + unsigned i,j; + + assert( cfgFn != NULL ); + + // parse file + if( cmJsonInitializeFromFile( &p->jsH, cfgFn, &p->ctx ) != kOkJsRC ) + { + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. file parse failed on: '%s'", cfgFn ); + goto errLabel; + } + + // get the json cfg root + if( (rootPtr = cmJsonRoot( p->jsH )) == NULL ) + { + rc = _cmFtError( kCfgParseFailFtRC, p, "The cfg. file '%s' appears to be empty.", cfgFn ); + goto errLabel; + } + + // read the cfg file header + if((jsRC = cmJsonMemberValues( rootPtr, &errLabelPtr, + "outDir", kStringTId, &outDir, + "wndMs", kRealTId, &wndMs, + "hopFact", kIntTId, &hopFact, + "normAudioFl", kTrueTId, &normAudioFl, + "constQMinPitch", kStringTId, &constQMinPitchStr, + "constQMaxPitch", kStringTId, &constQMaxPitchStr, + "constQBinsPerOctave", kIntTId, &constQBinsPerOctave, + "minDb", kRealTId, &minDb, + "floorThreshDb", kRealTId, &floorThreshDb, + "featArray", kArrayTId, &featArrayNodePtr, + "audioFnArray", kArrayTId, &audioFnArrayNodePtr, + NULL )) != kOkJsRC ) + { + if( jsRC == kNodeNotFoundJsRC ) + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. field not found:'%s' in file:'%s'.",cmStringNullGuard(errLabelPtr),cmStringNullGuard(cfgFn)); + else + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. header parse failed '%s'.",cmStringNullGuard(cfgFn) ); + + goto errLabel; + + } + + // convert the min const-q sci pitch string to a midi pitch value + if( (constQMinPitch = cmSciPitchToMidi( constQMinPitchStr )) == kInvalidMidiPitch ) + { + rc = _cmFtError( kCfgParseFailFtRC, p, "The const-Q min. pitch ('%s') is invalid.", cmStringNullGuard(constQMinPitchStr)); + goto errLabel; + } + + // convert the max const-q sci pitch string to a midi pitch value + if( (constQMaxPitch = cmSciPitchToMidi( constQMaxPitchStr )) == kInvalidMidiPitch ) + { + rc = _cmFtError( kCfgParseFailFtRC, p, "The const-Q max. pitch ('%s') is invalid.", cmStringNullGuard(constQMaxPitchStr)); + goto errLabel; + } + + unsigned parseAttrCnt = cmJsonChildCount( featArrayNodePtr ); + p->attrArray = cmMemAllocZ( cmFtAttr_t, parseAttrCnt ); + + + // read the attribute array + for(i=0,j=0; iattrArray[j].cnt = 0; + p->attrArray[j].enableFl = true; + + if((jsRC = cmJsonMemberValues( cmJsonArrayElement(featArrayNodePtr,i), &errLabelPtr, + "feat", kStringTId, &featLabel, + "cnt", kIntTId | kOptArgJsFl, &p->attrArray[j].cnt, + "normFl", kTrueTId, &p->attrArray[j].normFl, + "enableFl", kTrueTId | kOptArgJsFl, &p->attrArray[j].enableFl, + NULL )) != kOkJsRC ) + { + if( jsRC == kNodeNotFoundJsRC ) + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. feature attribute field:'%s' not found at index %i in file:'%s'.",cmStringNullGuard(errLabelPtr),i,cmStringNullGuard(cfgFn)); + else + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. feature attribute parse failed at index %i in '%s'.",i,cmStringNullGuard(cfgFn) ); + + goto errLabel; + } + + if( p->attrArray[j].enableFl ) + { + + // convert the feature label to an id + if( (p->attrArray[j].id = cmFtFeatLabelToId( featLabel)) == kInvalidFtId ) + { + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. feature '%s' was not found at featArray index %i in '%s'.", featLabel, i, cmStringNullGuard(cfgFn)); + goto errLabel; + } + + ++j; + } + + } + + p->attrCnt = j; + + p->paramCnt = cmJsonChildCount( audioFnArrayNodePtr ); + p->paramArray = cmMemAllocZ( cmFtParam_t, p->paramCnt ); + + // read the audio file array + for(i=0; iparamCnt; ++i) + { + + cmJsonNode_t* skipArrayNodePtr = NULL; + + // read the audio file read + if((jsRC = cmJsonMemberValues( cmJsonArrayElement(audioFnArrayNodePtr,i), &errLabelPtr, + "audioFn", kStringTId, &p->paramArray[i].audioFn, + "featFn", kStringTId, &p->paramArray[i].featFn, + "skipArray",kArrayTId | kOptArgJsFl, &skipArrayNodePtr, + "chIdx", kIntTId, &p->paramArray[i].chIdx, + NULL)) != kOkJsRC ) + { + if( jsRC == kNodeNotFoundJsRC ) + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. audio file field :'%s' not found at index %i in file:'%s'.",cmStringNullGuard(errLabelPtr),i,cmStringNullGuard(cfgFn)); + else + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. audio file parse failed at index %i in '%s'.",i,cmStringNullGuard(cfgFn) ); + + goto errLabel; + } + + p->paramArray[i].wndMs = wndMs; + p->paramArray[i].hopFact = hopFact; + p->paramArray[i].normAudioFl = normAudioFl; + p->paramArray[i].constQBinsPerOctave = constQBinsPerOctave; + p->paramArray[i].constQMinPitch = constQMinPitch; + p->paramArray[i].constQMaxPitch = constQMaxPitch; + p->paramArray[i].minDb = minDb; + p->paramArray[i].floorThreshDb = floorThreshDb; + p->paramArray[i].attrArray = p->attrArray; + p->paramArray[i].attrCnt = p->attrCnt; + p->paramArray[i].skipCnt = skipArrayNodePtr==NULL ? 0 : cmJsonChildCount( skipArrayNodePtr ); + p->paramArray[i].skipArray = skipArrayNodePtr==NULL ? NULL : cmMemAllocZ( cmFtSkip_t, p->paramArray[i].skipCnt ); + + + // read the skip array in the audio file recd + for(j=0; jparamArray[i].skipCnt; ++j) + { + if((jsRC = cmJsonMemberValues( cmJsonArrayElement(skipArrayNodePtr,j), &errLabelPtr, + "smpIdx", kIntTId, &p->paramArray[i].skipArray[j].smpIdx, + "smpCnt", kIntTId, &p->paramArray[i].skipArray[j].smpCnt, + NULL)) != kOkJsRC ) + { + if( jsRC == kNodeNotFoundJsRC ) + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. audio file skip field '%s' not found at index %i in file:'%s'.",cmStringNullGuard(errLabelPtr),j,cmStringNullGuard(cfgFn)); + else + rc = _cmFtError( kCfgParseFailFtRC, p, "Cfg. audio file skip parse failed at index %i in '%s'.",j, cmStringNullGuard(cfgFn) ); + + goto errLabel; + } + + } + + // if the audio file does not exist + if( cmFsIsFile( p->paramArray[i].audioFn ) == false ) + { + rc = _cmFtError( kFileNotFoundFtRC, p, "The audio file '%s' was not found.", p->paramArray[i].audioFn ); + goto errLabel; + } + + // form the feature file name for this file + if((p->paramArray[i].featFn = cmFsMakeFn( outDir, p->paramArray[i].featFn, NULL, NULL )) == NULL ) + { + rc = _cmFtError( kFileSysFailFtRC, p, "The attempt to create the feature file name for '%s' failed.", cmStringNullGuard(p->paramArray[i].featFn)); + goto errLabel; + } + + + } + + // if the output directory does not exist then create it + if( cmFsIsDir(outDir) == false ) + if( cmFsMkDir(outDir) != kOkFsRC ) + { + rc = _cmFtError( kDirCreateFailFtRC, p, "The attempt to create the output directory '%s' failed.",outDir); + goto errLabel; + } + + + errLabel: + + + return rc; + +} + +bool _cmFtZeroSkipSamples( const cmFtParam_t* pp, cmSample_t* v, unsigned vn, unsigned begSmpIdx ) +{ + unsigned endSmpIdx = begSmpIdx + vn - 1; + bool retFl = false; + unsigned i = 0; + const cmFtSkip_t* sp = pp->skipArray; + + // for each skipArray[] record + for(i=0; iskipCnt; ++sp,++i) + if( sp->smpCnt != 0 ) + { + unsigned bi = 0; + unsigned ei = vn-1; + + unsigned sp_endIdx; + + // if sp->smpCnt is negative then skip to end of file + if( sp->smpCnt == -1 ) + sp_endIdx = endSmpIdx; + else + sp_endIdx = sp->smpIdx + sp->smpCnt - 1; + + + // begSmpIdx:endSmpIdx indicate the index range of v[] + // sp->smpIdx:sp_endIdx indicate the skip index range + + + // if the skip range is entirely before or after v[] + if( sp_endIdx < begSmpIdx || sp->smpIdx > endSmpIdx ) + continue; + + // if sp->smpIdx is inside v[] + if( sp->smpIdx > begSmpIdx ) + bi = sp->smpIdx - begSmpIdx; + + // if sp_endIdx is inside v[] + if( sp_endIdx < endSmpIdx ) + { + assert( endSmpIdx - sp_endIdx <= ei ); + ei -= endSmpIdx - sp_endIdx; + } + + assert( bi <= ei ); + assert( bi < vn && ei < vn ); + + // zero the samples which are inside the skip range + cmVOS_Zero(v+bi,(ei-bi)+1); + retFl = true; + } + + return retFl; +} + + +cmFtRC_t _cmFtProcInit( _cmFt_t* p, cmFtInfo_t* f, cmFtParam_t* pp, _cmFtAnl_t* anlArray ) +{ + cmFtRC_t rc = kOkFtRC; + unsigned i; + + + // initialize the phase vocoder + if( cmPvAnlInit( p->pvocPtr, f->hopSmpCnt, f->srate, f->fftSmpCnt, f->hopSmpCnt, kNoCalcHzPvaFl ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p," The phase vocoder initialization failed."); + goto errLabel; + } + + assert( f->binCnt == p->pvocPtr->binCnt ); + + cmReal_t binHz = f->srate / f->fftSmpCnt; + + // initialize each requested feature extractor + for(i=0; iattrCnt; ++i) + { + _cmFtAnl_t* a = anlArray + i; + + assert( a->lp != NULL ); + + switch( a->ap->id ) + { + case kAmplFtId: + case kDbAmplFtId: + case kPowFtId: + case kDbPowFtId: + case kPhaseFtId: + if( a->ap->cnt > f->binCnt ) + { + rc = _cmFtError(kParamRangeFtRC,p,"The '%s' cnt value: %i must be less than the bin count: %i.",a->lp->label,a->ap->cnt,f->binCnt+1); + goto errLabel; + } + + if( a->ap->cnt == 0 ) + a->ap->cnt = f->binCnt; + + break; + + case kBfccFtId: // initialize the BFCC generator + if( a->ap->cnt > kDefaultBarkBandCnt ) + { + rc = _cmFtError(kParamRangeFtRC,p,"The BFCC feature vector length (%i) must be less than (%i).", a->ap->cnt, kDefaultBarkBandCnt+1 ); + goto errLabel; + } + + if( a->ap->cnt == 0 ) + a->ap->cnt = kDefaultBarkBandCnt; + + if( cmBfccInit( p->bfccPtr, kDefaultBarkBandCnt, p->pvocPtr->binCnt, binHz ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p," The BFCC generator initialization failed."); + goto errLabel; + } + break; + + case kMfccFtId: // initialize the MFCC generator + if( a->ap->cnt > kDefaultMelBandCnt ) + { + rc = _cmFtError(kParamRangeFtRC,p,"The MFCC feature vector length (%i) must be less than (%i).", a->ap->cnt, kDefaultMelBandCnt+1 ); + goto errLabel; + } + + if( a->ap->cnt == 0 ) + a->ap->cnt = kDefaultMelBandCnt; + + if( cmMfccInit( p->mfccPtr, f->srate, kDefaultMelBandCnt, a->ap->cnt, p->pvocPtr->binCnt ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p," The MFCC generator initialization failed."); + goto errLabel; + } + break; + + case kCepsFtId: // initialize the cepstrum generator + + if( a->ap->cnt > f->binCnt ) + { + rc = _cmFtError(kParamRangeFtRC,p,"The '%s' cnt value: %i must be less than the bin count: %i.",a->lp->label,a->ap->cnt,f->binCnt+1); + goto errLabel; + } + + if( a->ap->cnt == 0 ) + a->ap->cnt = f->binCnt; + + if( cmCepsInit( p->cepsPtr, p->pvocPtr->binCnt, a->ap->cnt ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p," The Cepstrum generator initialization failed."); + goto errLabel; + } + break; + + case kConstQFtId: // initialize the constant Q generator + case kLogConstQFtId: + + if( cmConstQInit(p->constqPtr, f->srate, pp->constQMinPitch, pp->constQMaxPitch, pp->constQBinsPerOctave, kConstQThresh ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p,"The constant-q generator initialization failed."); + goto errLabel; + } + + if( a->ap->cnt > p->constqPtr->constQBinCnt ) + { + rc = _cmFtError(kParamRangeFtRC,p,"The '%s' cnt value: %i must be less than the bin count: %i.",a->lp->label,a->ap->cnt,p->constqPtr->constQBinCnt+1); + goto errLabel; + } + + if( a->ap->cnt == 0 ) + a->ap->cnt = p->constqPtr->constQBinCnt; + break; + + case kRmsFtId: + case kDbRmsFtId: + a->ap->cnt = 1; // scalars must have a cnt == 1 + break; + + case kD1AmplFtId: + case kD1DbAmplFtId: + case kD1PowFtId: + case kD1DbPowFtId: + case kD1PhaseFtId: + case kD1BfccFtId: + case kD1MfccFtId: + case kD1CepsFtId: + case kD1ConstQFtId: + case kD1LogConstQFtId: + if( a->ap->cnt == 0 ) + a->ap->cnt = a->sp->ap->cnt; + break; + + case kD1RmsFtId: + case kD1DbRmsFtId: + a->ap->cnt = 1; + break; + + default: + { assert(0); } + + } // end switch + + + // setup the feature label record and allocate the feature vector + if( a->ap->cnt ) + { + // 2==cvp and pvp + kStatRecdVectCnt==count of summary vectors + unsigned nn = a->ap->cnt * (2 + kStatRecdVectCnt); + unsigned n = 0; + + assert(a->v == NULL); + a->v = cmMemAllocZ( cmReal_t, nn ); + a->cvp = a->v + n; n += a->ap->cnt; + a->pvp = a->v + n; n += a->ap->cnt; + + a->sr->cnt = a->ap->cnt; + + a->sr->rawMinV = a->v + n; n += a->ap->cnt; + a->sr->rawMaxV = a->v + n; n += a->ap->cnt; + a->sr->rawAvgV = a->v + n; n += a->ap->cnt; + a->sr->rawSdvV = a->v + n; n += a->ap->cnt; + a->sr->rawMin = cmReal_MAX; + a->sr->rawMax = -cmReal_MAX; + cmVOR_Fill( a->sr->rawMinV, a->ap->cnt, cmReal_MAX ); + cmVOR_Fill( a->sr->rawMaxV, a->ap->cnt, -cmReal_MAX ); + + a->sr->normMinV = a->v + n; n += a->ap->cnt; + a->sr->normMaxV = a->v + n; n += a->ap->cnt; + a->sr->normAvgV = a->v + n; n += a->ap->cnt; + a->sr->normSdvV = a->v + n; n += a->ap->cnt; + a->sr->normMin = cmReal_MAX; + a->sr->normMax = -cmReal_MAX; + cmVOR_Fill( a->sr->normMinV, a->ap->cnt, cmReal_MAX ); + cmVOR_Fill( a->sr->normMaxV, a->ap->cnt, -cmReal_MAX ); + + assert(n == nn); + } + + if( a->sp != NULL ) + { + if( a->sp->ap->cnt > a->ap->cnt ) + { + rc = _cmFtError( kParamRangeFtRC,p,"The feature element count '%i' for '%s' is greater than the source vector '%s' '%i'.", a->ap->cnt, a->lp->label, a->sp->lp->label, a->sp->ap->cnt ); + goto errLabel; + } + } + + } // end for + + errLabel: + return rc; +} + +cmFtRC_t _cmFtProcExec( _cmFt_t* p, cmFtInfo_t* f, cmFtParam_t* pp, cmFrameFileH_t ffH, _cmFtAnl_t* anlArray, const cmSample_t* audV ) +{ + cmFtRC_t rc = kOkFtRC; + unsigned i; + + + for(i=0; i < pp->attrCnt; ++i) + { + _cmFtAnl_t* a = anlArray + i; + + // swap current and previous pointer + cmReal_t* tp = a->cvp; + a->cvp = a->pvp; + a->pvp = tp; + + switch( a->lp->id ) + { + case kAmplFtId: + cmVOR_Copy(a->cvp, a->ap->cnt, p->pvocPtr->magV ); + break; + + case kDbAmplFtId: + cmVOR_AmplToDbVV( a->cvp, a->ap->cnt, p->pvocPtr->magV, pp->minDb ); + break; + + case kPowFtId: + cmVOR_PowVVS( a->cvp, a->ap->cnt, p->pvocPtr->magV, 2.0 ); + break; + + case kDbPowFtId: + cmVOR_PowToDbVV( a->cvp, a->ap->cnt, a->sp->cvp, pp->minDb ); + break; + + case kPhaseFtId: + cmVOR_Copy( a->cvp, a->ap->cnt, p->pvocPtr->phsV ); + break; + + case kBfccFtId: + { + cmBfccExec( p->bfccPtr, p->pvocPtr->magV, p->pvocPtr->binCnt ); + cmVOR_Copy(a->cvp, a->ap->cnt, p->bfccPtr->outV ); + } + break; + + case kMfccFtId: + { + cmMfccExecAmplitude( p->mfccPtr, p->pvocPtr->magV, p->pvocPtr->binCnt ); + cmVOR_Copy( a->cvp, a->ap->cnt, p->mfccPtr->outV ); + } + break; + + case kCepsFtId: + { + cmCepsExec( p->cepsPtr, p->pvocPtr->magV, p->pvocPtr->phsV, p->pvocPtr->binCnt ); + cmVOR_Copy(a->cvp, a->ap->cnt, p->cepsPtr->outV ); + } + break; + + case kConstQFtId: + { + // convert from float complex to double complex + cmComplexR_t tmp0[ p->pvocPtr->binCnt ]; + unsigned j; + for(j=0; jpvocPtr->binCnt; ++j) + tmp0[j] = p->pvocPtr->ft.complexV[j]; + + cmConstQExec( p->constqPtr, tmp0, p->pvocPtr->binCnt ); + cmVOR_Copy( a->cvp, a->ap->cnt, p->constqPtr->magV ); + } + break; + + case kLogConstQFtId: + cmVOR_LogV( a->cvp, a->ap->cnt, p->constqPtr->magV ); + break; + + case kRmsFtId: + a->cvp[0] = cmVOS_RMS( audV, p->afRdPtr->outN, p->afRdPtr->outN ); + break; + + case kDbRmsFtId: + cmVOR_AmplToDbVV( a->cvp, 1, a->sp->cvp, pp->minDb ); + break; + + case kD1AmplFtId: + case kD1DbAmplFtId: + case kD1PowFtId: + case kD1DbPowFtId: + case kD1PhaseFtId: + case kD1BfccFtId: + case kD1MfccFtId: + case kD1CepsFtId: + case kD1ConstQFtId: + case kD1LogConstQFtId: + case kD1RmsFtId: + case kD1DbRmsFtId: + cmVOR_SubVVV( a->cvp, a->ap->cnt, a->sp->pvp, a->sp->cvp ); + break; + + default: + assert(0); + break; + + } // end switch + + if( cmFrameFileWriteMtxReal( ffH, a->lp->ffMtxId, a->lp->ffUnitsId, a->cvp, a->ap->cnt, 1 ) != kOkFfRC ) + { + rc = _cmFtError( kFrameWriteFailFtRC, p, "Matrix write failed (feature:%s size:%i).",a->lp->label,a->ap->cnt); + goto errLabel; + } + + + } + + errLabel: + return rc; + +} + +unsigned _cmFtWriteField( char* buf, unsigned bufByteCnt, unsigned bufIdx, const void* s, unsigned srcByteCnt ) +{ + assert( bufIdx + srcByteCnt <= bufByteCnt ); + memcpy(buf+bufIdx,s,srcByteCnt); + return bufIdx + srcByteCnt; +} + + + +cmFtRC_t _cmFtWriteFileHdr( _cmFt_t* p, cmFtInfo_t* f, cmFtParam_t* pp, cmFrameFileH_t ffH, cmFtSumm_t* summArray, bool updateFl ) +{ + cmFtRC_t rc = kOkFtRC; + void* buf; + unsigned bufByteCnt; + + // serialize the file header + if((rc = _cmFtSerializeFileHdr(p,f,pp,summArray,&buf,&bufByteCnt)) != kOkFtRC ) + goto errLabel; + + if( updateFl ) + { + const cmFfMtx_t* mp = NULL; + void* hdrPtr = NULL; + if( (hdrPtr = cmFrameFileMtxBlob(ffH, kDataMId, kNoUnitsUId, &mp )) == NULL ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file header read before update failed."); + goto errLabel; + } + + assert( mp->rowCnt == bufByteCnt ); + memcpy( hdrPtr, buf, bufByteCnt ); + } + else + { + if( cmFrameFileWriteMtxBlob( ffH, kDataMId, kNoUnitsUId, buf, bufByteCnt, 1 ) != kOkFfRC ) + { + rc = _cmFtError( kFrameWriteFailFtRC, p, "Header write failed."); + goto errLabel; + } + } + + errLabel: + return rc; + +} + + + +// Interface to the _cmFtProcFile() user programmable process function. +// This function is called once per each feature vector in the feature file. +// v[fn] points to the feature file. +// Return true if the feature has been modified and should be written back to disk. + typedef bool (*_cmFtProcFunc_t)( _cmFt_t* p, _cmFtAnl_t* a, cmReal_t* v, unsigned vn ); + +// Iterate through each frame and each frame matrix call procFunc(). +cmFtRC_t _cmFtProcFile( _cmFt_t* p, cmFrameFileH_t ffH, cmFtParam_t* pp, _cmFtAnl_t* anlArray, _cmFtProcFunc_t procFunc ) +{ + cmFtRC_t rc = kOkFtRC; + cmFfRC_t ffRC = kOkFfRC; + unsigned i,j; + + ++p->progPassIdx; + p->progSmpIdx = 0; + p->progSmpCnt = cmFrameFileDesc( ffH )->frameCnt; + + // rewind the frame file + if( cmFrameFileRewind( ffH ) != kOkFfRC ) + { + rc = _cmFtError(kFrameFileFailFtRC,p,"Normalize rewind failed on '%s'.", cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + // load the next data frame + for(i=0; (ffRC=cmFrameFileFrameLoadNext(ffH,kFrameTypeFtId,kFrameStreamFtId,NULL)) == kOkFfRC; ++i,++p->progSmpIdx) + { + bool updateFl = false; + + // for each feature matrix + for(j=0; jattrCnt; ++j) + { + unsigned dn; + cmReal_t* dp; + const cmFfMtx_t* mtxDescPtr = NULL; + _cmFtAnl_t* a = anlArray + j; + + // get a pointer to the matrix data + if((dp = cmFrameFileMtxReal( ffH, a->lp->ffMtxId, a->lp->ffUnitsId, &mtxDescPtr )) == NULL ) + { + rc = _cmFtError(kFrameFileFailFtRC,p,"Data access failed during post processing on feature:'%s' in '%s'.", a->lp->label,cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + // get the lenth of the feature vector + dn = mtxDescPtr->rowCnt*mtxDescPtr->colCnt; + + // processes this feature + if( procFunc(p,a,dp,dn) ) + updateFl = true; + } + + // write the frame back to disk + if( updateFl ) + if( cmFrameFileFrameUpdate(ffH) != kOkFfRC ) + { + rc = _cmFtError(kFrameFileFailFtRC,p,"Post procssing failed on record index %i in '%s'.", i, cmStringNullGuard(pp->featFn)); + goto errLabel; + } + } + + if( ffRC != kEofFfRC && ffRC != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC,p,"Post processing iterationg failed on record index %i in '%s'.",i,cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + errLabel: + return rc; +} + + +// Sum the feature vector into a->sr->rawAvg and track the global min/max value. +bool _cmFtProcRawMinMaxSum( _cmFt_t* p, _cmFtAnl_t* a, cmReal_t* v, unsigned vn ) +{ + assert( vn == a->ap->cnt ); + + cmVOR_AddVV( a->sr->rawAvgV, vn, v ); // track vector sum for use in avg + + cmVOR_MinVV( a->sr->rawMinV, vn, v ); // track min/max per vector dim + cmVOR_MaxVV( a->sr->rawMaxV, vn, v ); + + a->sr->rawMin = cmMin(a->sr->rawMin, cmVOR_Min(v,vn,1)); // track global min/max + a->sr->rawMax = cmMax(a->sr->rawMax, cmVOR_Max(v,vn,1)); + return false; +} + +// Sum the the squared diff. between feature value and feature avg into rawSdvV[] +bool _cmFtProcRawStdDev( _cmFt_t* p, _cmFtAnl_t* a, cmReal_t* v, unsigned vn ) +{ + cmReal_t t[ vn ]; + assert( vn == a->ap->cnt ); + + cmVOR_SubVVV( t, a->ap->cnt, v, a->sr->rawAvgV ); + cmVOR_PowVS( t, a->ap->cnt, 2.0 ); + cmVOR_AddVV( a->sr->rawSdvV, a->ap->cnt, t ); + + return false; +} + +bool _cmFtProcNormMinMaxSum( _cmFt_t* p, _cmFtAnl_t* a, cmReal_t* v, unsigned vn ) +{ + assert( a->ap->cnt == vn ); + + if( a->ap->normFl == false ) + { + cmVOR_Zero( a->sr->normMaxV, vn ); + cmVOR_Zero( a->sr->normMinV, vn ); + a->sr->normMin = 0; + a->sr->normMax = 0; + } + else + { + + if( a->lp->bipolarFl ) + { + // subtract mean and divide by std-dev + cmVOR_SubVV(v, vn, a->sr->rawAvgV ); + cmVOR_DivVVZ(v, vn, a->sr->rawSdvV ); + } + else + { + // scale feature into unit range based on file wide min/max + cmVOR_SubVS(v, vn, a->sr->rawMin ); + + if( a->sr->rawMax - a->sr->rawMin > 0 ) + cmVOR_DivVS(v, vn, a->sr->rawMax - a->sr->rawMin ); + else + cmVOR_Zero(v,vn); + + // convert to unit total energy (UTE) + // (this makes the vector sum to one (like a prob. distrib)) + if( vn > 1 ) + { + cmReal_t sum = cmVOR_Sum(v, vn ); + if( sum > 0 ) + cmVOR_DivVS(v, vn, sum ); + else + cmVOR_Zero(v,vn); + } + + } + + cmVOR_AddVV( a->sr->normAvgV, a->ap->cnt, v ); // track norm sum + cmVOR_MinVV( a->sr->normMinV, vn, v ); // track norm min/max per dim + cmVOR_MaxVV( a->sr->normMaxV, vn, v ); + a->sr->normMin = cmMin(a->sr->normMin, cmVOR_Min(v,vn,1)); // track norm global min/max + a->sr->normMax = cmMax(a->sr->normMax, cmVOR_Max(v,vn,1)); + + return true; + } + + return false; +} + +// calc squared diff into a->sr->normSdv[] +bool _cmFtNormStdDev( _cmFt_t* p, _cmFtAnl_t* a, cmReal_t* v, unsigned vn ) +{ + if( a->ap->normFl ) + { + assert( a->ap->cnt == vn ); + cmReal_t t[vn]; + + cmVOR_SubVVV( t, a->ap->cnt, v, a->sr->normAvgV ); + cmVOR_PowVS( t, a->ap->cnt, 2.0 ); + cmVOR_AddVV( a->sr->normSdvV, a->ap->cnt, t ); + } + + return false; +} + +// anlArray[] sorting function +int _cmFtAnlCompare( const void* pp0, const void* pp1 ) +{ + const _cmFtAnl_t* p0 = (const _cmFtAnl_t*)pp0; + const _cmFtAnl_t* p1 = (const _cmFtAnl_t*)pp1; + + assert( p0 != NULL && p0->lp !=NULL && p1!=NULL && p1->lp != NULL ); + return p0->lp->order - p1->lp->order; +} + + +cmFtRC_t _cmFtValidateAttrArray( _cmFt_t* p ) +{ + cmFtRC_t rc = kOkFtRC; + unsigned i,j; + + for(i=0; iattrCnt; ++i) + { + _cmFtLabel_t* lp = _cmFtIdToLabelPtr(p->attrArray[i].id); + + assert( lp != NULL ); + + // check for duplicate features + for(j=0; jattrCnt; ++j) + if( i!=j && p->attrArray[i].id == p->attrArray[j].id ) + { + rc = _cmFtError( kParamErrorFtRC, p, "The attribute '%s' has duplicate entries in the attribute array.", cmStringNullGuard(lp->label)); + goto errLabel; + } + + // verify that the source id for this secondary feature was specified + if( lp->srcId != kInvalidFtId ) + { + for(j=0; jattrCnt; ++j) + if( p->attrArray[j].id == lp->srcId ) + break; + + if( j == p->attrCnt ) + { + rc = _cmFtError( kParamErrorFtRC, p, "The primary feature '%s' must be specified in order to use the secondary feature '%s'.",cmStringNullGuard(_cmFtIdToLabelPtr(lp->srcId)->label),lp->label); + goto errLabel; + } + } + } + + errLabel: + return rc; +} + +cmFtRC_t cmFtAnalyzeFile( cmFtH_t h, cmFtParam_t* pp) +{ + cmFtRC_t rc = kOkFtRC; + _cmFt_t* p = _cmFtHandleToPtr(h); + cmSample_t minSmp,maxSmp,meanSmp; + cmReal_t audioSigNormFact; + cmFtInfo_t f; + unsigned frameIdx,sampleIdx; + cmFrameFileH_t ffH = cmFrameFileNullHandle; + cmAudioFileInfo_t afInfo; + _cmFtAnl_t* anlArray = NULL; + cmFtSumm_t* summArray = NULL; + unsigned i; + cmReal_t floorThreshAmpl; + + if((rc = _cmFtValidateAttrArray(p)) != kOkFtRC ) + goto errLabel; + + cmVOR_DbToAmplVV(&floorThreshAmpl,1,&pp->floorThreshDb); + + // get the audio file header information + if( cmAudioFileGetInfo(pp->audioFn, &afInfo, &p->ctx.rpt ) != kOkAfRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p, "The audio file open failed on '%s'.",cmStringNullGuard(pp->audioFn)); + goto errLabel; + } + + p->progSmpCnt = afInfo.frameCnt; + p->progSmpIdx = 0; + f.srate = afInfo.srate; + f.smpCnt = afInfo.frameCnt; + f.fftSmpCnt = cmNearPowerOfTwo( (unsigned)floor( pp->wndMs * f.srate / 1000 ) ); + f.binCnt = f.fftSmpCnt / 2 + 1; + f.hopSmpCnt = f.fftSmpCnt / pp->hopFact; + f.frmCnt = 0; + f.skipFrmCnt = 0; + f.floorFrmCnt = 0; + + + // verify that the audio channel index is valid + if( pp->chIdx >= afInfo.chCnt ) + { + rc = _cmFtError(kChIdxInvalidFtRC,p,"The channel index (%i) specified for audio file '%s' is greater than the audio file channel count.",pp->chIdx,pp->audioFn,afInfo.chCnt ); + goto errLabel; + } + + // initialize the audio file reader + if( cmAudioFileRdOpen( p->afRdPtr, f.hopSmpCnt, pp->audioFn, pp->chIdx, 0, cmInvalidIdx ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p, "The audio file reader open failed."); + goto errLabel; + } + + // get the range of sample values from this audio file for later normalization + if( cmAudioFileRdMinMaxMean( p->afRdPtr, pp->chIdx, &minSmp, &maxSmp, &meanSmp ) != cmOkRC ) + { + rc = _cmFtError(kDspProcFailFtRC,p,"Audio file min/max/mean processing failed on the audio file:'%s'.",cmStringNullGuard(pp->audioFn)); + goto errLabel; + } + + audioSigNormFact = cmMax( fabs(minSmp), fabs(maxSmp) ); + + // allocate anlArray[] + anlArray = cmMemAllocZ( _cmFtAnl_t, pp->attrCnt ); + summArray = cmMemAllocZ( cmFtSumm_t, pp->attrCnt ); + + // iniitalize anlArray[] + for(i=0; iattrCnt; ++i) + { + _cmFtAnl_t* a = anlArray + i; + + a->ap = pp->attrArray + i; + a->lp = _cmFtIdToLabelPtr(a->ap->id); + a->sr = summArray + i; + a->sr->id = a->lp->id; + } + + // sort anlArray[] into init and exec order + qsort( anlArray, pp->attrCnt, sizeof(anlArray[0]), _cmFtAnlCompare); + + // set the anlArray[i] source attribute pointer for secondary features (feat's based on other feat's) + for(i=0; iattrCnt; ++i) + if( anlArray[i].lp->srcId != kInvalidFtId ) + { + unsigned j; + for(j=0; jattrCnt; ++j) + if( i!=j && anlArray[j].lp->id == anlArray[i].lp->srcId ) + { + anlArray[i].sp = anlArray + j; + break; + } + + assert( j != pp->attrCnt ); + } + + + // initialize the feature extractors and allocate feature vector memory + if((rc = _cmFtProcInit(p, &f, pp, anlArray)) != kOkFtRC ) + goto errLabel; + + // create the output frame file + if( cmFrameFileCreate(&ffH, pp->featFn, f.srate, &p->ctx ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "The feature file '%s' could not be created.",cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + // read the next block of samples from the audio file + for(frameIdx=0,sampleIdx=0; cmAudioFileRdRead(p->afRdPtr) != cmEofRC; sampleIdx+=f.hopSmpCnt ) + { + cmSample_t aV[ p->afRdPtr->outN ]; + cmSample_t* audV = aV; + cmSample_t rms; + + p->progSmpIdx = sampleIdx; + + // if this audio buffer is fully or paritally marked as 'skip' + if( _cmFtZeroSkipSamples( pp, p->afRdPtr->outV, p->afRdPtr->outN, p->afRdPtr->curFrmIdx - p->afRdPtr->lastReadFrmCnt ) ) + { + ++f.skipFrmCnt; + continue; + } + + // if the audio buffer is zero - skip it + if((rms = cmVOS_RMS( p->afRdPtr->outV, p->afRdPtr->outN, p->afRdPtr->outN )) < floorThreshAmpl ) + { + ++f.floorFrmCnt; + continue; + } + + // normalize the audio + if( pp->normAudioFl ) + cmVOS_MultVVS( audV, p->afRdPtr->outN, p->afRdPtr->outV, audioSigNormFact ); + else + audV = p->afRdPtr->outV; + + // execute the phase vocoder + if( cmPvAnlExec(p->pvocPtr, audV, p->afRdPtr->outN )==false ) + continue; + + // create an empty frame + if( cmFrameFileFrameCreate( ffH, kFrameTypeFtId, kFrameStreamFtId, sampleIdx, 0 ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame creation failed for frame index:%i on frame file:'%s'.",frameIdx,cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + // include the incomplete file header record in the first frame + if( frameIdx == 0 ) + if((rc = _cmFtWriteFileHdr( p, &f, pp, ffH, summArray, false )) != kOkFtRC ) + goto errLabel; + + // execute each of the feature extractors and store the result + if((rc = _cmFtProcExec(p, &f, pp, ffH, anlArray, audV )) != kOkFtRC ) + goto errLabel; + + // close and write the current frame + if( cmFrameFileFrameClose( ffH ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame write failed for frame index:%i on frame file:'%s'.",frameIdx,cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + ++frameIdx; + + } + + f.frmCnt = frameIdx; + + + // update the rawAvgV[] for each feature + if( f.frmCnt > 0 ) + { + // sum feature value into a->sr->rawAvgV[] + if(( rc = _cmFtProcFile(p,ffH,pp,anlArray, _cmFtProcRawMinMaxSum )) != kOkFtRC ) + goto errLabel; + + // complete the a->sr->rawAvgV[] calc + for(i=0; iattrCnt; ++i) + cmVOR_DivVS( anlArray[i].sr->rawAvgV, anlArray[i].ap->cnt, f.frmCnt ); + + // calc sum of squared diff into a->sr->rawSdvV[] + if(( rc = _cmFtProcFile(p,ffH,pp,anlArray, _cmFtProcRawStdDev )) != kOkFtRC ) + goto errLabel; + + // complete calc of std-dev + for(i=0; iattrCnt; ++i) + { + _cmFtAnl_t* a = anlArray + i; + cmVOR_DivVS( a->sr->rawSdvV, a->ap->cnt, f.frmCnt ); + cmVOR_PowVS( a->sr->rawSdvV, a->ap->cnt, 0.5 ); + } + + // make the initial normalized vector calculation (min/max/sum) + if(( rc = _cmFtProcFile(p,ffH,pp,anlArray, _cmFtProcNormMinMaxSum )) != kOkFtRC ) + goto errLabel; + + + // complete the a->sr->normAvgV[] calculation + for(i=0; iattrCnt; ++i) + cmVOR_DivVS( anlArray[i].sr->normAvgV, anlArray[i].ap->cnt, f.frmCnt ); + + + // calc squared of squared diff into a->sr->normSdvV[] + if(( rc = _cmFtProcFile(p,ffH,pp,anlArray, _cmFtNormStdDev )) != kOkFtRC ) + goto errLabel; + + // complete the calc of norm std-dev + for(i=0; iattrCnt; ++i) + { + _cmFtAnl_t* a = anlArray + i; + cmVOR_DivVS( a->sr->normSdvV, a->ap->cnt, f.frmCnt ); + cmVOR_PowVS( a->sr->normSdvV, a->ap->cnt, 0.5 ); + + } + } + + //------------------------------------------------------------------------- + // + // rewrite the updated feature file header into the first frame + // + + // rewind to the first frame + if( cmFrameFileRewind( ffH ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file rewind failed during header update on '%s'.", cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + // make the first frame current and load it into the cmFrameFiles current frame buffer + if( cmFrameFileFrameLoadNext( ffH, kFrameTypeFtId, kFrameStreamFtId, NULL ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file load next frme failed during header update on '%s'.", cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + // copy the update header record into the current frame buffer + if((rc = _cmFtWriteFileHdr(p, &f, pp, ffH, summArray, true)) != kOkFtRC ) + goto errLabel; + + // write the updated frame back to disk + if( cmFrameFileFrameUpdate( ffH ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file frame update failed during header update on '%s'.", cmStringNullGuard(pp->featFn)); + goto errLabel; + } + + errLabel: + + if( anlArray != NULL ) + for(i=0; iattrCnt; ++i) + cmMemPtrFree(&anlArray[i].v); + + cmMemPtrFree(&anlArray); + cmMemPtrFree(&summArray); + cmFrameFileClose(&ffH); + + return rc; +} + +cmFtRC_t cmFtAnalyze( cmFtH_t h ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFt_t* p = _cmFtHandleToPtr(h); + unsigned i; + + for(i=0; iparamCnt; ++i) + { + p->progParamIdx = i; + p->progPassIdx = 0; + + if((rc = cmFtAnalyzeFile(h,p->paramArray+i)) != kOkFtRC ) + break; + } + return rc; +} + +const char* cmFtAnalyzeProgress( cmFtH_t h, unsigned* passPtr, cmReal_t* percentPtr ) +{ + _cmFt_t* p = _cmFtHandleToPtr(h); + + + if( percentPtr != NULL ) + *percentPtr = 0; + + if( passPtr != NULL) + *passPtr = 0; + + if( p->progParamIdx == cmInvalidIdx ) + return NULL; + + if( percentPtr != NULL && p->progSmpCnt > 0 ) + *percentPtr = 100.0 * p->progSmpIdx / p->progSmpCnt; + + if( passPtr != NULL ) + *passPtr = p->progPassIdx; + + return p->paramArray[ p->progParamIdx ].audioFn; +} + +cmFtRC_t _cmFtReaderClose( _cmFtFile_t* fp ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + /* + unsigned i; + + if( cmPlviewIsValid( p->plvH ) ) + for(i=0; iinfo.param.attrCnt; ++i) + if( cmPlviewFreeSource( p->plvH, fp->descArray[i].ap->id ) != kOkPlvRC ) + { + rc = _cmFtError( kPlviewFailFtRC, p, "Plview source free failed on feature '%s'.",fp->descArray[i].lp->label); + goto errLabel; + } + */ + + if( cmFrameFileClose( &fp->ffH ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file close failed."); + goto errLabel; + } + + cmMemPtrFree(&fp->descArray); + cmMemPtrFree(&fp->info.summArray); + cmMemPtrFree(&fp->info.param.skipArray); + cmMemPtrFree(&fp->info.param.attrArray); + cmMemPtrFree(&fp->hdrBuf); + cmMemPtrFree(&fp); + + errLabel: + return rc; +} + +/* +// Fill buf[rowCnt,colCnt] with data from the source submatrix located at rowIdx,colIdx. +// Return the count of elements actually copied into buf[]. +unsigned cmFtPlviewSrcFunc( void* userPtr, unsigned srcId, unsigned binIdx, unsigned frmIdx, cmReal_t* buf, unsigned binCnt, unsigned frmCnt ) +{ + assert(userPtr != NULL ); + + _cmFtFile_t* fp = (_cmFtFile_t*)userPtr; + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + cmFfRC_t rc = kOkFfRC; + const cmFfFrame_t* frmDescPtr = NULL; + const cmFfMtx_t* mtxDescPtr = NULL; + unsigned i; + + // seek to frmIdx + if((rc = cmFrameFileSeek( fp->ffH, frmIdx )) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Seek failed on plot data cmcess."); + goto errLabel; + } + + // load the frame + for(i=0; iffH, kFrameTypeFtId, kFrameStreamFtId, &frmDescPtr))==kOkFfRC; ++i) + { + const cmReal_t* dp; + const _cmFtLabel_t* lp = _cmFtIdToLabelPtr(srcId); + assert(lp != NULL); + if((dp = cmFrameFileMtxReal( fp->ffH, lp->ffMtxId, lp->ffUnitsId, kRealFmtId, &mtxDescPtr)) == NULL ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Mtx data cmcess failed on plot data access."); + goto errLabel; + } + + cmVOR_Copy( buf + (i*binCnt), binCnt, dp ); + return binCnt; + } + + errLabel: + return 0; +} +*/ + + +cmFtRC_t cmFtReaderOpen(cmFtH_t h, cmFtFileH_t* hp, const char* featFn, const cmFtInfo_t** infoPtrPtr ) +{ + cmFfRC_t ffRC = kOkFfRC; + const cmFfFile_t* fileDescPtr = NULL; + const cmFfFrame_t* frameDescPtr = NULL; + cmFtRC_t rc = kOkFtRC; + _cmFt_t* p = _cmFtHandleToPtr(h); + _cmFtFile_t* fp = cmMemAllocZ( _cmFtFile_t, 1 ); + const cmFfMtx_t* mp = NULL; + void* buf = NULL; + unsigned i,j; + //cmPlvSrc_t plvSrc; + + + if( infoPtrPtr != NULL ) + *infoPtrPtr = NULL; + + fp->h = h; + fp->ffH = cmFrameFileNullHandle; + + + // open the frame file + if( cmFrameFileOpen(&fp->ffH, featFn, &p->ctx, &fileDescPtr ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file open failed."); + goto errLabel; + } + + // load the first frame + if((ffRC = cmFrameFileFrameLoadNext( fp->ffH, kFrameTypeFtId, kFrameStreamFtId, &frameDescPtr )) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file load failed."); + goto errLabel; + } + + // read the file header + if((buf = cmFrameFileMtxBlob(fp->ffH, kDataMId, kNoUnitsUId, &mp )) == NULL ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file header read failed."); + goto errLabel; + } + + // parse the file header into fp->infoPtr + if((rc = _cmDeserializeFileHdr( p, fp, buf, mp->rowCnt*mp->colCnt )) != kOkFtRC ) + goto errLabel; + + fp->descArray = cmMemAllocZ( _cmFtDesc_t, fp->info.param.attrCnt ); + + // for each feature + for(i=0; iinfo.param.attrCnt; ++i) + { + // setup the desc array + fp->descArray[i].ap = fp->info.param.attrArray + i; + fp->descArray[i].lp = _cmFtIdToLabelPtr( fp->descArray[i].ap->id ); + + // sync descArray[] to summArray[] by matching the feature id's + for(j=0; jinfo.param.attrCnt; ++j) + if( fp->info.summArray[j].id == fp->descArray[i].lp->id ) + { + fp->descArray[i].sr = fp->info.summArray + j; + break; + } + + /* + plvSrc.id = fp->descArray[i].lp->id; + plvSrc.label = fp->descArray[i].lp->label; + plvSrc.rn = fp->descArray[i].ap->cnt; + plvSrc.cn = fp->info.frmCnt; + plvSrc.userPtr = fp; + plvSrc.srcFuncPtr = cmFtPlviewSrcFunc; + plvSrc.worldExts.xMin = 0; + plvSrc.worldExts.xMax = fp->info.frmCnt; + plvSrc.worldExts.yMin = fp->descArray[i].ap->cnt <= 1 ? fp->descArray[i].sr->rawMin : 0; + plvSrc.worldExts.yMax = fp->descArray[i].ap->cnt <= 1 ? fp->descArray[i].sr->rawMax : fp->descArray[i].ap->cnt; + + if( cmPlviewIsValid( p->plvH ) ) + if( cmPlviewAllocSource( p->plvH, &plvSrc ) != kOkPlvRC ) + { + rc = _cmFtError( kPlviewFailFtRC, p, "Plview source allocattion failed for feature '%s'.",fp->descArray[i].lp->label); + goto errLabel; + } + */ + } + + // rewind to the frame file + if((ffRC = cmFrameFileRewind( fp->ffH )) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file rewind failed."); + goto errLabel; + } + + hp->h = fp; + + if( infoPtrPtr != NULL ) + *infoPtrPtr = &fp->info; + + errLabel: + if( rc != kOkFtRC ) + _cmFtReaderClose(fp); + return rc; +} + +cmFtRC_t cmFtReaderClose( cmFtFileH_t* hp ) +{ + cmFtRC_t rc = kOkFtRC; + + if( cmFtReaderIsValid(*hp) == false ) + return rc; + + _cmFtFile_t* fp = _cmFtFileHandleToPtr(*hp); + + if((rc = _cmFtReaderClose(fp)) != kOkFtRC ) + goto errLabel; + + hp->h = NULL; + + errLabel: + return rc; +} + +bool cmFtReaderIsValid( cmFtFileH_t h ) +{ return h.h != NULL; } + +unsigned cmFtReaderFeatCount( cmFtFileH_t h ) +{ + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + return fp->info.param.attrCnt; +} + +unsigned cmFtReaderFeatId( cmFtFileH_t h, unsigned index ) +{ + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + assert( index < fp->info.param.attrCnt ); + return fp->descArray[index].lp->id; +} + + +cmFtRC_t cmFtReaderRewind( cmFtFileH_t h ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + + if(cmFrameFileRewind( fp->ffH ) != kOkFfRC ) + { + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file advance failed."); + goto errLabel; + } + + errLabel: + return rc; +} + +cmFtRC_t cmFtReaderSeek( cmFtFileH_t h, unsigned frmIdx ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + + if( cmFrameFileSeek( fp->ffH, kFrameStreamFtId, frmIdx ) != kOkFtRC ) + { + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file seek failed."); + goto errLabel; + } + + errLabel: + return rc; +} + +cmFtRC_t cmFtReaderAdvance( cmFtFileH_t h, cmFtFrameDesc_t* fdp ) +{ + cmFfRC_t ffRC = kOkFfRC; + const cmFfFrame_t* frameDescPtr = NULL; + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + + + if((ffRC = cmFrameFileFrameLoadNext( fp->ffH, kFrameTypeFtId, kFrameStreamFtId, &frameDescPtr )) != kOkFfRC ) + { + if( ffRC == kEofFfRC ) + rc = kEofFtRC; + else + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame file advance failed."); + goto errLabel; + } + } + + errLabel: + if( fdp != NULL ) + { + if( rc == kOkFtRC ) + { + fdp->smpIdx = frameDescPtr->time.sampleIdx; + fdp->frmIdx = cmFrameFileFrameLoadedIndex(fp->ffH); + } + else + { + fdp->smpIdx = cmInvalidIdx; + fdp->frmIdx = cmInvalidIdx; + } + } + return rc; +} + +cmReal_t* cmFtReaderData( cmFtFileH_t h, unsigned id, unsigned* cntPtr ) +{ + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + cmReal_t* dp = NULL; + _cmFtLabel_t* lp = _cmFtIdToLabelPtr(id); + const cmFfMtx_t* mdp = NULL; + + assert( lp != NULL ); + + if( cntPtr != NULL ) + *cntPtr = 0; + + if((dp = cmFrameFileMtxReal(fp->ffH,lp->ffMtxId,lp->ffUnitsId,&mdp)) == NULL ) + return NULL; + + if( cntPtr != NULL ) + *cntPtr = mdp->rowCnt * mdp->colCnt; + + return dp; +} + +cmFtRC_t cmFtReaderCopy( cmFtFileH_t h, unsigned featId, unsigned frmIdx, cmReal_t* buf, unsigned frmCnt, unsigned elePerFrmCnt, unsigned* outEleCntPtr ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + _cmFtLabel_t* lp = _cmFtIdToLabelPtr(featId); + + assert( lp != NULL ); + + if( cmFrameFileMtxLoadReal( fp->ffH, kFrameStreamFtId, lp->ffMtxId, lp->ffUnitsId, frmIdx, frmCnt, buf, frmCnt*elePerFrmCnt, outEleCntPtr ) != kOkFfRC ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Frame load matrix failed."); + goto errLabel; + } + + errLabel: + return rc; +} + + +cmFtRC_t cmFtReaderMultiSetup( cmFtFileH_t h, cmFtMulti_t* multiArray, unsigned multiCnt, unsigned* featVectEleCntPtr ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + unsigned i,j; + + assert( featVectEleCntPtr != NULL ); + *featVectEleCntPtr = 0; + + for(i=0; iinfo.param.attrCnt; ++j) + { + if( fp->info.param.attrArray[j].id == multiArray[i].featId ) + { + // if the multi ele cnt is -1 then use all avail ele's + if( multiArray[i].cnt == -1 ) + multiArray[i].cnt = fp->info.param.attrArray[j].cnt; + + + // verify the feature element count + if( fp->info.param.attrArray[j].cnt < multiArray[i].cnt ) + { + rc = _cmFtError( kInvalidFeatIdFtRC, p, "The requested feature element count %i is greater than the actual feature count %i in feature file '%s'.",multiArray[i].cnt,fp->info.param.attrArray[j].cnt,fp->info.param.featFn); + goto errLabel; + } + break; + } + } + + // verify that the feature attr recd was found + if( j >= fp->info.param.attrCnt ) + { + rc = _cmFtError( kInvalidFeatIdFtRC, p, "The feature %i was not used in the feature file '%s'.",multiArray[i].featId,fp->info.param.featFn); + goto errLabel; + } + + multiArray[i].id0 = lp->ffMtxId; + multiArray[i].id1 = lp->ffUnitsId; + + *featVectEleCntPtr += multiArray[i].cnt; + } + + errLabel: + return rc; +} + +cmFtRC_t cmFtReaderMultiData( cmFtFileH_t h, const cmFtMulti_t* multiArray, unsigned multiCnt, cmReal_t* outV, unsigned outN ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + unsigned i; + unsigned n = 0; + + for(i=0; iffH,m->id0,m->id1,&mdp)) == NULL ) + { + rc = _cmFtError( kFrameFileFailFtRC, p, "Matrix read failed on feature file '%s'.", fp->info.param.featFn); + goto errLabel; + } + + assert(m->cnt <= mdp->rowCnt*mdp->colCnt); + assert(n + m->cnt <= outN ); + + cmVOR_Copy(outV, m->cnt, dp ); + outV += m->cnt; + n += m->cnt; + + } + + errLabel: + return rc; + +} + +cmFtSumm_t* _cmFtReaderFindSummPtr( _cmFtFile_t* fp, unsigned featId ) +{ + unsigned i; + const cmFtParam_t* pp = &fp->info.param; + + for(i=0; iattrCnt; ++i) + if( fp->info.summArray[i].id == featId ) + return fp->info.summArray + i; + return NULL; +} + +cmFtRC_t cmFtReaderReport( cmFtFileH_t h, unsigned featId ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + const cmFtInfo_t* ip = &fp->info; + const cmFtParam_t* pp = &ip->param; + unsigned i; + cmFtSumm_t* s; + + _cmFtPrint(p,"ch:%i audio:%s\n",pp->chIdx,pp->audioFn); + _cmFtPrint(p,"wndMs:%f hopFact:%i normAudioFl:%i \n",pp->wndMs,pp->hopFact,pp->normAudioFl); + + _cmFtPrint(p,"skip:\n"); + for(i=0; iskipCnt; ++i) + _cmFtPrint(p,"idx:%10i cnt:%10i \n",pp->skipArray[i].smpIdx,pp->skipArray[i].smpCnt); + + _cmFtPrint(p,"attr:\n"); + for(i=0; iattrCnt; ++i) + _cmFtPrint(p,"cnt:%4i normFl:%i raw min:%12f max:%12f norm min:%12f max:%12f %s\n",pp->attrArray[i].cnt,pp->attrArray[i].normFl,fp->descArray[i].sr->rawMin,fp->descArray[i].sr->rawMax,fp->descArray[i].sr->normMin,fp->descArray[i].sr->normMax,cmFtFeatIdToLabel(pp->attrArray[i].id)); + + _cmFtPrint(p,"frmCnt:%i skipFrmCnt:%i floorFrmCnt:%i srate:%f fftSmpCnt:%i hopSmpCnt:%i binCnt:%i binHz:%f\n",ip->frmCnt,ip->skipFrmCnt,ip->floorFrmCnt,ip->srate,ip->fftSmpCnt,ip->hopSmpCnt,ip->binCnt,ip->srate/ip->fftSmpCnt); + + if( featId != kInvalidFtId ) + { + if((s = _cmFtReaderFindSummPtr(fp,featId)) == NULL ) + return _cmFtError( kInvalidFeatIdFtRC, p, "The feature id %i is not valid.",featId); + + _cmFtPrint(p,"feature:%s \n",_cmFtIdToLabelPtr(featId)->label); + + cmVOR_PrintLE("raw min: ", &p->ctx.rpt, 1, s->cnt, s->rawMinV ); + cmVOR_PrintLE("raw max: ", &p->ctx.rpt, 1, s->cnt, s->rawMaxV ); + cmVOR_PrintLE("raw avg: ", &p->ctx.rpt, 1, s->cnt, s->rawAvgV ); + cmVOR_PrintLE("raw sdv: ", &p->ctx.rpt, 1, s->cnt, s->rawSdvV ); + + cmVOR_PrintLE("norm min:", &p->ctx.rpt, 1, s->cnt, s->normMinV ); + cmVOR_PrintLE("norm max:", &p->ctx.rpt, 1, s->cnt, s->normMaxV ); + cmVOR_PrintLE("norm avg:", &p->ctx.rpt, 1, s->cnt, s->normAvgV ); + cmVOR_PrintLE("norm sdv:", &p->ctx.rpt, 1, s->cnt, s->normSdvV ); + } + + return rc; +} + +cmFtRC_t cmFtReaderReportFn( cmFtH_t h, const cmChar_t* fn, unsigned featId ) +{ + cmFtRC_t rc0,rc1; + cmFtFileH_t fh = cmFtFileNullHandle; + if((rc0 = cmFtReaderOpen(h,&fh,fn,NULL)) != kOkFtRC ) + return rc0; + + rc0 = cmFtReaderReport(fh,featId); + + rc1 = cmFtReaderClose(&fh); + + return rc0 != kOkFtRC ? rc0 : rc1; +} + +cmFtRC_t cmFtReaderReportFeature( cmFtFileH_t h, unsigned featId, unsigned frmIdx, unsigned frmCnt ) +{ + cmFtRC_t rc = kOkFtRC; + _cmFtFile_t* fp = _cmFtFileHandleToPtr(h); + _cmFt_t* p = _cmFtHandleToPtr(fp->h); + unsigned i; + cmFtFrameDesc_t ftFrameDesc; + + if((rc = cmFtReaderSeek(h,frmIdx)) != kOkFtRC ) + return rc; + + for(i=0; ih); + unsigned i; + cmFtFrameDesc_t ftFrameDesc; + cmFileH_t fH; + unsigned hdr[] = {0,0,0}; + unsigned maxCnt = 0; + + // create the output file + if( cmFileOpen(&fH,outFn,kWriteFileFl,p->err.rpt) != kOkFileRC ) + return _cmFtError( kFileFailFtRC, p, "Feature to binary file '%s' failed on output file creation.",outFn); + + // if frmCnt is not valid then set it to all frames past frmIdx + if( frmCnt == cmInvalidCnt ) + frmCnt = cmFrameFileFrameCount(fp->ffH,kFrameStreamFtId); + + // validate frm idx + if( frmIdx > frmCnt ) + { + rc = _cmFtError( kInvalidFrmIdxFtRC,p,"Frame index %i is invalid for frame count = %i.",frmIdx,frmCnt); + goto errLabel; + } + + // seek to the location first output frame + if((rc = cmFtReaderSeek(h,frmIdx)) != kOkFtRC ) + goto errLabel; + + hdr[0] = frmCnt; // count of frames + hdr[1] = 0; // count of elements per frame + hdr[2] = sizeof(cmReal_t); + + // write the file header + if( cmFileWrite(fH,hdr,sizeof(hdr)) != kOkFileRC ) + { + rc = _cmFtError( kFileFailFtRC,p,"The output file header write failed."); + goto errLabel; + } + + // iterate through each frame + for(i=0; i maxCnt ) + maxCnt = cnt; + } + + // rewind to the beginning of the file + if( cmFileSeek(fH,kBeginFileFl,0) != kOkFileRC ) + { + rc = _cmFtError( kFileFailFtRC,p,"Output file rewind failed."); + goto errLabel; + } + + // rewrite the header + hdr[1] = maxCnt; + if( cmFileWrite(fH,hdr,sizeof(hdr)) != kOkFileRC ) + { + rc = _cmFtError( kFileFailFtRC,p,"The output file header re-write failed."); + goto errLabel; + } + + errLabel: + + if( cmFileIsValid(fH) ) + if( cmFileClose(&fH) != kOkFileRC ) + _cmFtError( kFileFailFtRC,p,"Output file close failed."); + + return rc; +} + +cmFtRC_t cmFtReaderToBinaryFn(cmFtH_t h, const cmChar_t* fn, unsigned featId, unsigned frmIdx, unsigned frmCnt, const cmChar_t* outFn ) +{ + cmFtRC_t rc = kOkFtRC; + cmFtFileH_t fH = cmFtFileNullHandle; + + if((rc = cmFtReaderOpen(h,&fH,fn,NULL)) != kOkFtRC ) + return rc; + + rc = cmFtReaderToBinary(fH,featId,frmIdx,frmCnt,outFn); + + cmFtRC_t rc1 = cmFtReaderClose(&fH); + + return rc==kOkFtRC ? rc1 : rc; +} diff --git a/cmFeatFile.h b/cmFeatFile.h new file mode 100644 index 0000000..810838f --- /dev/null +++ b/cmFeatFile.h @@ -0,0 +1,279 @@ +/// \file cmFeatFile.h +/// \brief Audio file acoustic feature analyzer and accompanying file reader. +/// +/// + +#ifndef cmFeatFile_h +#define cmFeatFile_h + +#ifdef __cplusplus +extern "C" { +#endif + + + + /// Result codes for all functions in cmFeatFile.h + enum + { + kOkFtRC = cmOkRC, + kCfgParseFailFtRC, + kFileSysFailFtRC, + kJsonFailFtRC, + kDspProcFailFtRC, + kDirCreateFailFtRC, + kFileNotFoundFtRC, + kAudioFileOpenFailFtRC, + kFrameFileFailFtRC, + kChIdxInvalidFtRC, + kParamRangeFtRC, + kParamErrorFtRC, + kFrameWriteFailFtRC, + kEofFtRC, + kPlviewFailFtRC, + kSerialFailFtRC, + kInvalidFeatIdFtRC, + kFileFailFtRC, + kInvalidFrmIdxFtRC + }; + + /// Feature Id's + enum + { + kInvalidFtId, ///< 0 + kAmplFtId, ///< 1 Fourier transform amplitude + kDbAmplFtId, ///< 2 Fourier transform decibel + kPowFtId, ///< 3 Fourier transform power + kDbPowFtId, ///< 4 Fourier transform power decibel + kPhaseFtId, ///< 5 Fourier transform phase (not unwrapped) + kBfccFtId, ///< 6 Bark Frequency Cepstral Coeffcients + kMfccFtId, ///< 7 Mel Frequency Cepstral Coefficients + kCepsFtId, ///< 8 Cepstral Coefficients + kConstQFtId, ///< 9 Constant-Q transform + kLogConstQFtId, ///< 10 Log Constant-Q transform + kRmsFtId, ///< 11 Root means square of the audio signal + kDbRmsFtId, ///< 12 RMS in decibels + + kD1AmplFtId, ///< 13 1st order difference over time of the Fourier transform amplitude + kD1DbAmplFtId, ///< 14 1st order difference over time of the Fourier transform decibel + kD1PowFtId, ///< 15 1st order difference over time of the Fourier transform power + kD1DbPowFtId, ///< 16 1st order difference over time of the Fourier transform power decibel + kD1PhaseFtId, ///< 17 1st order difference over time of the Fourier transform phase (not unwrapped) + kD1BfccFtId, ///< 18 1st order difference over time of the Bark Frequency Cepstral Coeffcients + kD1MfccFtId, ///< 19 1st order difference over time of the Mel Frequency Cepstral Coefficients + kD1CepsFtId, ///< 20 1st order difference over time of the Cepstral Coefficients + kD1ConstQFtId, ///< 21 1st order difference over time of the Constant-Q transform + kD1LogConstQFtId, ///< 22 1st order difference over time of the Log Constant-Q transform + kD1RmsFtId, ///< 23 1st order difference over time of the Root means square of the audio signal + kD1DbRmsFtId, ///< 24 1st order difference over time of the RMS in decibels + + }; + + /// User defined feature parameters + typedef struct + { + unsigned id; ///< feature id + unsigned cnt; ///< length of feature vector + bool normFl; ///< normalize this feature + bool enableFl; ///< true if this feature is enabled + } cmFtAttr_t; + + + /// Skip input audio range record + typedef struct + { + unsigned smpIdx; ///< Index of first sample to skip + unsigned smpCnt; ///< Count of successive samples to skip. + } cmFtSkip_t; + + + /// Analysis parameters + typedef struct + { + const char* audioFn; ///< Audio file name. + const char* featFn; ///< Feature file name. + unsigned chIdx; ///< Audio file channel index + cmReal_t wndMs; ///< Length of the analysis window in milliseconds. + unsigned hopFact; ///< Analysis window overlap factor 1 = 1:1 2=2:1 ... + bool normAudioFl; ///< Normalize the audio over the length of the audio file + cmMidiByte_t constQMinPitch; ///< Used to determine the base const-q octave. + cmMidiByte_t constQMaxPitch; ///< Used to determine the maximum const-q frequency of interest. + unsigned constQBinsPerOctave; ///< Bands per const-q octave. + unsigned onsetMedFiltWndSmpCnt; ///< Complex onset median filter + cmReal_t onsetThreshold; ///< Complex onset threshold + cmReal_t minDb; ///< Fourier Transform magnitude values below minDb are set to minDb. + cmReal_t floorThreshDb; ///< Frames with an RMS below this value will be skipped + cmFtSkip_t* skipArray; ///< skipArray[skipCnt] user defined sample skip ranges + unsigned skipCnt; ///< Count of records in skipArray[]. + cmFtAttr_t* attrArray; ///< attrArray[attrCnt] user defined parameter array + unsigned attrCnt; ///< Count of records in attrArray[]. + } cmFtParam_t; + + + /// Feature summary information + typedef struct + { + unsigned id; ///< feature id (same as associated cmFtAttr.id) + unsigned cnt; ///< length of each feature vector (same as associated cmFtAttr.cnt) + + /// The raw feature summary values are calculated prior to normalization. + cmReal_t* rawMinV; ///< Vector of min value over time for each feature element. + cmReal_t* rawMaxV; ///< Vector of max value over time for each feature element. + cmReal_t* rawAvgV; ///< Vector of avg value over time for each feature element. + cmReal_t* rawSdvV; ///< Vector of standard deviation values over time for each feature element. + cmReal_t rawMin; ///< Min value of all values for this feature. Equivalent to min(rawMinV). + cmReal_t rawMax; ///< Max value of all values for this feature. Equivalent to max(rawMaxV). + + /// normalized feature summary values + cmReal_t* normMinV; ///< Vector of min value over time for each feature element. + cmReal_t* normMaxV; ///< Vector of max value over time for each feature element. + cmReal_t* normAvgV; ///< Vector of avg value over time for each feature element. + cmReal_t* normSdvV; ///< Vector of standard deviation values over time for each feature element. + cmReal_t normMin; ///< Min value of all values for this feature. Equivalent to min(normMinV). + cmReal_t normMax; ///< Max value of all values for this feature. Equivalent to max(rawMaxV). + + } cmFtSumm_t; + + /// Feature file info record + typedef struct + { + unsigned frmCnt; ///< count of frames in the file + cmReal_t srate; ///< audio sample rate + unsigned smpCnt; ///< audio sample count + unsigned fftSmpCnt; ///< FFT window length (always power of 2) + unsigned hopSmpCnt; ///< audio sample hop count + unsigned binCnt; ///< FFT bin count (always fftSmpCnt/2 + 1) + unsigned skipFrmCnt; ///< count of frames skipped based on user skip array + unsigned floorFrmCnt; ///< count of frames skipped because below floorThreshDb + cmFtParam_t param; ///< analysis parameter record used to form this feature file + cmFtSumm_t* summArray; ///< summArray[ param.attrCnt ] feature summary information + } cmFtInfo_t; + + /// Data structure returned by cmFtReaderAdvance(). + typedef struct + { + unsigned smpIdx; ///< The audio signal sample index this frames information is based on. + unsigned frmIdx; ///< The frame index relative to other frames in this feature file. + } cmFtFrameDesc_t; + + typedef cmHandle_t cmFtH_t; ///< Analyzer handle + typedef cmHandle_t cmFtFileH_t; ///< Feature file handle. + typedef unsigned cmFtRC_t; ///< Result code type used by all functions in cmFeatFile.h. + + extern cmFtH_t cmFtNullHandle; ///< A NULL handle useful for indicating an uninitialized analyzer. + extern cmFtFileH_t cmFtFileNullHandle; ///< A NULL handle useful for indicating an uninitialized feature file. + + + /// Given a feature type id return the associated label. + const char* cmFtFeatIdToLabel( unsigned featId ); + + /// Given a feature type label return the associated id. + unsigned cmFtFeatLabelToId( const char* label ); + + /// \name Feature Analyzer Related functions + ///@{ + + /// Initialize the feature analyzer. The memory manager and file system must + /// be initialized (cmMdInitialize(), cmFsInitialize()) prior to calling this function. + cmFtRC_t cmFtInitialize( cmFtH_t* hp, cmCtx_t* ctx ); + + /// Finalize a feature analyzer. + cmFtRC_t cmFtFinalize( cmFtH_t* h ); + + /// Return true if the handle represents an initialized feature analyzer. + bool cmFtIsValid( cmFtH_t h ); + + /// Parse a JSON file containing a set of analysis parameters. + cmFtRC_t cmFtParse( cmFtH_t h, const char* cfgFn ); + + /// Run the analyzer. + cmFtRC_t cmFtAnalyze( cmFtH_t h ); + + /// If cmFtAnalyze() is being run in a seperate thread this function + /// can be used to access the analyzers progress. + const char* cmFtAnalyzeProgress( cmFtH_t h, unsigned* passPtr, cmReal_t* percentPtr ); + + ///@} + + /// \name Feature File Related Functions + ///@{ + + /// Open a feature file. + /// Note that inforPtrPtr is optional and will be ignored if it is set to NULL. + cmFtRC_t cmFtReaderOpen( cmFtH_t h, cmFtFileH_t* hp, const char* featFn, const cmFtInfo_t** infoPtrPtr ); + + /// Close a feature file. + cmFtRC_t cmFtReaderClose( cmFtFileH_t* hp ); + + /// Return true if the handle reprents an open feature file. + bool cmFtReaderIsValid( cmFtFileH_t h ); + + /// Return the count of features types this file contains. + unsigned cmFtReaderFeatCount( cmFtFileH_t h ); + + /// Return the feature type id associated with the specified index. + unsigned cmFtReaderFeatId( cmFtFileH_t h, unsigned index ); + + /// Reset the current file location to the first frame but do not load it. + /// The next call to cmFtReadAdvance() will load the next frame. + cmFtRC_t cmFtReaderRewind( cmFtFileH_t h ); + + /// Make frmIdx the current file location. + cmFtRC_t cmFtReaderSeek( cmFtFileH_t h, unsigned frmIdx ); + + /// Load the current frame, advance the current file position, and return + /// a pointer to a cmFtFrameDesc_t record for the loaded frame. + /// Returns kEofFtRC upon reaching end of file. + /// The frameDescPtr is optional. + cmFtRC_t cmFtReaderAdvance( cmFtFileH_t h, cmFtFrameDesc_t* frameDescPtr ); + + /// Returns a pointer to a data matrix in the feature identified by featId in the current feature frame. + cmReal_t* cmFtReaderData( cmFtFileH_t h, unsigned featId, unsigned* cntPtr ); + + /// Copy the contents of a given set of frames into buf[frmCnt*elePerFrmCnt]. + cmFtRC_t cmFtReaderCopy( cmFtFileH_t h, unsigned featId, unsigned frmIdx, cmReal_t* buf, unsigned frmCnt, unsigned elePerFrmCnt, unsigned* outEleCntPtr ); + + /// Data structure used to specify multiple features for use by cmFtReaderMultiSetup(). + typedef struct + { + unsigned featId; ///< Feature id of feature to include in the feature vector + unsigned cnt; ///< Set to count of feat ele's for this feat. Error if greater than avail. Set to -1 to use all avail ele's. + /// returned with actual count used + + unsigned id0; ///< Ignored on input. Used internally by cmFtReaderXXX() + unsigned id1; ///< Ignored on input. Used internally by cmFtReaderXXX() + } cmFtMulti_t; + + /// Setup an array of cmFtMulti_t records. The cmFtMulti_t array + /// used by cmFtReaderMulitData() must be initialized by this function. + cmFtRC_t cmFtReaderMultiSetup( cmFtFileH_t h, cmFtMulti_t* multiArray, unsigned multiCnt, unsigned* featVectEleCntPtr ); + + /// Fill outV[outN] with a consecutive data from the features specified in the cmFtMulti_t array. + /// Use cmFtReaderMultiSetup() to configure the cmFtMulti_t array prior to calling this function. + cmFtRC_t cmFtReaderMultiData( cmFtFileH_t h, const cmFtMulti_t* multiArray, unsigned multiCnt, cmReal_t* outV, unsigned outN ); + + /// Report summary information for the specified feature. + cmFtRC_t cmFtReaderReport( cmFtFileH_t h, unsigned featId ); + + /// Identical to cmFtReaderReport() except the feature file is identified from a file name rather than an open cmFtFileH_t. + cmFtRC_t cmFtReaderReportFn( cmFtH_t h, const cmChar_t* fn, unsigned featId ); + + /// Report feature data for the specified set of feature frames. + cmFtRC_t cmFtReaderReportFeature( cmFtFileH_t h, unsigned featId, unsigned frmIdx, unsigned frmCnt ); + + /// Write a feature into a binary file. + /// Set 'frmCnt' to the cmInvalidCnt to include all frames past frmIdx. + /// The first three unsigned values in the output file + /// contain the row count, maximum column count, and the count of bytes in each data element (4=float,8=double). + /// Each row of the file begins with the count of elements in the row and is followed by a data array. + cmFtRC_t cmFtReaderToBinary( cmFtFileH_t h, unsigned featId, unsigned frmIdx, unsigned frmCnt, const cmChar_t* outFn ); + + /// Identical to cmFtReaderToBinary() except it takes a feature file name instead of a file handle. + cmFtRC_t cmFtReaderToBinaryFn( cmFtH_t h, const cmChar_t* fn, unsigned featId, unsigned frmIdx, unsigned frmCnt, const cmChar_t* outFn ); + + ///@} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmFile.c b/cmFile.c new file mode 100644 index 0000000..da337f3 --- /dev/null +++ b/cmFile.c @@ -0,0 +1,555 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmFile.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include +cmFileH_t cmFileNullHandle = { NULL }; + +typedef struct +{ + FILE* fp; + cmErr_t err; + cmChar_t* fnStr; +} cmFile_t; + +cmFile_t* _cmFileHandleToPtr( cmFileH_t h ) +{ + cmFile_t* p = (cmFile_t*)h.h; + assert(p != NULL); + return p; +} + +cmFileRC_t _cmFileError( cmFile_t* p, cmFileRC_t rc, int errNumb, const cmChar_t* msg ) +{ + if(errNumb == 0) + rc = cmErrMsg(&p->err,rc,"%s on file '%s'",msg,p->fnStr); + else + rc = cmErrMsg(&p->err,rc,"%s on file '%s'\nSystem Msg:%s",msg,p->fnStr,strerror(errNumb)); + + return rc; +} + +cmFileRC_t cmFileOpen( cmFileH_t* hp, const cmChar_t* fn, enum cmFileOpenFlags_t flags, cmRpt_t* rpt ) +{ + char mode[] = "/0/0/0"; + cmFile_t* p = NULL; + cmErr_t err; + cmFileRC_t rc; + + if((rc = cmFileClose(hp)) != kOkFileRC ) + return rc; + + cmErrSetup(&err,rpt,"File"); + + hp->h = NULL; + + if( cmIsFlag(flags,kReadFileFl) ) + mode[0]='r'; + else + if( cmIsFlag(flags,kWriteFileFl) ) + mode[0]='w'; + else + if( cmIsFlag(flags,kAppendFileFl) ) + mode[0]='a'; + else + cmErrMsg(&err,kInvalidFlagFileRC,"File open flags must contain 'kReadFileFl','kWriteFileFl', or 'kAppendFileFl'."); + + if( cmIsFlag(flags,kUpdateFileFl) ) + mode[1]='+'; + + if( fn == NULL ) + return cmErrMsg(&err,kObjAllocFailFileRC,"File object allocation failed due to empty file name."); + + unsigned byteCnt = sizeof(cmFile_t) + strlen(fn) + 1; + + if((p = (cmFile_t*)cmMemMallocZ(byteCnt)) == NULL ) + return cmErrMsg(&err,kObjAllocFailFileRC,"File object allocation failed for file '%s'.",cmStringNullGuard(fn)); + + cmErrClone(&p->err,&err); + + p->fnStr = (cmChar_t*)(p+1); + strcpy(p->fnStr,fn); + + + errno = 0; + if((p->fp = fopen(fn,mode)) == NULL ) + { + cmFileRC_t rc = _cmFileError(p,kOpenFailFileRC,errno,"File open failed"); + cmMemFree(p); + return rc; + } + + hp->h = p; + + return kOkFileRC; +} + +cmFileRC_t cmFileClose( cmFileH_t* hp ) +{ + if( cmFileIsValid(*hp) == false ) + return kOkFileRC; + + cmFile_t* p = _cmFileHandleToPtr(*hp); + + errno = 0; + if( p->fp != NULL ) + if( fclose(p->fp) != 0 ) + return _cmFileError(p,kCloseFailFileRC,errno,"File close failed"); + + cmMemFree(p); + hp->h = NULL; + + return kOkFileRC; +} + +bool cmFileIsValid( cmFileH_t h ) +{ return h.h != NULL; } + +cmFileRC_t cmFileRead( cmFileH_t h, void* buf, unsigned bufByteCnt ) +{ + cmFile_t* p = _cmFileHandleToPtr(h); + + errno = 0; + if( fread(buf,bufByteCnt,1,p->fp) != 1 ) + return _cmFileError(p,kReadFailFileRC,errno,"File read failed"); + + return kOkFileRC; +} + +cmFileRC_t cmFileWrite( cmFileH_t h, const void* buf, unsigned bufByteCnt ) +{ + cmFile_t* p = _cmFileHandleToPtr(h); + + errno = 0; + if( fwrite(buf,bufByteCnt,1,p->fp) != 1 ) + return _cmFileError(p,kWriteFailFileRC,errno,"File write failed"); + + return kOkFileRC; +} + +cmFileRC_t cmFileSeek( cmFileH_t h, enum cmFileSeekFlags_t flags, int offsByteCnt ) +{ + cmFile_t* p = _cmFileHandleToPtr(h); + unsigned fileflags = 0; + + if( cmIsFlag(flags,kBeginFileFl) ) + fileflags = SEEK_SET; + else + if( cmIsFlag(flags,kCurFileFl) ) + fileflags = SEEK_CUR; + else + if( cmIsFlag(flags,kEndFileFl) ) + fileflags = SEEK_END; + else + return cmErrMsg(&p->err,kInvalidFlagFileRC,"Invalid file seek flag on '%s'.",p->fnStr); + + errno = 0; + if( fseek(p->fp,offsByteCnt,fileflags) != 0 ) + return _cmFileError(p,kSeekFailFileRC,errno,"File seek failed"); + + return kOkFileRC; +} + +cmFileRC_t cmFileTell( cmFileH_t h, long* offsPtr ) +{ + assert( offsPtr != NULL ); + *offsPtr = -1; + cmFile_t* p = _cmFileHandleToPtr(h); + errno = 0; + if((*offsPtr = ftell(p->fp)) == -1) + return _cmFileError(p,kTellFailFileRC,errno,"File tell failed"); + return kOkFileRC; +} + + +bool cmFileEof( cmFileH_t h ) +{ return feof( _cmFileHandleToPtr(h)->fp ) != 0; } + + +unsigned cmFileByteCount( cmFileH_t h ) +{ + struct stat sr; + int f; + cmFile_t* p = _cmFileHandleToPtr(h); + const cmChar_t errMsg[] = "File byte count request failed."; + + errno = 0; + + if((f = fileno(p->fp)) == -1) + { + _cmFileError(p,kHandleInvalidFileRC,errno,errMsg); + return 0; + } + + if(fstat(f,&sr) == -1) + { + _cmFileError(p,kStatFailFileRC,errno,errMsg); + return 0; + } + + return sr.st_size; +} + +const cmChar_t* cmFileName( cmFileH_t h ) +{ + cmFile_t* p = _cmFileHandleToPtr(h); + return p->fnStr; +} + +cmFileRC_t cmFileFnWrite( const cmChar_t* fn, cmRpt_t* rpt, const void* buf, unsigned bufByteCnt ) +{ + cmFileH_t h = cmFileNullHandle; + cmFileRC_t rc; + + if((rc = cmFileOpen(&h,fn,kWriteFileFl,rpt)) != kOkFileRC ) + goto errLabel; + + rc = cmFileWrite(h,buf,bufByteCnt); + + errLabel: + cmFileClose(&h); + + return rc; +} + +cmChar_t* _cmFileToBuf( cmFileH_t h, unsigned nn, unsigned* bufByteCntPtr ) +{ + errno = 0; + + unsigned n = cmFileByteCount(h); + cmChar_t* buf = NULL; + cmFile_t* p = _cmFileHandleToPtr(h); + + + // if the file size calculation is ok + if( errno != 0 ) + { + _cmFileError(p,kBufAllocFailFileRC,errno,"Invalid file buffer length."); + goto errLabel; + } + + // allocate the read target buffer + if((buf = cmMemAlloc(cmChar_t,n+nn)) == NULL) + { + _cmFileError(p,kBufAllocFailFileRC,0,"Read buffer allocation failed."); + goto errLabel; + } + + // read the file + if( cmFileRead(h,buf,n) != kOkFileRC ) + goto errLabel; + + // zero memory after the file data + memset(buf+n,0,nn); + + if( bufByteCntPtr != NULL ) + *bufByteCntPtr = n; + + return buf; + + errLabel: + if( bufByteCntPtr != NULL ) + *bufByteCntPtr = 0; + + cmMemFree(buf); + + return NULL; + +} + +cmChar_t* _cmFileFnToBuf( const cmChar_t* fn, cmRpt_t* rpt, unsigned nn, unsigned* bufByteCntPtr ) +{ + cmFileH_t h = cmFileNullHandle; + cmChar_t* buf = NULL; + + if( cmFileOpen(&h,fn,kReadFileFl | kBinaryFileFl,rpt) != kOkFileRC ) + goto errLabel; + + buf = _cmFileToBuf(h,nn,bufByteCntPtr); + + errLabel: + cmFileClose(&h); + + return buf; +} + +cmChar_t* cmFileToBuf( cmFileH_t h, unsigned* bufByteCntPtr ) +{ return _cmFileToBuf(h,0,bufByteCntPtr); } + +cmChar_t* cmFileFnToBuf( const cmChar_t* fn, cmRpt_t* rpt, unsigned* bufByteCntPtr ) +{ return _cmFileFnToBuf(fn,rpt,0,bufByteCntPtr); } + +cmChar_t* cmFileToStr( cmFileH_t h, unsigned* bufByteCntPtr ) +{ return _cmFileToBuf(h,1,bufByteCntPtr); } + +cmChar_t* cmFileFnToStr( const cmChar_t* fn, cmRpt_t* rpt, unsigned* bufByteCntPtr ) +{ return _cmFileFnToBuf(fn,rpt,1,bufByteCntPtr); } + +cmFileRC_t cmFileLineCount( cmFileH_t h, unsigned* lineCntPtr ) +{ + cmFileRC_t rc = kOkFileRC; + cmFile_t* p = _cmFileHandleToPtr(h); + unsigned lineCnt = 0; + long offs; + int c; + + + assert( lineCntPtr != NULL ); + *lineCntPtr = 0; + + if((rc = cmFileTell(h,&offs)) != kOkFileRC ) + return rc; + + errno = 0; + + while(1) + { + c = fgetc(p->fp); + + if( c == EOF ) + { + if( errno ) + rc =_cmFileError(p,kReadFailFileRC,errno,"File read char failed"); + else + ++lineCnt; // add one in case the last line isn't terminated with a '\n'. + + break; + } + + // if an end-of-line was encountered + if( c == '\n' ) + ++lineCnt; + + } + + if((rc = cmFileSeek(h,kBeginFileFl,offs)) != kOkFileRC ) + return rc; + + *lineCntPtr = lineCnt; + + return rc; +} + +cmFileRC_t _cmFileGetLine( cmFile_t* p, cmChar_t* buf, unsigned* bufByteCntPtr ) +{ + // fgets() reads up to n-1 bytes into buf[] + if( fgets(buf,*bufByteCntPtr,p->fp) == NULL ) + { + // an read error or EOF condition occurred + *bufByteCntPtr = 0; + + if( !feof(p->fp ) ) + return _cmFileError(p,kReadFailFileRC,errno,"File read line failed"); + + return kReadFailFileRC; + } + + return kOkFileRC; +} + +cmFileRC_t cmFileGetLine( cmFileH_t h, cmChar_t* buf, unsigned* bufByteCntPtr ) +{ + assert( bufByteCntPtr != NULL ); + cmFile_t* p = _cmFileHandleToPtr(h); + unsigned tn = 128; + cmChar_t t[ tn ]; + unsigned on = *bufByteCntPtr; + long offs; + cmFileRC_t rc; + + // store the current file offset + if((rc = cmFileTell(h,&offs)) != kOkFileRC ) + return rc; + + // if no buffer was given then use t[] + if( buf == NULL || *bufByteCntPtr == 0 ) + { + *bufByteCntPtr = tn; + buf = t; + } + + // fill the buffer from the current line + if((rc = _cmFileGetLine(p,buf,bufByteCntPtr)) != kOkFileRC ) + return rc; + + // get length of the string in the buffer + // (this is one less than the count of bytes written to the buffer) + unsigned n = strlen(buf); + + // if the provided buffer was large enough to read the entire string + if( on > n+1 ) + { + //*bufByteCntPtr = n+1; + return kOkFileRC; + } + + // + // the provided buffer was not large enough + // + + // m tracks the length of the string + unsigned m = n; + + while( n+1 == *bufByteCntPtr ) + { + // fill the buffer from the current line + if((rc = _cmFileGetLine(p,buf,bufByteCntPtr)) != kOkFileRC ) + return rc; + + n = strlen(buf); + m += n; + } + + // restore the original file offset + if((rc = cmFileSeek(h,kBeginFileFl,offs)) != kOkFileRC ) + return rc; + + // add 1 for /0, 1 for /n and 1 to detect buf-too-short + *bufByteCntPtr = m+3; + + return kBufTooSmallFileRC; + +} + +cmFileRC_t cmFileGetLineAuto( cmFileH_t h, cmChar_t** bufPtrPtr, unsigned* bufByteCntPtr ) +{ + cmFileRC_t rc = kOkFileRC; + bool fl = true; + cmChar_t* buf = *bufPtrPtr; + + *bufPtrPtr = NULL; + + while(fl) + { + fl = false; + + switch( rc = cmFileGetLine(h,buf,bufByteCntPtr) ) + { + case kOkFileRC: + { + *bufPtrPtr = buf; + } + break; + + case kBufTooSmallFileRC: + buf = cmMemResizeZ(cmChar_t,buf,*bufByteCntPtr); + fl = true; + break; + + default: + cmMemFree(buf); + break; + } + } + + + + return rc; +} + +cmFileRC_t cmFileReadChar( cmFileH_t h, char* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadUChar( cmFileH_t h, unsigned char* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadShort( cmFileH_t h, short* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadUShort( cmFileH_t h, unsigned short* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadLong( cmFileH_t h, long* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadULong( cmFileH_t h, unsigned long* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadInt( cmFileH_t h, int* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadUInt( cmFileH_t h, unsigned int* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadFloat( cmFileH_t h, float* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadDouble( cmFileH_t h, double* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileReadBool( cmFileH_t h, bool* buf, unsigned cnt ) +{ return cmFileRead(h,buf,sizeof(buf[0])*cnt); } + + + +cmFileRC_t cmFileWriteChar( cmFileH_t h, const char* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteUChar( cmFileH_t h, const unsigned char* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteShort( cmFileH_t h, const short* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteUShort( cmFileH_t h, const unsigned short* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteLong( cmFileH_t h, const long* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteULong( cmFileH_t h, const unsigned long* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteInt( cmFileH_t h, const int* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteUInt( cmFileH_t h, const unsigned int* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteFloat( cmFileH_t h, const float* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteDouble( cmFileH_t h, const double* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + +cmFileRC_t cmFileWriteBool( cmFileH_t h, const bool* buf, unsigned cnt ) +{ return cmFileWrite(h,buf,sizeof(buf[0])*cnt); } + + + + +cmFileRC_t cmFilePrint( cmFileH_t h, const cmChar_t* text ) +{ + cmFile_t* p = _cmFileHandleToPtr(h); + + errno = 0; + if( fputs(text,p->fp) < 0 ) + return _cmFileError(p,kPrintFailFileRC,errno,"File print failed"); + + return kOkFileRC; +} + + +cmFileRC_t cmFileVPrintf( cmFileH_t h, const cmChar_t* fmt, va_list vl ) +{ + cmFile_t* p = _cmFileHandleToPtr(h); + + if( vfprintf(p->fp,fmt,vl) < 0 ) + return _cmFileError(p,kPrintFailFileRC,errno,"File print failed"); + + return kOkFileRC; +} + +cmFileRC_t cmFilePrintf( cmFileH_t h, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + cmFileRC_t rc = cmFileVPrintf(h,fmt,vl); + va_end(vl); + return rc; +} + + diff --git a/cmFile.h b/cmFile.h new file mode 100644 index 0000000..987f007 --- /dev/null +++ b/cmFile.h @@ -0,0 +1,203 @@ +//{ +//( +// File abstraction class which slightly extends the C standard file handling routines +// to cm style error handling. +//) +// +#ifndef cmFile_h +#define cmFile_h + +#ifdef __cplusplus +extern "C" { +#endif + + //( + enum + { + kOkFileRC = cmOkRC, + kInvalidFlagFileRC, + kOpenFailFileRC, + kCloseFailFileRC, + kReadFailFileRC, + kWriteFailFileRC, + kSeekFailFileRC, + kTellFailFileRC, + kPrintFailFileRC, + kObjAllocFailFileRC, + kHandleInvalidFileRC, + kStatFailFileRC, + kBufAllocFailFileRC, + kBufTooSmallFileRC + }; + + typedef unsigned cmFileRC_t; + typedef cmHandle_t cmFileH_t; + + extern cmFileH_t cmFileNullHandle; + + // Flags for use with cmFileOpen(). + enum cmFileOpenFlags_t + { + kReadFileFl = 0x01, //< Open a file for reading + kWriteFileFl = 0x02, //< Create an empty file for writing + kAppendFileFl = 0x04, //< Open a file for writing at the end of the file. + kUpdateFileFl = 0x08, //< Open a file for reading and writing. + kBinaryFileFl = 0x10 //< Open a file for binary (not text) input/output. + }; + + // Open or create a file. + // Equivalent to fopen(). + // If *hp was not initalized by an earlier call to cmFileOpen() then it should + // be set to cmFileNullHandle prior to calling this function. If *hp is a valid handle + // then it is automatically finalized by an internal call to cmFileClose() prior to + // being re-iniitalized. + cmFileRC_t cmFileOpen( + cmFileH_t* hp, // Pointer to a client supplied cmFileHandle_t to recieve the handle for the new object. + const cmChar_t* fn, // The name of the file to open or create. + enum cmFileOpenFlags_t flags, // See cmFileOpenFlags_t + cmRpt_t* rpt // The cmRpt_t to use for error reporting + ); + + // Close a file opened with Equivalent to fclose(). + cmFileRC_t cmFileClose( cmFileH_t* hp ); + + // Return true if the file handle is associated with an open file. + bool cmFileIsValid( cmFileH_t h ); + + // Read a block bytes from a file. Equivalent to fread(). + cmFileRC_t cmFileRead( cmFileH_t h, void* buf, unsigned bufByteCnt ); + + // Write a block of bytes to a file. Equivalent to fwrite(). + cmFileRC_t cmFileWrite( cmFileH_t h, const void* buf, unsigned bufByteCnt ); + + enum cmFileSeekFlags_t + { + kBeginFileFl = 0x01, + kCurFileFl = 0x02, + kEndFileFl = 0x04 + }; + + // Set the file position indicator. Equivalent to fseek(). + cmFileRC_t cmFileSeek( cmFileH_t h, enum cmFileSeekFlags_t flags, int offsByteCnt ); + + // Return the file position indicator. Equivalent to ftell(). + cmFileRC_t cmFileTell( cmFileH_t h, long* offsPtr ); + + // Return true if the file position indicator is at the end of the file. + // Equivalent to feof(). + bool cmFileEof( cmFileH_t h ); + + // Return the length of the file in bytes + unsigned cmFileByteCount( cmFileH_t h ); + + // Return the file name associated with a file handle. + const cmChar_t* cmFileName( cmFileH_t h ); + + // Write a buffer to a file. + cmFileRC_t cmFileFnWrite( const cmChar_t* fn, cmRpt_t* rpt, const void* buf, unsigned bufByteCnt ); + + // Allocate and fill a buffer from the file. + // Set *bufByteCntPtr to count of bytes read into the buffer. + // 'bufByteCntPtr' is optional - set it to NULL if it is not required by the caller. + // It is the callers responsibility to delete the returned buffer with a + // call to cmMemFree() + cmChar_t* cmFileToBuf( cmFileH_t h, unsigned* bufByteCntPtr ); + + // Same as cmFileToBuf() but accepts a file name argument. + // 'rpt' is the report object to use for error reporting. + cmChar_t* cmFileFnToBuf( const cmChar_t* fn, cmRpt_t* rpt, unsigned* bufByteCntPtr ); + + // Allocate and fill a zero terminated string from a file. + // Set *bufByteCntPtr to count of bytes read into the buffer.= + // (the buffer memory size is one byte larger to account for the terminating zero) + // 'bufByteCntPtr' is optional - set it to NULL if it is not required by the caller. + // It is the callers responsibility to delete the returned buffer with a + // call to cmMemFree() + cmChar_t* cmFileToStr( cmFileH_t h, unsigned* bufByteCntPtr ); + + // Same as cmFileToBuf() but accepts a file name argument. + // 'rpt' is the report object to use for error reporting. + cmChar_t* cmFileFnToStr( const cmChar_t* fn, cmRpt_t* rpt, unsigned* bufByteCntPtr ); + + // Return the count of lines in a file. + cmFileRC_t cmFileLineCount( cmFileH_t h, unsigned* lineCntPtr ); + + // Read the next line into buf[bufByteCnt]. + // Consider using cmFileGetLineAuto() as an alternative to this function + // to avoid having to use a buffer with an explicit size. + // + // If buf is not long enough to hold the entire string then + // + // 1. The function returns kFileBufTooSmallRC + // 2. *bufByteCntPtr is set to the size of the required buffer. + // 3. The internal file position is left unchanged. + // + // If the buffer is long enough to hold the entire line then + // *bufByteCntPtr is left unchanged. + // See cmFileGetLineTest() in cmProcTest.c or cmFileGetLineAuto() + // in cmFile.c for examples of how to use this function to a + // achieve proper buffer sizing. + cmFileRC_t cmFileGetLine( cmFileH_t h, cmChar_t* buf, unsigned* bufByteCntPtr ); + + // A version of cmFileGetLine() which eliminates the need to handle buffer + // sizing. + // + // Example usage: + // + // cmChar_t* buf = NULL; + // unsigned bufByteCnt = 0; + // while(cmFileGetLineAuto(h,&buf,&bufByteCnt)==kOkFileRC) + // proc(buf); + // cmMemPtrFree(buf); + // + // On the first call to this function *bufPtrPtr must be set to NULL and + // *bufByteCntPtr must be set to 0. + // Following the last call to this function call cmMemPtrFree(bufPtrptr) + // to be sure the line buffer is fully released. Note this step is not + // neccessary if the last call does not return kOkFileRC. + cmFileRC_t cmFileGetLineAuto( cmFileH_t h, cmChar_t** bufPtrPtr, unsigned* bufByteCntPtr ); + + // Binary Array Reading Functions + // Each of these functions reads a block of binary data from a file. + // The advantage to using these functions over cmFileRead() is only that they are type specific. + cmFileRC_t cmFileReadChar( cmFileH_t h, char* buf, unsigned cnt ); + cmFileRC_t cmFileReadUChar( cmFileH_t h, unsigned char* buf, unsigned cnt ); + cmFileRC_t cmFileReadShort( cmFileH_t h, short* buf, unsigned cnt ); + cmFileRC_t cmFileReadUShort( cmFileH_t h, unsigned short* buf, unsigned cnt ); + cmFileRC_t cmFileReadLong( cmFileH_t h, long* buf, unsigned cnt ); + cmFileRC_t cmFileReadULong( cmFileH_t h, unsigned long* buf, unsigned cnt ); + cmFileRC_t cmFileReadInt( cmFileH_t h, int* buf, unsigned cnt ); + cmFileRC_t cmFileReadUInt( cmFileH_t h, unsigned int* buf, unsigned cnt ); + cmFileRC_t cmFileReadFloat( cmFileH_t h, float* buf, unsigned cnt ); + cmFileRC_t cmFileReadDouble( cmFileH_t h, double* buf, unsigned cnt ); + cmFileRC_t cmFileReadBool( cmFileH_t h, bool* buf, unsigned cnt ); + + // Binary Array Writing Functions + // Each of these functions writes an array to a binary file. + // The advantage to using functions rather than cmFileWrite() is only that they are type specific. + cmFileRC_t cmFileWriteChar( cmFileH_t h, const char* buf, unsigned cnt ); + cmFileRC_t cmFileWriteUChar( cmFileH_t h, const unsigned char* buf, unsigned cnt ); + cmFileRC_t cmFileWriteShort( cmFileH_t h, const short* buf, unsigned cnt ); + cmFileRC_t cmFileWriteUShort( cmFileH_t h, const unsigned short* buf, unsigned cnt ); + cmFileRC_t cmFileWriteLong( cmFileH_t h, const long* buf, unsigned cnt ); + cmFileRC_t cmFileWriteULong( cmFileH_t h, const unsigned long* buf, unsigned cnt ); + cmFileRC_t cmFileWriteInt( cmFileH_t h, const int* buf, unsigned cnt ); + cmFileRC_t cmFileWriteUInt( cmFileH_t h, const unsigned int* buf, unsigned cnt ); + cmFileRC_t cmFileWriteFloat( cmFileH_t h, const float* buf, unsigned cnt ); + cmFileRC_t cmFileWriteDouble( cmFileH_t h, const double* buf, unsigned cnt ); + cmFileRC_t cmFileWriteBool( cmFileH_t h, const bool* buf, unsigned cnt ); + + // Formatted Text Output Functions: + // Print formatted text to a file. + cmFileRC_t cmFilePrint( cmFileH_t h, const cmChar_t* text ); + cmFileRC_t cmFilePrintf( cmFileH_t h, const cmChar_t* fmt, ... ); + cmFileRC_t cmFileVPrintf( cmFileH_t h, const cmChar_t* fmt, va_list vl ); + //) + //} + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/cmFileSys.c b/cmFileSys.c new file mode 100644 index 0000000..eca4dfa --- /dev/null +++ b/cmFileSys.c @@ -0,0 +1,1285 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmLinkedHeap.h" +#include "cmFileSys.h" + +#include +#include +#include // basename(), dirname() +#include // opendir()/readdir() +#include // PATH_MAX +#include // mkdir + +#ifdef OS_OSX +#include "osx/cmFileSysOsx.h" +#endif + +#ifdef OS_LINUX +#include "linux/cmFileSysLinux.h" +#endif + +cmFileSysH_t cmFileSysNullHandle = {NULL}; + +typedef struct +{ + cmErr_t err; + cmLHeapH_t heapH; +#ifdef OS_OSX + _cmFsOsx_t* p; +#endif +#ifdef OS_LINUX + _cmFsLinux_t* p; +#endif +} cmFs_t; + +cmFsRC_t _cmFileSysError( cmFs_t* p, cmFsRC_t rc, int sysErr, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + + if( sysErr == 0 ) + rc = cmErrVMsg(&p->err,rc,fmt,vl); + else + { + const unsigned bufCharCnt = 511; + cmChar_t buf[bufCharCnt+1]; + vsnprintf(buf,bufCharCnt,fmt,vl); + rc = cmErrMsg(&p->err,rc,"%s\nSystem Msg:%s",buf,strerror(sysErr)); + } + + va_end(vl); + return rc; +} + + +cmFs_t* _cmFileSysHandleToPtr( cmFileSysH_t h ) +{ + cmFs_t* p = (cmFs_t*)h.h; + assert( p != NULL); + return p; +} + +cmFsRC_t _cmFileSysFinalize( cmFs_t* p ) +{ + cmFsRC_t rc = kOkFsRC; + + if( cmLHeapIsValid(p->heapH) ) + cmLHeapDestroy(&p->heapH); + +#ifdef OS_OSX + if( p->p != NULL ) + if((rc = _cmOsxFileSysFinalize(p->p) ) != kOkFsRC ) + { + _cmFileSysError(p,kOsxFailFsRC,0,"The OSX file system finalization failed."); + return rc; + } +#endif + +#ifdef OS_LINUX + if( p->p != NULL ) + if((rc = _cmLinuxFileSysFinalize(p->p) ) != kOkFsRC ) + { + _cmFileSysError(p,kLinuxFailFsRC,0,"The Linux file system finalization failed."); + return rc; + } +#endif + + cmMemPtrFree(&p); + + return rc; +} + +cmFsRC_t cmFileSysInitialize( cmFileSysH_t* hp, cmCtx_t* ctx, const cmChar_t* appNameStr ) +{ + cmFs_t* p; + cmFsRC_t rc; + cmErr_t err; + + if((rc = cmFileSysFinalize(hp)) != kOkFsRC ) + return rc; + + cmErrSetup(&err,&ctx->rpt,"File System"); + + if((p = cmMemAllocZ( cmFs_t, 1 )) == NULL ) + return cmErrMsg(&err,kMemAllocErrFsRC,"Unable to allocate the file system object."); + + cmErrClone(&p->err,&err); + + if(cmLHeapIsValid( p->heapH = cmLHeapCreate(1024,ctx)) == false ) + { + rc = _cmFileSysError(p,kLHeapAllocErrFsRC,0,"Unable to allocate the linked heap."); + goto errLabel; + } + +#ifdef OS_OSX + if( (rc = _cmOsxFileSysInit(&p->p, p->heapH, &p->err)) != kOkFsRC ) + { + rc = _cmFileSysError(p,kOsxFailFsRC,0,"OSX file system initialization failed."); + goto errLabel; + } +#endif + +#ifdef OS_LINUX + if( (rc = _cmLinuxFileSysInit(&p->p, p->heapH, &p->err)) != kOkFsRC ) + { + rc = _cmFileSysError(p,kLinuxFailFsRC,0,"Linux file system initialization failed."); + goto errLabel; + } + else + { +#endif + + hp->h = p; + +#ifdef OS_LINUX + + cmChar_t hidAppNameStr[ strlen(appNameStr) + 2 ]; + + strcpy(hidAppNameStr,"."); + strcat(hidAppNameStr,appNameStr); + + p->p->prefDir = cmFileSysMakeFn( *hp, p->p->prefDir, hidAppNameStr, NULL, NULL ); + + // the resource directory must exist before the program can start + p->p->rsrcDir = cmFileSysMakeFn( *hp, p->p->rsrcDir, appNameStr, NULL, NULL ); + } +#endif + + errLabel: + if( rc != kOkFsRC ) + return _cmFileSysFinalize(p); + + return kOkFsRC; +} + +cmFsRC_t cmFileSysFinalize( cmFileSysH_t* hp ) +{ + cmFsRC_t rc; + + if( hp==NULL || cmFileSysIsValid(*hp) == false ) + return kOkFsRC; + + cmFs_t* p = _cmFileSysHandleToPtr(*hp); + + if((rc = _cmFileSysFinalize(p)) != kOkFsRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +const cmChar_t* cmFileSysPrefsDir( cmFileSysH_t h ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); +#if defined OS_OSX || defined OS_LINUX + return p->p->prefDir; +#else + return NULL; +#endif +} + +const cmChar_t* cmFileSysRsrcDir( cmFileSysH_t h ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); +#if defined OS_OSX || defined OS_LINUX + return p->p->rsrcDir; +#else + return NULL; +#endif +} + +const cmChar_t* cmFileSysUserDir( cmFileSysH_t h ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); +#if defined OS_OSX || defined OS_LINUX + return p->p->userDir; +#else + return NULL; +#endif +} + + +bool cmFileSysIsValid( cmFileSysH_t h ) +{ return h.h != NULL; } + +bool _cmFileSysIsDir( cmFs_t* p, const cmChar_t* dirStr ) +{ + struct stat s; + + errno = 0; + + if( stat(dirStr,&s) != 0 ) + { + // if the dir does not exist + if( errno == ENOENT ) + return false; + + _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'",dirStr); + return false; + } + + return S_ISDIR(s.st_mode); +} + +bool cmFileSysIsDir( cmFileSysH_t h, const cmChar_t* dirStr ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + return _cmFileSysIsDir(p,dirStr); +} + +bool _cmFileSysIsFile( cmFs_t* p, const cmChar_t* fnStr ) +{ + struct stat s; + errno = 0; + + if( stat(fnStr,&s) != 0 ) + { + + // if the file does not exist + if( errno == ENOENT ) + return false; + + _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'.",fnStr); + return false; + } + + return S_ISREG(s.st_mode); +} + +bool cmFileSysIsFile( cmFileSysH_t h, const cmChar_t* fnStr ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + + return _cmFileSysIsFile(p,fnStr); +} + +bool _cmFileSysIsLink( cmFs_t* p, const cmChar_t* fnStr ) +{ + struct stat s; + errno = 0; + + if( stat(fnStr,&s) != 0 ) + { + + // if the file does not exist + if( errno == ENOENT ) + return false; + + _cmFileSysError( p, kStatFailFsRC, errno, "'stat' failed on '%s'.",fnStr); + return false; + } + + return S_ISLNK(s.st_mode); +} + +bool cmFileSysIsLink( cmFileSysH_t h, const cmChar_t* fnStr ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + + return _cmFileSysIsLink(p,fnStr); +} + +bool _cmFileSysConcat( cmChar_t* rp, unsigned rn, char sepChar, const cmChar_t* suffixStr ) +{ + unsigned m = strlen(rp); + + // m==0 if no sep needs to be inserted or removed + + //if( m == 0 ) + // return false; + + if( m != 0 ) + { + + // if a sep char needs to be inserted + if( rp[m-1] != sepChar && suffixStr[0] != sepChar ) + { + assert((m+1)=rn) + return false; + + rp[m] = sepChar; + rp[m+1]= 0; + ++m; + } + else + // if a sep char needs to be removed + if( rp[m-1] == sepChar && suffixStr[0] == sepChar ) + { + rp[m-1] = 0; + --m; + } + + } + + assert( rn>=m && strlen(rp)+strlen(suffixStr) <= rn ); + strncat(rp,suffixStr,rn-m); + + return true; +} + +const cmChar_t* cmFileSysVMakeFn( cmFileSysH_t h, const cmChar_t* dir, const cmChar_t* fn, const cmChar_t* ext, va_list vl ) +{ + cmFsRC_t rc = kOkFsRC; + cmChar_t* rp = NULL; + const cmChar_t* dp = NULL; + unsigned n = 0; + cmFs_t* p = _cmFileSysHandleToPtr(h); + char pathSep = cmPathSeparatorChar[0]; + char extSep = '.'; + va_list vl_t; + va_copy(vl_t,vl); + + assert( fn != NULL ); + + // get prefix directory length + if( dir != NULL ) + n += strlen(dir) + 1; // add 1 for ending sep + + // get file name length + n += strlen(fn); + + // get extension length + if( ext != NULL ) + n += strlen(ext) + 1; // add 1 for period + + // get length of all var args dir's + while( (dp = va_arg(vl,const cmChar_t*)) != NULL ) + n += strlen(dp) + 1; // add 1 for ending sep + + // add 1 for terminating zero and allocate memory + + if((rp = cmLHeapAllocZ( p->heapH, n+1 )) == NULL ) + { + rc = _cmFileSysError(p,kMemAllocErrFsRC,0,"Unable to allocate file name memory."); + goto errLabel; + } + + va_copy(vl,vl_t); + + rp[n] = 0; + rp[0] = 0; + + // copy out the prefix dir + if( dir != NULL ) + strncat(rp,dir,n-strlen(rp)); + + // copy out ecmh of the var arg's directories + while((dp = va_arg(vl,const cmChar_t*)) != NULL ) + if(!_cmFileSysConcat(rp,n,pathSep,dp) ) + { + assert(0); + rc = _cmFileSysError(p,kAssertFailFsRC,0,"Assert failed."); + goto errLabel; + } + + + // copy out the file name + if(!_cmFileSysConcat(rp,n,pathSep,fn)) + { + assert(0); + rc = _cmFileSysError(p,kAssertFailFsRC,0,"Assert failed."); + goto errLabel; + } + + // copy out the extension + if( ext != NULL ) + if(!_cmFileSysConcat(rp,n,extSep,ext)) + { + assert(0); + rc = _cmFileSysError(p,kAssertFailFsRC,0,"Assert failed."); + goto errLabel; + } + + assert(strlen(rp)<=n); + + errLabel: + + if( rc != kOkFsRC && rp != NULL ) + cmLHeapFree(p->heapH, rp ); + + return rp; +} + +const cmChar_t* cmFileSysMakeFn( cmFileSysH_t h, const cmChar_t* dir, const cmChar_t* fn, const cmChar_t* ext, ... ) +{ + va_list vl; + va_start(vl,ext); + const cmChar_t* retPtr = cmFileSysVMakeFn(h,dir,fn,ext,vl); + va_end(vl); + return retPtr; +} + +void cmFileSysFreeFn( cmFileSysH_t h, const cmChar_t* fn ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + + if( fn == NULL ) + return; + + cmLHeapFree(p->heapH, (void*)fn); +} + +cmFsRC_t cmFileSysMkDir( cmFileSysH_t h, const cmChar_t* dir ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + + if( mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0 ) + return _cmFileSysError( p, kMkDirFailFsRC, errno, "The attempt to create the directory '%s' failed.",dir); + + return kOkFsRC; +} + +cmFsRC_t cmFileSysMkDirAll( cmFileSysH_t h, const cmChar_t* dir ) +{ + cmFsRC_t rc = kOkFsRC; + cmFs_t* p = _cmFileSysHandleToPtr(h); + cmChar_t** a = NULL; + unsigned i; + + if((a = cmFileSysDirParts(h,dir)) == NULL ) + return _cmFileSysError(p, kInvalidDirFsRC, 0, "The directory '%s' could not be parsed.",cmStringNullGuard(dir)); + + for(i=0; rc==kOkFsRC && a[i]!=NULL; ++i) + { + cmChar_t* d = cmFileSysFormDir(h,a,i+1); + + if( cmFileSysIsDir(h,d)==false ) + if((rc = cmFileSysMkDir(h,d)) != kOkFsRC ) + break; + + cmFileSysFreeDir(h,d); + } + + cmFileSysFreeDirParts(h,a); + + return rc; +} + +cmFileSysPathPart_t* cmFileSysPathParts( cmFileSysH_t h, const cmChar_t* pathStr ) +{ + + int n = 0; // char's in pathStr + int dn = 0; // char's in the dir part + int fn = 0; // char's in the name part + int en = 0; // char's in the ext part + cmChar_t* cp = NULL; + cmFileSysPathPart_t* rp = NULL; + + cmFs_t* p = _cmFileSysHandleToPtr(h); + + if( pathStr==NULL ) + return NULL; + + // skip leading white space + while( *pathStr ) + { + if( isspace(*pathStr ) ) + ++pathStr; + else + break; + } + + // get the length of pathStr + if((n = strlen(pathStr)) == 0 ) + return NULL; + + // remove trailing spaces + for(; n>0; --n) + { + if( isspace(pathStr[n-1]) ) + --n; + else + break; + } + + // + if( n == 0 ) + return NULL; + + + char buf[n+1]; + buf[n] = 0; + + // Get the last word (which may be a file name) from pathStr. + // (pathStr must be copied into a buf because basename() + // is allowed to change the values in its arg.) + strncpy(buf,pathStr,n); + cp = basename(buf); + + if( cp != NULL ) + { + cmChar_t* ep; + // does the last word have a period in it + if( (ep = strchr(cp,'.')) != NULL ) + { + *ep = 0; // end the file name at the period + ++ep; // set the ext marker + en = strlen(ep); // get the length of the ext + } + + fn = strlen(cp); //get the length of the file name + } + + // Get the directory part. + // ( pathStr must be copied into a buf because dirname() + // is allowed to change the values in its arg.) + strncpy(buf,pathStr,n); + + // if the last char in pathStr[] is '/' then the whole string is a dir. + // (this is not the answer that dirname() and basename() would give). + if( pathStr[n-1] == cmPathSeparatorChar[0] ) + { + fn = 0; + en = 0; + dn = n; + cp = buf; + } + else + { + cp = dirname(buf); + } + + + if( cp != NULL ) + dn = strlen(cp); + + // get the total size of the returned memory. (add 3 for ecmh possible terminating zero) + n = sizeof(cmFileSysPathPart_t) + dn + fn + en + 3; + + // alloc memory + if((rp = (cmFileSysPathPart_t*)cmLHeapAllocZ( p->heapH, n )) == NULL ) + { + _cmFileSysError( p, kLHeapAllocErrFsRC, 0, "Unable to allocate the file system path part record for '%s'.",pathStr); + return NULL; + } + + // set the return pointers for ecmh of the parts + rp->dirStr = (const cmChar_t* )(rp + 1); + rp->fnStr = rp->dirStr + dn + 1; + rp->extStr = rp->fnStr + fn + 1; + + + // if there is a directory part + if( dn>0 ) + strcpy((cmChar_t*)rp->dirStr,cp); + else + rp->dirStr = NULL; + + if( fn || en ) + { + // Get the trailing word again. + // pathStr must be copied into a buf because basename() may + // is allowed to change the values in its arg. + strncpy(buf,pathStr,n); + cp = basename(buf); + + + if( cp != NULL ) + { + + cmChar_t* ep; + if( (ep = strchr(cp,'.')) != NULL ) + { + *ep = 0; + ++ep; + + assert( strlen(ep) == en ); + strcpy((cmChar_t*)rp->extStr,ep); + } + + + assert( strlen(cp) == fn ); + if(fn) + strcpy((cmChar_t*)rp->fnStr,cp); + } + } + + if( fn == 0 ) + rp->fnStr = NULL; + + if( en == 0 ) + rp->extStr = NULL; + + return rp; + +} + +void cmFileSysFreePathParts( cmFileSysH_t h, cmFileSysPathPart_t* pp ) +{ + if( pp == NULL ) + return; + + cmFs_t* p = _cmFileSysHandleToPtr(h); + + cmLHeapFree(p->heapH, (void*)pp ); +} + +cmChar_t** cmFileSysDirParts( cmFileSysH_t h, const cmChar_t* dirStr ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + const cmChar_t* s = dirStr; + const cmChar_t* ep = dirStr + strlen(dirStr); + char pathSep = cmPathSeparatorChar[0]; + unsigned n = 2; + unsigned i = 0; + + // skip leading white space or pathsep + while( isspace(*s) && s < ep ) + ++s; + + // if the directory string is empty + if( s >= ep ) + return NULL; + + // set the beginning of the input dirStr past any leading white space + dirStr = s; + + // count the number of dir segments - this might overcount the + // number of segments if there are multiple repeated path seperator + // char's - but this is ok because 'n' is only used to determine + // the size of the returned array - which will simply have some + // NULL entries at the end. + for(; s < ep; ++s ) + if( *s == pathSep ) + ++n; + + // allocate the array + cmChar_t** a = cmLhAllocZ(p->heapH,cmChar_t*,n); + + // reset the cur location to the begin of the buf + s = dirStr; + + // if the path starts at the root + if( *s == pathSep ) + { + a[0] = cmPathSeparatorChar; // cmPathSeparatorChar is a static string in cmGlobal.h + i = 1; + ++s; + } + + for(; iheapH,cmChar_t,sn); + strncpy(a[i],s,sn-1); + a[i][sn-1] = 0; + } + + s = s1+1; + } + + return a; +} + +void cmFileSysFreeDirParts( cmFileSysH_t h, cmChar_t** dirStrArray ) +{ + if( dirStrArray == NULL ) + return; + + cmFs_t* p = _cmFileSysHandleToPtr(h); + + unsigned i; + for(i=0; dirStrArray[i]!=NULL; ++i) + { + // cmPathSeparatorChar is statically alloc'd in cmGlobal.h + if( dirStrArray[i] != cmPathSeparatorChar ) + cmLHeapFree(p->heapH,dirStrArray[i]); + } + + cmLHeapFree(p->heapH, (void*)dirStrArray ); +} + +unsigned cmFileSysDirPartsCount(cmFileSysH_t h, cmChar_t** dirStrArray ) +{ + unsigned i = 0; + if( dirStrArray == NULL ) + return 0; + + while(dirStrArray[i] != NULL ) + ++i; + + return i; +} + +cmChar_t* cmFileSysFormDir( cmFileSysH_t h, cmChar_t** a, unsigned m ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + unsigned n; + unsigned i; + + // determine the length of the return string + for(i=0,n=0; a[i]!=NULL && iheapH,cmChar_t,n); + const cmChar_t* ep = r + n; + + // form the return string + for(i=0; a[i]!=NULL && iheapH,(void*)dir); +} + + +typedef struct +{ + cmFs_t* p; + unsigned filterFlags; + cmFileSysDirEntry_t* rp; + cmChar_t* dataPtr; + cmChar_t* endPtr; + unsigned entryCnt; + unsigned entryIdx; + unsigned dataByteCnt; + unsigned passIdx; + +} cmFileSysDeRecd_t; + +cmFsRC_t _cmFileSysDirGetEntries( cmFileSysDeRecd_t* drp, const cmChar_t* dirStr ) +{ + cmFsRC_t rc = kOkFsRC; + DIR* dirp = NULL; + struct dirent* dp = NULL; + char curDirPtr[] = "./"; + unsigned dn = 0; + + + if( dirStr == NULL || strlen(dirStr) == 0 ) + dirStr = curDirPtr; + + unsigned fnCharCnt= strlen(dirStr) + PATH_MAX; + char fn[ fnCharCnt + 1 ]; + + + // copy the directory into fn[] ... + fn[0] =0; + fn[fnCharCnt] = 0; + + strcpy(fn,dirStr); + + assert( strlen(fn)+2 < fnCharCnt ); + + // ... and be sure that it is terminated with a path sep char + if( fn[ strlen(fn)-1 ] != cmPathSeparatorChar[0] ) + strcat(fn,cmPathSeparatorChar); + + // file names will be appended to the path at this location + unsigned fni = strlen(fn); + + // open the directory + if((dirp = opendir(dirStr)) == NULL) + { + rc = _cmFileSysError(drp->p,kOpenDirFailFsRC,errno,"Unable to open the directory:'%s'.",dirStr); + goto errLabel; + } + + + // get the next directory entry + while((dp = readdir(dirp)) != NULL ) + { + // validate d_name + if( (dp->d_name != NULL) && ((dn = strlen(dp->d_name)) > 0) ) + { + unsigned flags = 0; + + // handle cases where d_name begins with '.' + if( dp->d_name[0] == '.' ) + { + + if( strcmp(dp->d_name,".") == 0 ) + { + if( cmIsFlag(drp->filterFlags,kCurDirFsFl) == false ) + continue; + + flags |= kCurDirFsFl; + } + + if( strcmp(dp->d_name,"..") == 0 ) + { + if( cmIsFlag(drp->filterFlags,kParentDirFsFl) == false ) + continue; + + flags |= kParentDirFsFl; + } + + if( flags == 0 ) + { + if( cmIsFlag(drp->filterFlags,kInvisibleFsFl) == false ) + continue; + + flags |= kInvisibleFsFl; + } + } + + fn[fni] = 0; + strncat( fn, dp->d_name, fnCharCnt-fni ); + unsigned fnN = strlen(fn); + + // if the filename is too long for the buffer + if( fnN > fnCharCnt ) + { + rc = _cmFileSysError(drp->p, kFnTooLongFsRC, errno, "The directory entry:'%s' was too long to be processed.",dp->d_name); + goto errLabel; + } + + // is the entry a file + if( _cmFileSysIsFile(drp->p,fn) ) + { + if( cmIsFlag(drp->filterFlags,kFileFsFl)==false ) + continue; + + flags |= kFileFsFl; + } + else + { + // is the entry a dir + if( _cmFileSysIsDir(drp->p,fn) ) + { + if( cmIsFlag(drp->filterFlags,kDirFsFl) == false) + continue; + + flags |= kDirFsFl; + + if( cmIsFlag(drp->filterFlags,kRecurseFsFl) ) + if((rc = _cmFileSysDirGetEntries(drp,fn)) != kOkFsRC ) + goto errLabel; + } + } + + assert(flags != 0); + + if( drp->passIdx == 0 ) + { + ++drp->entryCnt; + + // add 1 for the name terminating zero + drp->dataByteCnt += sizeof(cmFileSysDirEntry_t) + 1; + + if( cmIsFlag(drp->filterFlags,kFullPathFsFl) ) + drp->dataByteCnt += fnN; + else + drp->dataByteCnt += dn; + + } + else + { + assert( drp->passIdx == 1 ); + assert( drp->entryIdx < drp->entryCnt ); + + unsigned n = 0; + if( cmIsFlag(drp->filterFlags,kFullPathFsFl) ) + { + n = fnN+1; + assert( drp->dataPtr + n <= drp->endPtr ); + strcpy(drp->dataPtr,fn); + } + else + { + n = dn+1; + assert( drp->dataPtr + n <= drp->endPtr ); + strcpy(drp->dataPtr,dp->d_name); + } + + drp->rp[ drp->entryIdx ].flags = flags; + drp->rp[ drp->entryIdx ].name = drp->dataPtr; + drp->dataPtr += n; + assert( drp->dataPtr <= drp->endPtr); + ++drp->entryIdx; + } + } + } + + errLabel: + return rc; +} + + +cmFileSysDirEntry_t* cmFileSysDirEntries( cmFileSysH_t h, const cmChar_t* dirStr, unsigned filterFlags, unsigned* dirEntryCntPtr ) +{ + cmFsRC_t rc = kOkFsRC; + cmFileSysDeRecd_t r; + + memset(&r,0,sizeof(r)); + r.p = _cmFileSysHandleToPtr(h); + r.filterFlags = filterFlags; + + assert( dirEntryCntPtr != NULL ); + *dirEntryCntPtr = 0; + + for(r.passIdx=0; r.passIdx<2; ++r.passIdx) + { + if((rc = _cmFileSysDirGetEntries( &r, dirStr )) != kOkFsRC ) + goto errLabel; + + if( r.passIdx == 0 ) + { + // allocate memory to hold the return values + if(( r.rp = (cmFileSysDirEntry_t *)cmLHeapAllocZ( r.p->heapH, r.dataByteCnt )) == NULL ) + { + rc= _cmFileSysError( r.p, kMemAllocErrFsRC, 0, "Unable to allocate %i bytes of dir entry memory.",r.dataByteCnt); + goto errLabel; + } + + r.dataPtr = (cmChar_t*)(r.rp + r.entryCnt); + r.endPtr = ((cmChar_t*)r.rp) + r.dataByteCnt; + } + } + + errLabel: + + if( rc == kOkFsRC ) + { + assert( r.entryIdx == r.entryCnt ); + *dirEntryCntPtr = r.entryCnt; + } + else + { + if( r.rp != NULL ) + cmLHeapFree(r.p->heapH,r.rp); + + r.rp = NULL; + } + + return r.rp; +} + +void cmFileSysDirFreeEntries( cmFileSysH_t h, cmFileSysDirEntry_t* dp ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + if( dp != NULL ) + cmLHeapFree(p->heapH,dp); +} + +cmFsRC_t cmFileSysErrorCode( cmFileSysH_t h ) +{ + cmFs_t* p = _cmFileSysHandleToPtr(h); + return cmErrLastRC(&p->err); +} + +// +//====================================================================================================== +// Begin global versions +// + +cmFileSysH_t _cmFsH = { NULL }; + +cmFsRC_t cmFsInitialize( cmCtx_t* ctx, const cmChar_t* appNameStr ) +{ return cmFileSysInitialize(&_cmFsH,ctx,appNameStr); } + +cmFsRC_t cmFsFinalize() +{ return cmFileSysFinalize(&_cmFsH); } + +const cmChar_t* cmFsPrefsDir() +{ return cmFileSysPrefsDir(_cmFsH); } + +const cmChar_t* cmFsRsrcDir() +{ return cmFileSysRsrcDir(_cmFsH); } + +const cmChar_t* cmFsUserDir() +{ return cmFileSysUserDir(_cmFsH); } + +bool cmFsIsDir( const cmChar_t* dirStr ) +{ return cmFileSysIsDir(_cmFsH,dirStr); } + +bool cmFsIsFile( const cmChar_t* fnStr ) +{ return cmFileSysIsFile(_cmFsH,fnStr); } + +bool cmFsIsLink( const cmChar_t* fnStr ) +{ return cmFileSysIsLink(_cmFsH,fnStr); } + +const cmChar_t* cmFsVMakeFn( const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, va_list vl ) +{ return cmFileSysVMakeFn(_cmFsH,dirPrefix,fn,ext,vl); } + +const cmChar_t* cmFsMakeFn( const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, ... ) +{ + va_list vl; + va_start(vl,ext); + const cmChar_t* retPtr = cmFsVMakeFn(dirPrefix,fn,ext,vl); + va_end(vl); + return retPtr; +} + +void cmFsFreeFn( const cmChar_t* fn ) +{ cmFileSysFreeFn(_cmFsH, fn); } + +cmFsRC_t cmFsMkDir( const cmChar_t* dir ) +{ return cmFileSysMkDir(_cmFsH,dir); } + +cmFsRC_t cmFsMkDirAll( const cmChar_t* dir ) +{ return cmFileSysMkDirAll(_cmFsH,dir); } + +cmFileSysPathPart_t* cmFsPathParts( const cmChar_t* pathNameStr ) +{ return cmFileSysPathParts(_cmFsH,pathNameStr); } + +void cmFsFreePathParts( cmFileSysPathPart_t* p ) +{ cmFileSysFreePathParts(_cmFsH,p); } + +cmChar_t** cmFsDirParts( const cmChar_t* dirStr ) +{ return cmFileSysDirParts(_cmFsH,dirStr); } + +void cmFsFreeDirParts( cmChar_t** dirStrArray ) +{ cmFileSysFreeDirParts(_cmFsH,dirStrArray); } + +unsigned cmFsDirPartsCount( cmChar_t** dirStrArray ) +{ return cmFileSysDirPartsCount(_cmFsH, dirStrArray); } + +cmChar_t* cmFsFormDir( cmChar_t** dirStrArray, unsigned n ) +{ return cmFileSysFormDir(_cmFsH,dirStrArray,n); } + +void cmFsFreeDir( const cmChar_t* dir ) +{ cmFileSysFreeDir(_cmFsH,dir); } + +cmFileSysDirEntry_t* cmFsDirEntries( const cmChar_t* dirStr, unsigned includeFlags, unsigned* dirEntryCntPtr ) +{ return cmFileSysDirEntries(_cmFsH,dirStr,includeFlags,dirEntryCntPtr); } + +void cmFsDirFreeEntries( cmFileSysDirEntry_t* p ) +{ cmFileSysDirFreeEntries(_cmFsH,p); } + +cmFsRC_t cmFsErrorCode() +{ return cmFileSysErrorCode(_cmFsH); } + +// end global version +//====================================================================================================== +// + + +//{ { label:cmFileSysEx } +//( +// +// cmFileSysTest() function gives usage and testing examples +// for some of the cmFileSys functions. +// Note that the HOME_DIR macro should be set to your true +// $HOME directroy and SRC_DIR should be set to any existing +// and accessible directory. +// +//) +//( + +#if defined(OS_OSX) +#define HOME_DIR "/Users/kevin" +#else +#define HOME_DIR "/home/kevin" +#endif + +#define SRC_DIR HOME_DIR"/src" + +void _cmFileSysTestFnParser( + cmFileSysH_t h, + cmRpt_t* rpt, + const cmChar_t* fn ); + +cmFsRC_t cmFileSysTest( cmCtx_t* ctx ) +{ + // The global heap manager must have been initialized + // via cmMdInitialize() prior to running this function. + + cmFsRC_t rc = kOkFsRC; + cmFileSysH_t h = cmFileSysNullHandle; + const char dir0[] = SRC_DIR; + const char dir1[] = HOME_DIR"/blah"; + const char file0[] = HOME_DIR"/.emacs"; + const char file1[] = HOME_DIR"/blah.txt"; + const char not[] = " not "; + const char e[] = " "; + bool fl = false; + const cmChar_t* fn = NULL; + + // Initialize the file system. + if((rc = cmFileSysInitialize(&h,ctx,"fs_test")) != kOkFsRC ) + return rc; + + //---------------------------------------------------------- + // Print the standard directories + // + printf("Prefs Dir:%s\n",cmFsPrefsDir()); + printf("Rsrc Dir: %s\n",cmFsRsrcDir()); + printf("User Dir: %s\n",cmFsUserDir()); + + //---------------------------------------------------------- + // Run the file system type checker + // + fl = cmFileSysIsDir(h,dir0); + printf("'%s' is%sa directory.\n",dir0, (fl ? e : not)); + + fl = cmFileSysIsDir(h,dir1); + printf("'%s' is%sa directory.\n",dir1, (fl ? e : not)); + + fl = cmFileSysIsFile(h,file0); + printf("'%s' is%sa file.\n",file0, (fl ? e : not)); + + fl = cmFileSysIsFile(h,file1); + printf("'%s' is%sa file.\n",file1, (fl ? e : not)); + + //---------------------------------------------------------- + // Test the file name creation functions + // + if((fn = cmFileSysMakeFn(h,HOME_DIR,"cmFileSys", + "c","src","cm","src",NULL)) != NULL) + { + printf("File:'%s'\n",fn); + } + cmFileSysFreeFn(h,fn); + + if((fn = cmFileSysMakeFn(h,HOME_DIR,"cmFileSys", + ".c","/src/","/cm/","/src/",NULL)) != NULL ) + { + printf("File:'%s'\n",fn); + } + cmFileSysFreeFn(h,fn); + + //---------------------------------------------------------- + // Test the file name parsing functions + // + _cmFileSysTestFnParser(h,&ctx->rpt, + HOME_DIR"/src/cm/src/cmFileSys.c"); + + _cmFileSysTestFnParser(h,&ctx->rpt, + HOME_DIR"/src/cm/src/cmFileSys"); + + _cmFileSysTestFnParser(h,&ctx->rpt, + HOME_DIR"/src/cm/src/cmFileSys/"); + + _cmFileSysTestFnParser(h,&ctx->rpt,"cmFileSys.c"); + _cmFileSysTestFnParser(h,&ctx->rpt,"/"); + _cmFileSysTestFnParser(h,&ctx->rpt," "); + + //---------------------------------------------------------- + // Test the directory tree walking routines. + // + cmRptPrintf(&ctx->rpt,"Dir Entry Test:\n"); + cmFileSysDirEntry_t* dep; + unsigned dirEntCnt; + unsigned filterFlags = kDirFsFl + | kFileFsFl + | kRecurseFsFl + | kFullPathFsFl; + + if((dep = cmFileSysDirEntries(h,SRC_DIR"/doc",filterFlags, + &dirEntCnt)) != NULL) + { + unsigned i; + for(i=0; irpt,"%s\n",dep[i].name); + + cmFileSysDirFreeEntries(h,dep); + } + + //---------------------------------------------------------- + // Test the directory parsing/building routines. + // + cmRptPrintf(&ctx->rpt,"Dir Parsing routings:\n"); + cmChar_t** a; + unsigned j; + for(j=0; j<2; ++j) + { + const cmChar_t* dstr = dir0 + j; + if((a = cmFileSysDirParts(h,dstr)) == NULL) + cmRptPrint(&ctx->rpt,"cmFileSysDirParts() failed.\n"); + else + { + unsigned i; + + cmRptPrintf(&ctx->rpt,"Input:%s\n",dstr); + for(i=0; a[i]!=NULL; ++i) + cmRptPrintf(&ctx->rpt,"%i : %s\n",i,a[i]); + + cmChar_t* d; + if((d = cmFileSysFormDir(h,a, + cmFileSysDirPartsCount(h,a))) != NULL ) + { + cmRptPrintf(&ctx->rpt,"Reformed:%s\n",d); + } + + cmFileSysFreeDirParts(h,a); + } + } + + //---------------------------------------------------------- + // Test the extended mkdir routine. + // + if( cmFileSysMkDirAll(h, HOME_DIR"/temp/doc/doc" )!=kOkFsRC ) + { + cmRptPrint(&ctx->rpt,"cmFileSysMkDirAll() failed.\n"); + } + + // finalize the file system + if((rc = cmFileSysFinalize(&h)) != kOkFsRC ) + return rc; + + cmRptPrintf(&ctx->rpt,"File Test done\n"); + + return rc; +} + +// Parse a file name and print the results. +// Called by cmFileSysTest(). +void _cmFileSysTestFnParser( + cmFileSysH_t h, + cmRpt_t* rpt, + const cmChar_t* fn ) +{ + cmFileSysPathPart_t* pp; + + cmRptPrintf(rpt,"Fn Parse Test:%s\n",fn); + + if((pp = cmFileSysPathParts(h,fn)) != NULL ) + { + if(pp->dirStr != NULL) + cmRptPrintf(rpt,"Dir:%s\n",pp->dirStr); + + if(pp->fnStr != NULL) + cmRptPrintf(rpt,"Fn:%s\n",pp->fnStr); + + if(pp->extStr != NULL ) + cmRptPrintf(rpt,"Ext:%s\n",pp->extStr); + + cmFileSysFreePathParts(h,pp); + } + + cmRptPrintf(rpt,"\n"); + +} +//) +//} diff --git a/cmFileSys.h b/cmFileSys.h new file mode 100644 index 0000000..23f672b --- /dev/null +++ b/cmFileSys.h @@ -0,0 +1,231 @@ +//{ +//( +// A collection of file system utility functions. +// +// Note that cmFileSysInitialize() creates an internal cmLHeapH_t based +// heap manager from which it allocates memory for some returned objects. +// (e.g. cmFileSysMakeFn(), cmFileSysPathParts(), cmFileSysDirEntries()) +// Where possible the client can explicitely free these objects via the +// provided functions. (e.g. cmFileSysFreeFn(), cmFileSysFreePathParts(), cmFileSysDirFreeEntries()) +// However if these objects are not free they will be automatically deallocated +// when the internal heap is destroyed by cmFileSysFinalize(). +// +//) + +#ifndef cmFileSys_h +#define cmFileSys_h + +#ifdef __cplusplus +extern "C" { +#endif + + //( + // Result codes returned by cmFileSys.h functions + enum + { + kOkFsRC = cmOkRC, + kMemAllocErrFsRC, + kLHeapAllocErrFsRC, + kStatFailFsRC, + kAssertFailFsRC, + kOpenDirFailFsRC, + kFnTooLongFsRC, + kMkDirFailFsRC, + kSysErrFsRC, + kOsxFailFsRC, + kLinuxFailFsRC, + kInvalidDirFsRC + }; + + + typedef cmHandle_t cmFileSysH_t; //< Opaque handle type used by all cmFileSys.h functions + typedef unsigned cmFsRC_t; //< Result code used as the return type by many cmFileSys.h functions. + + extern cmFileSysH_t cmFileSysNullHandle; // NULL handle to be used for setting cmFileSysH_t type handles to an explicit uninitialized value. + + // Initialize a file system object. + // If *hp was not initalized by an earlier call to cmFileSysInitialize() then it should + // be set to cmFileSysNullHandle prior to calling this function. If *hp is a valid handle + // then it is automatically finalized by an internal call to cmFileSysFinalize() prior to + // being re-iniitalized. + // The appNameStr is used to determine the location of the preference and resource directories + // on some platforms + cmFsRC_t cmFileSysInitialize( cmFileSysH_t* hp, cmCtx_t* ctx, const cmChar_t* appNameStr ); + + // Finalize a file system object. + // Upon successful completion *hp is set to cmFileSysNullHandle. + cmFsRC_t cmFileSysFinalize( cmFileSysH_t* hp ); + + // Returns true if the file system handle is active and initialized. + bool cmFileSysIsValid( cmFileSysH_t h ); + + const cmChar_t* cmFileSysPrefsDir( cmFileSysH_t h ); //< Return the operating system dependent preference data directory for this application. + const cmChar_t* cmFileSysRsrcDir( cmFileSysH_t h ); //< Return the operating system dependent application resource directory for this application. + const cmChar_t* cmFileSysUserDir( cmFileSysH_t h ); //< Return the operating system dependent user directory for this application. + + // Test the type of a file system object: + // + bool cmFileSysIsDir( cmFileSysH_t h, const cmChar_t* dirStr ); //< Return true if 'dirStr' refers to an existing directory. + bool cmFileSysIsFile( cmFileSysH_t h, const cmChar_t* fnStr ); //< Return true if 'fnStr' refers to an existing file. + bool cmFileSysIsLink( cmFileSysH_t h, const cmChar_t* fnStr ); //< Return true if 'fnStr' refers to a symbolic link. + + // Create File Names: + // + // Create a file name by concatenating sub-strings. + // + // Variable arg's. entries are directories inserted between + // 'dirPrefixStr' and the file name. + // Terminate var arg's directory list with a NULL. + // + // The returned string is allocated in a local heap maintained by the cmFileSys object. + // The memory used by the string will exist until it is released with cmFileSysFreeFn() + // or the cmFileSys object is finalized. + const cmChar_t* cmFileSysMakeFn( cmFileSysH_t h, const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, ... ); + + // Same as cmFileSysMakeFn with but with a va_list argument to accept the var. args. parameters. + const cmChar_t* cmFileSysVMakeFn( cmFileSysH_t h, const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, va_list vl ); + + // Release the file name created through an earlier call to cmFileSysMakeFn(). + void cmFileSysFreeFn( cmFileSysH_t h, const cmChar_t* fn ); + + // Create a directory - where the entire path already exists except for the + // final directory. + cmFsRC_t cmFileSysMkDir( cmFileSysH_t h, const cmChar_t* dir ); + + // Create a complete directory path - where any of the path segments may + // not already exist. + cmFsRC_t cmFileSysMkDirAll( cmFileSysH_t h, const cmChar_t* dir ); + + // Parse a path into its parts: + // + // Return record used by cmFileSysParts() + typedef struct + { + const cmChar_t* dirStr; + const cmChar_t* fnStr; + const cmChar_t* extStr; + } cmFileSysPathPart_t; + + // Given a file name decompose it into a directory string, file name string and file extension string. + // The cmFileSysPathPart_t record and the memory used by the strings that it references + // are allocated from a local heap maintained by the cmFileSys object. This memory will exist + // until it is released with cmFileSysFreePathParts() or the cmFileSysH_t handle is finalized. + cmFileSysPathPart_t* cmFileSysPathParts( cmFileSysH_t h, const cmChar_t* pathNameStr ); + + // Free the memory associated with a cmFileSysPathPart_t record returned from an eariler call to cmFileSysPathParts(). + void cmFileSysFreePathParts( cmFileSysH_t h, cmFileSysPathPart_t* p ); + + // Return the parts of a directory path as an array of strings. + // The last element in the array is set to NULL to mark the end of the array. + // Note that all '/' separator characters are removed from the result with + // the exception of the first one - which denotes the root directory. + // The returned array is allocated from the file systems internal heap and will + // be automatically released when the file system is closed by cmFileSysDestroy(). + // The caller may optionally release the array memory with a call to + // cmFileSysFreeDirParts(). + cmChar_t** cmFileSysDirParts( cmFileSysH_t h, const cmChar_t* dirStr ); + void cmFileSysFreeDirParts( cmFileSysH_t h, cmChar_t** dirStrArray ); + + // Return the count of elements in a directory parts array as returned by + // cmFileSysDirParts(). + unsigned cmFileSysDirPartsCount(cmFileSysH_t h, cmChar_t** dirStrArray ); + + // Form a directory string from a NULL terminated array of strings. + // If the first element in the array is set to '/' then the + // resulting directory will be absolute rather than relative. + // The returned string is allocated from the file systems internal heap and will + // be automatically released when the file system is closed by cmFileSysDestroy(). + // The caller may optionally release the array memory with a call to + // cmFileSysFreeDir(). + cmChar_t* cmFileSysFormDir( cmFileSysH_t h, cmChar_t** dirStrArray, unsigned n ); + void cmFileSysFreeDir( cmFileSysH_t h, const cmChar_t* dir ); + + // Walk a directory tree: + // + // Flags used by cmFileSysDirEntries 'includeFlags' parameter. + enum + { + kFileFsFl = 0x01, //< include all visible files + kDirFsFl = 0x02, //< include all visible directory + kInvisibleFsFl = 0x04, //< include file/dir name beginning with a '.' + kCurDirFsFl = 0x08, //< include '.' directory + kParentDirFsFl = 0x10, //< include '..' directory + + kAllFsFl = 0x1f, //< all type flags + + kFullPathFsFl = 0x40, //< return the full path in the 'name' field of cmFileSysDirEntry_t; + kRecurseFsFl = 0x80 //< recurse into directories + + }; + + // The return type for cmFileSysDirEntries(). + typedef struct + { + unsigned flags; //< Entry type flags from kXXXFsFl. + const cmChar_t* name; //< Entry name or full path depending on kFullPathFsFl. + } cmFileSysDirEntry_t; + + // Return the file and directory names contained in a given subdirectory. + // + // Set 'includeFlags' with the kXXXFsFl flags of the files to include in the returned + // directory entry array. The value pointed to by dirEntryCntPtr will be set to the + // number of records in the returned array. + cmFileSysDirEntry_t* cmFileSysDirEntries( cmFileSysH_t h, const cmChar_t* dirStr, unsigned includeFlags, unsigned* dirEntryCntPtr ); + + // Release the memory assoicated with a cmFileSysDirEntry_t array returned from an earlier call to cmFileSysDirEntries(). + void cmFileSysDirFreeEntries( cmFileSysH_t h, cmFileSysDirEntry_t* p ); + + // Return the last error code generated by the file system. + cmFsRC_t cmFileSysErrorCode( cmFileSysH_t h ); + + //------------------------------------------------------------------------------------------------- + + // Global file system functions: + // These functions work using a global cmFileSysH created by cmFsInitialize(). + // The functions are otherwise just wrappers for the same named function above. + + cmFsRC_t cmFsInitialize( cmCtx_t* ctx, const cmChar_t* appNameStr ); + cmFsRC_t cmFsFinalize(); + + const cmChar_t* cmFsPrefsDir(); + const cmChar_t* cmFsRsrcDir(); + const cmChar_t* cmFsUserDir(); + + bool cmFsIsDir( const cmChar_t* dirStr ); + bool cmFsIsFile( const cmChar_t* fnStr ); + bool cmFsIsLink( const cmChar_t* fnStr ); + + const cmChar_t* cmFsVMakeFn( const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, va_list vl ); + const cmChar_t* cmFsMakeFn( const cmChar_t* dirPrefix, const cmChar_t* fn, const cmChar_t* ext, ... ); + void cmFsFreeFn( const cmChar_t* fn ); + + cmFsRC_t cmFsMkDir( const cmChar_t* dir ); + cmFsRC_t cmFsMkDirAll( const cmChar_t* dir ); + + cmFileSysPathPart_t* cmFsPathParts( const cmChar_t* pathNameStr ); + void cmFsFreePathParts( cmFileSysPathPart_t* p ); + + + cmChar_t** cmFsDirParts( const cmChar_t* dirStr ); + void cmFsFreeDirParts( cmChar_t** dirStrArray ); + unsigned cmFsDirPartsCount( cmChar_t** dirStrArray ); + cmChar_t* cmFsFormDir( cmChar_t** dirStrArray, unsigned n ); + void cmFsFreeDir( const cmChar_t* dir ); + + cmFileSysDirEntry_t* cmFsDirEntries( const cmChar_t* dirStr, unsigned includeFlags, unsigned* dirEntryCntPtr ); + void cmFsDirFreeEntries( cmFileSysDirEntry_t* p ); + + cmFsRC_t cmFsErrorCode(); + + + // Test and example function to demonstrate the use of the functions in cmFileSys.h + cmFsRC_t cmFileSysTest( cmCtx_t* ctx ); + + //) + //} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmFloatTypes.h b/cmFloatTypes.h new file mode 100644 index 0000000..6dfc99c --- /dev/null +++ b/cmFloatTypes.h @@ -0,0 +1,81 @@ +/// \file cmFloatTypes.h +/// \brief Declare the types cmReal_t and cmSample_t and define some useful limits. +/// +/// For signal processing functions the cm library uses the types cmSample_t to indicate an audio +/// sample value and cmReal_t to specify a general purpose floating point value. The library +/// is designed in such a way that the actual type, float or double, for these two types may +/// be set at compilation time. Set the preprocessor variable CM_FLOAT_SMP to 1 to indicate +/// that cmSample_t will be of type 'float' otherwise it will be of type 'double'. +/// Set the preprocessor variable CM_FLOAT_REAL to 1 to indicate +/// that cmSample_t will be of type 'float' otherwise it will be of type 'double'. +/// By default cmSample_t is float nad cmReal_t is double. +/// + +#ifndef cmFloatTypes_h +#define cmFloatTypes_h + + +#ifdef __cplusplus +extern "C" { +#endif + + //----------------------------------------------------------------- +#ifndef CM_FLOAT_SMP +#define CM_FLOAT_SMP 1 +#endif + +#if CM_FLOAT_SMP == 1 + + typedef float cmSample_t; ///< cmSample_t is a float + typedef float _Complex cmComplexS_t;///< cmComplexS_t is single precision. + +#define cmSample_EPSILON FLT_EPSILON ///< Minimum increment between 1.0 and the next greaterv value. (1E-5) +#define cmSample_MAX FLT_MAX ///< Maximum representable number (1E+37). +#define cmSample_MIN FLT_MIN ///< Minimum representable number (1E-37). + +#else + + typedef double cmSample_t; ///< cmSample_t is a double + typedef double _Complex cmComplexS_t; ///< cmComplexS_t is doulbe precision. + +#define cmSample_EPSILON DBL_EPSILON ///< Minimum increment between 1.0 and the next greaterv value. (1E-9) +#define cmSample_MAX DBL_MAX ///< Maximum representable number (1E+37). +#define cmSample_MIN DBL_MIN ///< Minimum representable number (1E-37). + +#endif + +//----------------------------------------------------------------- +//----------------------------------------------------------------- +//----------------------------------------------------------------- + +#ifndef CM_FLOAT_REAL +#define CM_FLOAT_REAL 0 +#endif + +#if CM_FLOAT_REAL == 1 + + typedef float cmReal_t; ///< cmReal_t is a float + typedef float _Complex cmComplexR_t; ///< cmComplexR_t is single precision. + +#define cmReal_EPSILON FLT_EPSILON ///< Minimum increment between 1.0 and the next greaterv value. (1E-5) +#define cmReal_MAX FLT_MAX ///< Maximum representable number (1E+37). +#define cmReal_MIN FLT_MIN ///< Minimum representable number (1E-37). + +#else + + typedef double cmReal_t; ///< cmReal_t is a double. + typedef double _Complex cmComplexR_t; ///< cmComplexR_t is double precision. + +#define cmReal_EPSILON DBL_EPSILON ///< Minimum increment between 1.0 and the next greaterv value (1E-9). +#define cmReal_MAX DBL_MAX ///< Maximum representable number (1E+37). +#define cmReal_MIN DBL_MIN ///< Minimum representable number (1E-37). + + +#endif + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/cmFrameFile.c b/cmFrameFile.c new file mode 100644 index 0000000..4346f98 --- /dev/null +++ b/cmFrameFile.c @@ -0,0 +1,2210 @@ +#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 "cmJson.h" +#include "cmFrameFile.h" +#include "cmLinkedHeap.h" +#include "cmMath.h" +#include "cmVectOps.h" + +/* + +File Type: 4 0 +Chunk Bytes: 4 4 +Frame Count: 4 8 +Version: 4 16 +EOF Frm Offs:8 20 +Sample Rate: 8 28 32 + +Frame Type: 4 36 // Note: Update cmFrameFileFrameSkip() +Chunk Bytes: 4 40 // if the size of this header changes. +Mtx Count: 4 44 +Stream Id: 4 48 +Flags: 4 52 +Sample Idx 4 56 +Seconds: 8 60 32 + +Mtx Type: 4 68 +Data Bytes: 4 72 +Format Id: 4 76 +Units Id: 4 80 +Row Cnt: 4 86 +Col Cnt: 4 90 24 + + + + + */ + +#define _cmFfSwap16(fl,v) ((fl) ? cmSwap16(v) : (v)) +#define _cmFfSwap32(fl,v) ((fl) ? cmSwap32(v) : (v)) +#define _cmFfSwap64(fl,v) ((fl) ? cmSwap64(v) : (v)) +#define _cmFfWrSwapF(fl,v) ((fl) ? cmFfSwapFloatToUInt(v) : (*((unsigned*)&(v)))) +#define _cmFfRdSwapF(fl,v) ((fl) ? cmFfSwapUIntToFloat(v) : (*((float*)&(v)))) +#define _cmFfWrSwapD(fl,v) ((fl) ? cmFfSwapDoubleToULLong(v) : (*((unsigned long long*)&(v)))) +#define _cmFfRdSwapD(fl,v) ((fl) ? cmFfSwapULLongToDouble(v) : (*((double*)&(v)))) + + +enum +{ + kSampleIdxTimeFl = 0x01, + kSecondsTimeFl = 0x02 +}; + + +typedef struct _cmFfOffs_str +{ + unsigned frmIdx; // absolute frame index for this mtx + off_t offs; // file offset for mtx header + struct _cmFfOffs_str* linkPtr; +} _cmFfOffs_t; + +typedef struct +{ + _cmFfOffs_t* beg; + _cmFfOffs_t* end; + unsigned cnt; +} _cmFfOffsList_t; + +typedef struct _cmFfToC_str +{ + unsigned streamId; // + unsigned mtxType; // kInvalidMId when used with frmToC + unsigned mtxUnitsId; // kInvalidUId when used with frmToC + unsigned mtxFmtId; // kInvalidFmtId when used with frmToC + _cmFfOffsList_t offsList; // + unsigned lastFrmIdx; // used to prevent duplicate records during ToC creation + struct _cmFfToC_str* linkPtr; +} _cmFfToC_t; + +// private matrix desc record +typedef struct +{ + cmFfMtx_t m; // public mtx description record + unsigned byteCnt; // bytes in this->dataPtr block + void* dataPtr; // pointer to data for this mtx +} _cmFfMtx_t; + +// private frame desc record +typedef struct +{ + cmFfFrame_t f; // public frame description record + unsigned byteCnt; // byte count of frame file chunk + _cmFfMtx_t* mtxArray; // mtx ctl record array + char* dataPtr; // all memory used by all mtx's in this frame +} _cmFfFrame_t; + +typedef struct +{ + cmErr_t err; + cmCtx_t ctx; + FILE* fp; // + bool writeFl; // file is open for writing + unsigned fileChkByteCnt; // + unsigned nxtFrmIdx; // index of the next frame after the current frame + unsigned curFrmIdx; // write: not used read:index of currently loaded frame + off_t frameOffset; // read: offset to first mtx hdr in cur frame + // write:offset to cur frame hdr + off_t rewOffset; // rewind offset (first frame) + off_t eofOffset; // last frame (offset data frame) + cmFfFile_t f; // file header + _cmFfFrame_t frame; // cur frame + cmLHeapH_t lhH; // linked heap handle + _cmFfToC_t* mtxToC; // one ToC recd for each existing matrix stream/type/units/fmt combination + _cmFfToC_t* frmToC; // one ToC recd for each stream + void* writeMtxMem; + bool swapFl; +} cmFf_t; + +typedef struct +{ + unsigned fmtId; + unsigned wordByteCnt; + const char* label; +} _cmFfFmt_t; + +_cmFfFmt_t _cmFfFmtArray[] = +{ + { kUCharFmtId, 1, "char" }, + { kCharFmtId, 1, "uchar" }, + { kUShortFmtId, 2, "ushort" }, + { kShortFmtId, 2, "short" }, + { kULongFmtId, 4, "ulong" }, + { kLongFmtId, 4, "long" }, + { kUIntFmtId, 4, "uint" }, + { kIntFmtId, 4, "int" }, + { kLLongFmtId, 8, "llong" }, + { kULLongFmtId, 8, "ullong" }, + { kOff_tFmtId, sizeof(off_t), "off_t"}, + { kFloatFmtId, 4, "float" }, + { kDoubleFmtId, 8, "double" }, + { kStringZFmtId, 1, "string" }, + { kBlobFmtId, 1, "blob" }, + { kJsonFmtId, 1, "json" }, + { kInvalidFmtId, 0, "" } +}; + + +cmFrameFileH_t cmFrameFileNullHandle = { NULL }; +/* +void _cmFfPrint( cmFf_t* p, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + + if( p == NULL || p->vPrintFunc == NULL ) + vfprintf(stderr,fmt,vl); + else + p->vPrintFunc(p->rptDataPtr,fmt,vl); + + va_end(vl); + +} + +cmFfRC_t _cmFfVError( cmFf_t* p, cmFfRC_t rc, int sysErrCode, const char* fmt, va_list vl ) +{ + int bufCharCnt = 256; + char buf0[bufCharCnt+1]; + char buf1[bufCharCnt+1]; + char buf2[bufCharCnt+1]; + + snprintf(buf0,bufCharCnt,"cmFrameFile Error: (%i): ",rc ); + vsnprintf(buf1,bufCharCnt,fmt,vl); + snprintf(buf2,bufCharCnt,"System Error: "); + + + unsigned sn = strlen(buf0) + strlen(buf1); + + sn += sysErrCode == 0 ? 0 : strlen(buf2) + strlen(strerror(sysErrCode)); + + char buf3[sn+1]; + buf3[sn] = 0; + buf3[0] = 0; + + strncpy(buf3, buf0, sn-strlen(buf3) ); + strncat(buf3, buf1, sn-strlen(buf3) ); + if( sysErrCode ) + { + strncat(buf3,buf2, sn - strlen(buf3) ); + strncat(buf3,strerror(sysErrCode), sn - strlen(buf3) ); + } + + assert(strlen(buf3)==sn); + + _cmFfPrint(p,"%s\n",buf3); + + return rc; + +} +*/ + +cmFfRC_t _cmFfVError( cmFf_t* p, cmFfRC_t rc, int sysErrCode, const char* fmt, va_list vl ) +{ + if( p != NULL ) + return cmErrVSysMsg(&p->err,rc,sysErrCode,fmt,vl); + + printf("cmFrameFile Error: rc=%i ",rc); + vprintf(fmt,vl); + printf("\n"); + + if( sysErrCode ) + printf("cmFrameFile System Error code=%i %s\n\n",sysErrCode,strerror(sysErrCode)); + + return rc; +} + +cmFfRC_t _cmFfError( cmFf_t* p, cmFfRC_t rc, int sysErrCode, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + _cmFfVError( p, rc, sysErrCode, fmt, vl ); + va_end(vl); + return rc; +} + +cmFf_t* _cmFfHandleToPtr( cmFrameFileH_t h ) +{ + cmFf_t* p = (cmFf_t*)h.h; + if( p == NULL ) + _cmFfError(NULL,kInvalidHandleFfRC,0,"Null handle."); + assert( p != NULL); + return p; +} + +_cmFfFmt_t* _cmFfIdToFmtPtr( unsigned fmtId ) +{ + unsigned i; + for(i=0; _cmFfFmtArray[i].fmtId != kInvalidFmtId; ++i) + if( _cmFfFmtArray[i].fmtId == fmtId ) + break; + + return _cmFfFmtArray + i; +} + + +const void* _cmFfSwapVector( void* dV, const void* sV, unsigned n, unsigned bn ) +{ + unsigned i; + switch( bn ) + { + case 1: + return sV; + + case 2: + { + const unsigned short* x = (const unsigned short*)sV; + unsigned short* y = (unsigned short*)dV; + for(i=0; ifp) != 1 ) + return _cmFfError( p, kFileWriteFailFfRC, errno, "File write failed." ); + + p->fileChkByteCnt += byteCnt; + p->frame.byteCnt += byteCnt; + return kOkFfRC; +} + +cmFfRC_t _cmFfWriteOff_t( cmFf_t* p, off_t v ) +{ + cmFfRC_t rc; + + assert(sizeof(off_t)==8); + + v = _cmFfSwap64(p->swapFl,v); + + if((rc = _cmFfWrite(p,&v,sizeof(v))) != kOkFfRC ) + return rc; + + return kOkFfRC; +} + +cmFfRC_t _cmFfWriteUInt( cmFf_t* p, unsigned v ) +{ + cmFfRC_t rc; + + v = _cmFfSwap32(p->swapFl,v); + + if((rc = _cmFfWrite(p,&v,sizeof(v))) != kOkFfRC ) + return rc; + + return kOkFfRC; +} + +cmFfRC_t _cmFfWriteUIntV( cmFf_t* p, const unsigned* vp, unsigned n ) +{ + unsigned i; + cmFfRC_t rc; + + for(i=0; iswapFl,v); + + if((rc = _cmFfWrite(p, &vv, sizeof(vv))) != kOkFfRC ) + return rc; + + return kOkFfRC; +} + +cmFfRC_t _cmFfRead( cmFf_t* p, void* vp, unsigned bn ) +{ + if(fread(vp,bn,1,p->fp) != 1 ) + { + if( feof(p->fp) ) + return kEofFfRC; + + return _cmFfError( p, kFileReadFailFfRC, errno, "File read failed."); + } + + return kOkFfRC; + +} + +cmFfRC_t _cmFfReadOff_t( cmFf_t* p, off_t* vp ) +{ + cmFfRC_t rc; + + assert( sizeof(off_t)==8); + + if((rc = _cmFfRead(p,vp,sizeof(*vp))) != kOkFfRC ) + return rc; + + *vp = _cmFfSwap64(p->swapFl,*vp); + return kOkFfRC; +} + +cmFfRC_t _cmFfReadUInt( cmFf_t* p, unsigned* vp ) +{ + cmFfRC_t rc; + if((rc = _cmFfRead(p,vp,sizeof(*vp))) != kOkFfRC ) + return rc; + + *vp = _cmFfSwap32(p->swapFl,*vp); + return kOkFfRC; +} + +cmFfRC_t _cmFfReadDouble( cmFf_t* p, double* vp ) +{ + cmFfRC_t rc; + unsigned long long v; + if((rc = _cmFfRead(p,&v,sizeof(v))) != kOkFfRC ) + return rc; + + *vp = _cmFfRdSwapD(p->swapFl,v); + + return rc; +} + + +cmFfRC_t _cmFfTell( cmFf_t* p, off_t* offsPtr ) +{ + if((*offsPtr = ftello( p->fp )) == -1 ) + return _cmFfError( p, kFileTellFailFfRC, errno, "File tell failed."); + + return kOkFfRC; +} + +cmFfRC_t _cmFfSeek( cmFf_t* p, int whence, off_t offset ) +{ + //if( p->writeFl ) + // return _cmFfError( p, kInvalidFileModeFfRC, 0, "Cannot seek on file opened for writing."); + + if(fseeko(p->fp, offset, whence) != 0 ) + return _cmFfError( p, kFileSeekFailFfRC, errno, "File seek failed."); + + return kOkFfRC; +} + + +//------------------------------------------------------------------------------------------- + +// append a _cmFfOffs_t record to a _cmFfOffsList +void _cmFfAppendOffsList( cmFf_t* p, _cmFfOffsList_t* lp, unsigned frmIdx, unsigned offs ) +{ + _cmFfOffs_t* op = (_cmFfOffs_t*)cmLHeapAllocZ( p->lhH, sizeof(_cmFfOffs_t) ); + + op->frmIdx = frmIdx; + op->offs = offs; + + if( lp->end != NULL ) + lp->end->linkPtr = op; + else + { + assert( lp->beg == NULL ); + } + + lp->end = op; + + if( lp->beg == NULL ) + { + assert( lp->end == op ); + lp->beg = op; + } + + ++lp->cnt; +} + +// locate a ToC record in a ToC list +_cmFfToC_t* _cmFfFindToCPtr( cmFf_t* p, _cmFfToC_t* cp, unsigned streamId, unsigned mtxType, unsigned mtxUnitsId, unsigned mtxFmtId ) +{ + + while( cp != NULL ) + { + if( cp->streamId==streamId && cp->mtxType==mtxType && cp->mtxUnitsId==mtxUnitsId && cp->mtxFmtId==mtxFmtId ) + break; + + cp = cp->linkPtr; + } + + return cp; +} + + +cmFfRC_t _cmFfAppendToC( cmFf_t* p, _cmFfToC_t** tocPtrPtr, unsigned streamId, unsigned mtxType, unsigned mtxUnitsId, unsigned mtxFmtId, unsigned absFrameIdx, off_t fileOffset ) +{ + cmFfRC_t rc = kOkFfRC; + _cmFfToC_t* tocPtr = *tocPtrPtr; + _cmFfToC_t* cp; + + // use p->eofOffset as a flags to prevent appending the TOC matrices themselves to the TOC + if( p->writeFl && p->eofOffset != cmInvalidIdx ) + return rc; + + // find the contents record associated with this matrix stream,type,fmt,units + if(( cp = _cmFfFindToCPtr(p,tocPtr,streamId,mtxType,mtxUnitsId,mtxFmtId)) == NULL ) + { + // no existing contents recd was found so create a new one + cp = (_cmFfToC_t*)cmLHeapAllocZ( p->lhH, sizeof(_cmFfToC_t)); + cp->streamId = streamId; + cp->mtxType = mtxType; + cp->mtxUnitsId = mtxUnitsId; + cp->mtxFmtId = mtxFmtId; + cp->linkPtr = tocPtr; + cp->lastFrmIdx = cmInvalidIdx; + + //printf("create : stream:%i type:0x%x units:%i fmt:%i\n",streamId,mtxType,mtxUnitsId,mtxFmtId); + + *tocPtrPtr = cp; + } + + assert( p->nxtFrmIdx > 0 ); + + // verify that this frame does not have multiple matrixes of the same type + // (this would result in multiple identical _cmFfOffs_t records being written for the same _cmFfToC_t record) + if( absFrameIdx == cp->lastFrmIdx ) + rc = _cmFfError( p, kDuplicateMtxIdFfRC, 0, "Duplicate matrix types were found in the same frame: stream:%i type:%i units:%i fmt:%i.",streamId, mtxType, mtxUnitsId, mtxFmtId ); + + cp->lastFrmIdx = absFrameIdx; + + _cmFfAppendOffsList(p, &cp->offsList, absFrameIdx, fileOffset ); + + return rc; +} + +cmFfRC_t _cmFfAppendMtxToC( cmFf_t* p, unsigned streamId, unsigned mtxType, unsigned mtxUnitsId, unsigned mtxFmtId, unsigned absFrameIdx, off_t mtxFileOff ) +{ return _cmFfAppendToC(p, &p->mtxToC, streamId, mtxType, mtxUnitsId, mtxFmtId, absFrameIdx, mtxFileOff ); } + + +cmFfRC_t _cmFfAppendFrameToC( cmFf_t* p, unsigned streamId, unsigned absFrameIdx, off_t frmFileOff ) +{ return _cmFfAppendToC(p, &p->frmToC, streamId, kInvalidMId, kInvalidUId, kInvalidFmtId, absFrameIdx, frmFileOff ); } + + + +//------------------------------------------------------------------- + + +cmFfRC_t _cmFfWriteOffsList( cmFrameFileH_t h, _cmFfOffsList_t* lp, unsigned mtxId, void** arrayPtrPtr, unsigned* extraV, unsigned extraN ) +{ + cmFfRC_t rc = kOkFfRC; + unsigned i = 0; + unsigned j = 0; + unsigned n = (extraN + lp->cnt * sizeof(unsigned)) + (lp->cnt * sizeof(unsigned long long)); + + // allocate memory + *arrayPtrPtr = cmMemResizeZ( unsigned, *arrayPtrPtr, n ); + + unsigned *idxV = (unsigned*)(*arrayPtrPtr); + off_t* offV = (off_t*)(idxV + extraN + lp->cnt); + + // store the extra values + for(i=0; ibeg; + + while( op != NULL ) + { + idxV[i] = op->frmIdx; + ++i; + + offV[j] = op->offs; + ++j; + + op = op->linkPtr; + } + + assert( i == extraN + lp->cnt ); + assert( j == lp->cnt ); + + + // write the frame index vector + if((rc = cmFrameFileWriteMtxUInt(h, mtxId, kInvalidUId, idxV, extraN + lp->cnt, 1 )) != kOkFfRC ) + goto errLabel; + + // write the frame offset vector + if((rc = cmFrameFileWriteMtxOff_t(h, mtxId, kInvalidUId, offV, lp->cnt, 1 )) != kOkFfRC ) + goto errLabel; + + errLabel: + return rc; + +} + +cmFfRC_t _cmFfWriteToC( cmFrameFileH_t h, _cmFfToC_t* tocPtr, unsigned* mtxIdPtr, void** memPtr ) +{ + cmFfRC_t rc = kOkFfRC; + + // write the mtx offset matrix + _cmFfToC_t* cp = tocPtr; + + while( cp != NULL ) + { + enum { hdrN = 4 }; + + unsigned hdrV[hdrN]; + + // add 4 elements to the frame index vector containing header information + hdrV[0] = cp->streamId; + hdrV[1] = cp->mtxType; + hdrV[2] = cp->mtxUnitsId; + hdrV[3] = cp->mtxFmtId; + + //printf("write : stream:%i type:0x%x units:%i fmt:%i\n",cp->streamId,cp->mtxType,cp->mtxUnitsId,cp->mtxFmtId); + + if((rc = _cmFfWriteOffsList(h,&cp->offsList,*mtxIdPtr,memPtr,hdrV,hdrN)) != kOkFfRC ) + goto errLabel; + + --(*mtxIdPtr); + + cp = cp->linkPtr; + } + + errLabel: + return rc; +} + +cmFfRC_t _cmFfWriteTocFrame( cmFrameFileH_t h ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + void* uV = NULL; + unsigned mtxId = kTocMId; + + // seek to the end of the file + if((rc = _cmFfSeek(p,SEEK_END,0)) != kOkFfRC ) + goto errLabel; + + // store the offset to this frame + if((rc = _cmFfTell(p,&p->eofOffset)) != kOkFfRC ) + goto errLabel; + + // create the offset data frame + if((rc = cmFrameFileFrameCreate(h, kTocFrameTId, kTocStreamId, cmInvalidIdx, DBL_MAX)) != kOkFfRC ) + goto errLabel; + + // write the frame offset ToC + if((rc = _cmFfWriteToC(h, p->frmToC, &mtxId, &uV )) != kOkFfRC ) + goto errLabel; + + // write the mtx offset ToC + if((rc = _cmFfWriteToC(h, p->mtxToC, &mtxId, &uV )) != kOkFfRC ) + goto errLabel; + + // write the EOF frame + if((rc = cmFrameFileFrameClose(h)) != kOkFfRC ) + goto errLabel; + + // decrease the frameCnt so that the eof frame is not included in the file header frame count + //--p->f.frameCnt; + + errLabel: + cmMemPtrFree(&uV); + return rc; +} + +cmFfRC_t _cmFfLoadTocFrame( cmFrameFileH_t h ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + cmFfRC_t rc = kOkFfRC; + const cmFfFrame_t* frmDescPtr = NULL; + off_t orgOff; + unsigned i,j,k; + + if((rc = _cmFfTell(p,&orgOff)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfSeek(p,SEEK_SET,p->eofOffset)) != kOkFfRC ) + goto errLabel; + + if((rc = cmFrameFileFrameNext(h, kTocFrameTId, kTocStreamId )) != kOkFfRC ) + { + rc = _cmFfError( p, kTocFrameRdFailFfRC, 0, "Error reading EOF frame header."); + goto errLabel; + } + + if((rc = cmFrameFileFrameLoad(h, &frmDescPtr)) != kOkFfRC ) + { + rc = _cmFfError( p, kTocFrameRdFailFfRC, 0, "Error loading EOF frame."); + goto errLabel; + } + + for(i=0,j=0; imtxCnt; i+=2,++j) + { + const cmFfMtx_t* frmIdxMtxDescPtr = NULL; + const cmFfMtx_t* offsMtxDescPtr = NULL; + const unsigned* frmIdxV; + const off_t* frmOffV; + + // read the frame index vector + if((frmIdxV = cmFrameFileMtxUInt( h, kTocMId-j, kInvalidUId, &frmIdxMtxDescPtr )) == NULL ) + { + rc = _cmFfError( p, kTocFrameRdFailFfRC, 0, "Matrix frame index read failed for matrix type id %i.",kTocMId-j); + goto errLabel; + } + + // read the offset vector + if((frmOffV = cmFrameFileMtxOff_t( h, kTocMId-j, kInvalidUId, &offsMtxDescPtr )) == NULL ) + { + rc = _cmFfError( p, kTocFrameRdFailFfRC, 0, "Matrix frame offset read failed for matrix type id %i.",kTocMId-j); + goto errLabel; + } + + assert( frmIdxMtxDescPtr->rowCnt>=4 && frmIdxMtxDescPtr->rowCnt-4 == offsMtxDescPtr->rowCnt ); + + // decode the frame index header + unsigned streamId = frmIdxV[0]; + unsigned mtxType = frmIdxV[1]; + unsigned mtxUnitsId = frmIdxV[2]; + unsigned mtxFmtId = frmIdxV[3]; + + // increment the frame index vector passed the header + frmIdxV += 4; + + + bool frmTocFl = mtxType==kInvalidMId && mtxUnitsId==kInvalidUId && mtxFmtId==kInvalidUId; + + for(k=0; krowCnt && rc==kOkFfRC; ++k) + { + if( frmTocFl ) + rc = _cmFfAppendFrameToC(p, streamId, frmIdxV[k], frmOffV[k] ); + else + rc = _cmFfAppendMtxToC(p, streamId, mtxType, mtxUnitsId, mtxFmtId, frmIdxV[k], frmOffV[k] ); + } + } + + + if((rc = _cmFfSeek(p,SEEK_SET,orgOff)) != kOkFfRC ) + goto errLabel; + + errLabel: + return rc; +} + +//-------------------------------------------------------------------- + +cmFfRC_t _cmFrameFileFree( cmFf_t* p ) +{ + cmFfRC_t rc = kOkFfRC; + + if( p == NULL ) + return rc; + + // free the frame data ptr + cmMemPtrFree(&p->frame.dataPtr); + + // free the mtx array ptr + cmMemPtrFree(&p->frame.mtxArray); + + // close the file + if( p->fp != NULL ) + { + if( fclose(p->fp) == EOF ) + rc = _cmFfError(p,kFileCloseFailFfRC,errno,"File close failed."); + else + p->fp = NULL; + } + + cmMemPtrFree(&p->writeMtxMem); + + // release the filename string + cmMemPtrFree(&p->f.filenameStr); + + cmLHeapDestroy(&p->lhH); + + cmMemPtrFree(&p); + + return rc; +} + + +cmFfRC_t cmFrameFileCreate( cmFrameFileH_t* hPtr, const char* fn, double srate, cmCtx_t* ctx ) +{ + cmFfRC_t rc; + + // be sure the handle is not already in use + if( (rc = cmFrameFileClose(hPtr)) != kOkFfRC ) + return rc; + + unsigned version = 0; + cmFf_t* p; + + // allocate the file object + if((p = cmMemAllocZ( cmFf_t, 1 )) == NULL ) + return _cmFfError(NULL,kMemAllocErrFfRC,0,"Memory allocation failed."); + + cmErrSetup(&p->err,&ctx->rpt,"FrameFile"); + p->ctx = *ctx; + + // create the linked heap + if( cmLHeapIsValid(p->lhH = cmLHeapCreate( 16384, ctx )) == false ) + { + rc = _cmFfError( p, kLHeapFailFfRC,0,"Linked heap create failed."); + goto errLabel; + } + + // create the output file + if((p->fp = fopen(fn,"w+b")) == NULL ) + { + rc = _cmFfError( p,kFileOpenFailFfRC,errno,"Unable to create the file:'%s'.",fn); + goto errLabel; + } + + // type, byteCnt, frameCnt, , version + unsigned v[] = { kFileFfTId, 0, 0, version }; + if((rc = _cmFfWriteUIntV( p, v, sizeof(v)/sizeof(unsigned) ) ) != kOkFfRC ) + goto errLabel; + + // eof frame offset + if((rc = _cmFfWriteOff_t( p, p->eofOffset)) != kOkFfRC ) + goto errLabel; + + // file sample rate + if((rc = _cmFfWriteDouble( p, srate ) ) != kOkFfRC ) + goto errLabel; + + + p->writeFl = true; + p->fileChkByteCnt = 4 * sizeof(unsigned); // hdr bytes after byteCnt + p->nxtFrmIdx = 1; + p->curFrmIdx = cmInvalidIdx; + p->frameOffset = cmInvalidIdx; + p->eofOffset = cmInvalidIdx; + p->f.frameCnt = 0; + p->f.srate = srate; + p->f.version = version; + p->f.filenameStr = cmMemResizeStr(p->f.filenameStr,fn); + p->swapFl = false; + hPtr->h = p; + + if((rc = _cmFfTell( p, &p->rewOffset )) != kOkFfRC ) + goto errLabel; + + return rc; + + errLabel: + + _cmFrameFileFree(p); + + return rc; + +} + +cmFfRC_t cmFrameFileOpen( cmFrameFileH_t* hPtr, const char* fn, cmCtx_t* ctx, const cmFfFile_t** fileDescPtrPtr ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p; + unsigned fileId; + + if( fileDescPtrPtr != NULL ) + *fileDescPtrPtr = NULL; + + // be sure the handle is not already in use + if((rc = cmFrameFileClose(hPtr)) != kOkFfRC ) + return rc; + + // allocate the file object + if((p = cmMemAllocZ( cmFf_t, 1 )) == NULL ) + return _cmFfError(NULL,kMemAllocErrFfRC,0,"Memory allocation failed."); + + cmErrSetup(&p->err,&ctx->rpt,"Frame File"); + p->ctx = *ctx; + + + // create the linked heap + if( cmLHeapIsValid(p->lhH = cmLHeapCreate( 2048, ctx )) == false ) + { + rc = _cmFfError( p, kLHeapFailFfRC,0,"Linked heap create failed."); + goto errLabel; + } + + // open the file for reading + if((p->fp = fopen(fn,"r+b")) == NULL ) + { + rc = _cmFfError( p,kFileOpenFailFfRC,errno,"Unable to open the file:'%s'.",fn); + goto errLabel; + } + + p->writeFl = false; + + // file type id + if((rc = _cmFfReadUInt( p, &fileId ) ) != kOkFfRC ) + goto errLabel; + + // verify that this is a frame file + if( fileId != kFileFfTId ) + { + if( cmSwap32(fileId) == kFileFfTId ) + p->swapFl = true; + else + { + rc = _cmFfError( p,kNotFrameFileFfRC,0,"'%s' is not a frame file.",fn); + goto errLabel; + } + } + + // file chunk size + if((rc = _cmFfReadUInt( p, &p->fileChkByteCnt ) ) != kOkFfRC ) + goto errLabel; + + // file frame count + if((rc = _cmFfReadUInt( p, &p->f.frameCnt ) ) != kOkFfRC ) + goto errLabel; + + // file format version + if((rc = _cmFfReadUInt( p, &p->f.version ) ) != kOkFfRC ) + goto errLabel; + + // eof offset + if((rc = _cmFfReadOff_t( p, &p->eofOffset) ) != kOkFfRC ) + goto errLabel; + + // file sample rate + if((rc = _cmFfReadDouble( p, &p->f.srate ) ) != kOkFfRC ) + goto errLabel; + + p->f.filenameStr = cmMemResizeStr(p->f.filenameStr,fn); + p->nxtFrmIdx = 0; + p->curFrmIdx = cmInvalidIdx; + p->frameOffset = cmInvalidIdx; + + hPtr->h = p; + + if((rc = _cmFfLoadTocFrame(*hPtr)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfTell(p,&p->rewOffset)) != kOkFfRC ) + goto errLabel; + + if( fileDescPtrPtr != NULL ) + *fileDescPtrPtr = &p->f; + + return rc; + + errLabel: + + _cmFrameFileFree(p); + + hPtr->h = NULL; + + return rc; + +} + + +cmFfRC_t cmFrameFileClose( cmFrameFileH_t* hp ) +{ + cmFfRC_t rc; + + if( hp== NULL || cmFrameFileIsValid(*hp)==false) + return kOkFfRC; + + cmFf_t* p = _cmFfHandleToPtr(*hp); + + if( p->fp != NULL ) + { + + // update the file header + if( p->writeFl ) + { + if((rc = _cmFfWriteTocFrame(*hp)) != kOkFfRC ) + return rc; + + // rewind into the file header + if((rc = _cmFfSeek( p, SEEK_SET, sizeof(unsigned) )) != kOkFfRC ) + return rc; + + // update the file chunk size + if((rc = _cmFfWriteUInt( p, p->fileChkByteCnt ) ) != kOkFfRC ) + return rc; + + // update the file frame count + if((rc = _cmFfWriteUInt( p, p->f.frameCnt ) ) != kOkFfRC ) + return rc; + + // rewrite the version + if((rc = _cmFfWriteUInt( p, p->f.version ) ) != kOkFfRC ) + return rc; + + // update the eof frame offset + if((rc = _cmFfWriteOff_t(p, p->eofOffset ) ) != kOkFfRC ) + return rc; + } + } + + _cmFrameFileFree(p); + + hp->h = NULL; + + return kOkFfRC; +} + +bool cmFrameFileIsValid( cmFrameFileH_t h ) +{ return h.h != NULL; } + +const cmFfFile_t* cmFrameFileDesc( cmFrameFileH_t h ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + return &p->f; +} + +unsigned cmFrameFileFrameCount( cmFrameFileH_t h, unsigned streamId ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + _cmFfToC_t* cp = p->frmToC; + + while(cp != NULL ) + { + if( cp->streamId == streamId ) + return cp->offsList.cnt; + + cp = cp->linkPtr; + } + + return 0; +} + +cmFfRC_t cmFrameFileFrameCreate( cmFrameFileH_t h, unsigned frameType, unsigned streamId, unsigned sampleIdx, double secs ) +{ + cmFfRC_t rc; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned flags = sampleIdx == -1 ? kSecondsTimeFl : kSampleIdxTimeFl; + + if( p->writeFl == false ) + return _cmFfError( p, kInvalidFileModeFfRC, 0, "Cannot create new frames on frame files opened in read mode."); + + + // save the frame offset for later use in cmFrameFileCloseFrame() + if((rc = _cmFfTell(p,&p->frameOffset)) != kOkFfRC ) + return rc; + + // update the frame offset list + assert( p->nxtFrmIdx > 0 ); + rc = _cmFfAppendFrameToC(p, streamId, p->nxtFrmIdx-1, p->frameOffset ); + + + // frame: type, byteCnt, mtxCnt, streamId, flags, sampleIdx + unsigned v[] = { frameType, 0, 0, streamId, flags, sampleIdx }; + if((rc = _cmFfWriteUIntV( p, v, sizeof(v)/sizeof(unsigned) ) ) != kOkFfRC ) + return rc; + + if((rc = _cmFfWriteDouble( p, secs)) != kOkFfRC ) + return rc; + + p->frame.f.type = frameType; + p->frame.byteCnt = 6 * sizeof(unsigned); + p->frame.f.mtxCnt = 0; + p->frame.f.streamId = streamId; + p->frame.f.flags = flags; + p->frame.f.time.seconds = 0; + + return rc; +} + +cmFfRC_t cmFrameFileFrameClose( cmFrameFileH_t h ) +{ + cmFfRC_t rc = kOkFfRC; + + if( h.h == NULL ) + return kOkFfRC; + + cmFf_t* p = _cmFfHandleToPtr(h); + + // frames open in read-mode do not need to be closed + if( p->writeFl == false ) + return kOkFfRC; + + assert( p->frameOffset != 0 ); + + // store the current file position + off_t offs; // = ftello(p->fp); + if((rc = _cmFfTell(p,&offs)) != kOkFfRC ) + return rc; + + // seek to the frame byte count + if((rc = _cmFfSeek( p, SEEK_SET, p->frameOffset+sizeof(unsigned) )) != kOkFfRC ) + return rc; + + // write frame byteCnt + if((rc = _cmFfWriteUInt( p, p->frame.byteCnt ) ) != kOkFfRC ) + return rc; + + // write frame mtxCnt + if((rc = _cmFfWriteUInt( p, p->frame.f.mtxCnt ) ) != kOkFfRC ) + return rc; + + p->f.frameCnt++; + p->nxtFrmIdx++; + p->frameOffset = 0; + + p->frame.byteCnt = 0; + memset( &p->frame.f, 0, sizeof(p->frame.f)); + + + + // jump back to the end of the file + return _cmFfSeek(p, SEEK_SET, offs ); + +} + + +cmFfRC_t _cmFrameFileWriteMtx( cmFf_t* p, unsigned type, unsigned unitsId, unsigned fmtId, const void* dataPtr, unsigned rn, unsigned cn, bool writeTocFl ) +{ + cmFfRC_t rc; + + // track the file offset to this matrix + if( p->writeFl && writeTocFl ) + { + off_t fileOff; + + // get file offs to this mtx + if((rc = _cmFfTell(p,&fileOff)) != kOkFfRC ) + return rc; + + assert( p->nxtFrmIdx >= 1 ); + + // append a recd representing this matrix to the mtx TOC + rc = _cmFfAppendMtxToC(p, p->frame.f.streamId, type, unitsId, fmtId, p->nxtFrmIdx-1, fileOff ); + } + + unsigned wordByteCnt = _cmFfIdToFmtPtr(fmtId)->wordByteCnt; + unsigned byteCnt = rn*cn*wordByteCnt; + + + // write the mtx header + // mtx: type, byteCnt, fmtId, unitsId, rowCnt, colCnt + unsigned v[] = { type, byteCnt, fmtId, unitsId, rn, cn }; + if((rc = _cmFfWriteUIntV( p, v, sizeof(v)/sizeof(unsigned))) != kOkFfRC ) + return rc; + + const void* src_buf = dataPtr; + + if( p->swapFl ) + { + p->writeMtxMem = cmMemResize( char, p->writeMtxMem, byteCnt ); + src_buf = _cmFfSwapVector(p->writeMtxMem,src_buf,rn*cn,wordByteCnt); + } + + // write the mtx data + if(( rc = _cmFfWrite(p,src_buf,byteCnt)) != kOkFfRC ) + return rc; + + + // write pad - all matrices must end on 64 bit boundaries + unsigned n = byteCnt % 8; + + if( n ) + { + assert( n < 8 ); + + char v[8]; + memset(v,0,8); + if(( rc = _cmFfWrite(p,v,n)) != kOkFfRC ) + return rc; + + } + + ++p->frame.f.mtxCnt; + + return kOkFfRC; +} + +cmFfRC_t cmFrameFileWriteMtx( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, unsigned dataFmtId, const void* dataPtr, unsigned rn, unsigned cn ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + + return _cmFrameFileWriteMtx(p, mtxType, unitsId, dataFmtId, dataPtr, rn, cn, true ); +} + +cmFfRC_t cmFrameFileWriteMtxUChar( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned char* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kUCharFmtId, dataPtr, rn, cn ); } + +cmFfRC_t cmFrameFileWriteMtxChar( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const char* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kCharFmtId, dataPtr, rn, cn ); } + +cmFfRC_t cmFrameFileWriteMtxUShort( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned short* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kUShortFmtId, dataPtr,rn,cn ); } + +cmFfRC_t cmFrameFileWriteMtxShort( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const short* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kShortFmtId, dataPtr, rn, cn ); } + +cmFfRC_t cmFrameFileWriteMtxULong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned long* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kULongFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxLong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const long* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kLongFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxUInt( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kUIntFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxInt( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const int* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kIntFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxULLong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned long long* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kULLongFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxLLong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const long long* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kLLongFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxOff_t( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const off_t* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kOff_tFmtId, dataPtr, rn, cn );} + +cmFfRC_t cmFrameFileWriteMtxFloat( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const float* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kFloatFmtId, dataPtr, rn, cn ); } + +cmFfRC_t cmFrameFileWriteMtxDouble( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const double* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kDoubleFmtId, dataPtr, rn, cn ); } + +cmFfRC_t cmFrameFileWriteMtxBlob( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const void* dataPtr, unsigned rn, unsigned cn ) +{ return cmFrameFileWriteMtx( h, mtxType, unitsId, kBlobFmtId, dataPtr, rn, cn ); } + + +cmFfRC_t cmFrameFileWriteMtxStringZ( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const char* stringPtr ) +{ + unsigned n = strlen(stringPtr); + return cmFrameFileWriteMtx( h, mtxType, kInvalidUId, kStringZFmtId, stringPtr, n+1, 1 ); +} + +cmFfRC_t cmFrameFileWriteMtxJson( cmFrameFileH_t h, unsigned mtxType, cmJsonH_t jsH, const cmJsonNode_t* nodePtr ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + void* buf = NULL; + unsigned bufByteCnt = 0; + + if( cmJsonSerializeTree( jsH, nodePtr, &buf, &bufByteCnt ) != kOkJsRC ) + return _cmFfError(p,kJsonFailFfRC,0,"JSON serialuze failed."); + + return _cmFrameFileWriteMtx(p, mtxType, kNoUnitsUId, kJsonFmtId, buf, 1, bufByteCnt, true ); +} + +// Can only be called when p->fp is pointed to the beginning of a frame. +// Leaves file pointing to frame header 'flags' field. +cmFfRC_t _cmFrameFileFrameSeek( cmFf_t* p, unsigned keyFrameTypeId, unsigned keyFrameStreamId ) +{ + cmFfRC_t rc = kOkFfRC; + + while( rc == kOkFfRC ) + { + // frame type + if((rc = _cmFfReadUInt(p,&p->frame.f.type)) != kOkFfRC ) + break; + + // frame byte count + if((rc = _cmFfReadUInt(p,&p->frame.byteCnt)) != kOkFfRC ) + break; + + // frame mtx count + if((rc = _cmFfReadUInt(p,&p->frame.f.mtxCnt)) != kOkFfRC ) + return rc; + + // frame stream id + if((rc = _cmFfReadUInt(p,&p->frame.f.streamId)) != kOkFfRC ) + break; + + // condition: no match on type + if( (keyFrameTypeId == kInvalidFrameTId) && (keyFrameStreamId == kInvalidFrameTId || keyFrameStreamId == p->frame.f.streamId) ) + break; + + // condition: match on type + if( (keyFrameTypeId == p->frame.f.type) && (keyFrameStreamId == kInvalidFrameTId || keyFrameStreamId == p->frame.f.streamId) ) + break; + + // goto the next frame + if((rc = _cmFfSeek(p,SEEK_CUR,p->frame.byteCnt - (2*sizeof(unsigned)))) != kOkFfRC ) + break; + + ++p->nxtFrmIdx; + } + + return rc; + +} + +cmFfRC_t cmFrameFileRewind( cmFrameFileH_t h ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + + p->nxtFrmIdx = 0; + + return _cmFfSeek(p,SEEK_SET,p->rewOffset); +} + +cmFfRC_t cmFrameFileSeek( cmFrameFileH_t h, unsigned streamId, unsigned frameIdx ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned i = 0; + _cmFfToC_t* tocPtr; + + + + // locate the frame TOC recd assoc'd with stream id + if((tocPtr = _cmFfFindToCPtr(p, p->frmToC, streamId, kInvalidMId, kInvalidUId, kInvalidFmtId )) == NULL ) + { + rc = _cmFfError(p,kTocRecdNotFoundFfRC,0,"Unable to locate the TOC record for stream id %i.",streamId); + goto errLabel; + } + + // locate the TOC offset recd assoc'd with frameIdx + _cmFfOffs_t* cp = tocPtr->offsList.beg; + for(; cp != NULL && i!=frameIdx; ++i ) + cp = cp->linkPtr; + + + // if the frame index was not valid + if( cp == NULL ) + { + rc = _cmFfError(p,kInvalidFrameIdxFfRC,0,"%i is an invalid frame index for stream id %i.",frameIdx,streamId); + goto errLabel; + } + + // seek to the beginning of the frame + if((rc = _cmFfSeek(p,SEEK_SET,cp->offs)) != kOkFfRC ) + goto errLabel; + + errLabel: + return rc; +} + + +// Can only be called when p->fp is pointed to the beginning of a frame. +cmFfRC_t cmFrameFileFrameNext( cmFrameFileH_t h, unsigned keyFrameTypeId, unsigned keyFrameStreamId ) +{ + cmFfRC_t rc; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned sampleIdx; + double seconds; + + // go to the requested frame + if((rc = _cmFrameFileFrameSeek(p, keyFrameTypeId, keyFrameStreamId)) != kOkFfRC ) + return rc; + + // frame flags + if((rc = _cmFfReadUInt(p,&p->frame.f.flags)) != kOkFfRC ) + return rc; + + // frame sample idx + if((rc = _cmFfReadUInt(p,&sampleIdx)) != kOkFfRC ) + return rc; + + // frame seconds + if((rc = _cmFfReadDouble(p,&seconds)) != kOkFfRC ) + return rc; + + if( cmIsFlag(p->frame.f.flags,kSampleIdxTimeFl) ) + p->frame.f.time.sampleIdx = sampleIdx; + + if( cmIsFlag(p->frame.f.flags,kSecondsTimeFl) ) + p->frame.f.time.seconds = seconds; + + + return rc; +} + +cmFfRC_t _cmFrameFileCheckForDuplicateMtxId( cmFrameFileH_t h ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned i; + + for(i=0; iframe.f.mtxCnt; ++i) + { + unsigned mtxIdx = cmFrameFileMtxIndex(h, p->frame.mtxArray[i].m.type, p->frame.mtxArray[i].m.unitsId, p->frame.mtxArray[i].m.fmtId ); + + assert( mtxIdx != cmInvalidIdx ); + + if( mtxIdx != i ) + { + rc = _cmFfError( p, kDuplicateMtxIdFfRC, 0, "Duplicate matrix signatures exist form type:%i units:%i fmt:%i at frame index %i.", p->frame.mtxArray[i].m.type, p->frame.mtxArray[i].m.unitsId, p->frame.mtxArray[i].m.fmtId,p->nxtFrmIdx ); + goto errLabel; + } + + } + + errLabel: + return rc; +} + +// read a matrix header and data +cmFfRC_t _cmFfReadMtx( cmFf_t* p, _cmFfMtx_t* mp, void* buf, unsigned bufByteCnt ) +{ + cmFfRC_t rc; + + if((rc = _cmFfReadUInt(p,&mp->m.type)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadUInt(p,&mp->byteCnt)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadUInt(p,&mp->m.fmtId)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadUInt(p,&mp->m.unitsId)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadUInt(p,&mp->m.rowCnt)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadUInt(p,&mp->m.colCnt)) != kOkFfRC ) + goto errLabel; + + if( buf != NULL ) + { + if( mp->byteCnt > bufByteCnt ) + { + rc = _cmFfError(p,kBufTooSmallFfRC,0, "Matrix buffer too small to complete the read."); + goto errLabel; + } + + // read in the mtx data + if((rc = _cmFfRead(p,buf,mp->byteCnt)) != kOkFfRC ) + goto errLabel; + + + if( p->swapFl ) + { + // swap on read + _cmFfSwapVector(buf,buf,mp->m.rowCnt*mp->m.colCnt, _cmFfIdToFmtPtr(mp->m.fmtId)->wordByteCnt ); + } + + } + + + errLabel: + return rc; +} + +cmFfRC_t cmFrameFileFrameLoad( cmFrameFileH_t h, const cmFfFrame_t** frameDescPtrPtr ) +{ + cmFfRC_t rc; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned i; + + if(frameDescPtrPtr != NULL) + *frameDescPtrPtr = NULL; + + // store pointer to matrix data offset - for use in cmFrameFileFrameUpdate() + if((rc = _cmFfTell(p,&p->frameOffset)) != kOkFfRC ) + goto errLabel; + + // create a block of memory large enough to hold the entire frame + // (this is more than is actually needed because it includes the mtx header records) + p->frame.dataPtr = cmMemResizeZ( char, p->frame.dataPtr, p->frame.byteCnt ); + + // create a mtx array to hold each mtx record + p->frame.mtxArray = cmMemResizeZ( _cmFfMtx_t, p->frame.mtxArray, p->frame.f.mtxCnt ); + + char* dp = p->frame.dataPtr; + unsigned emptyByteCnt = p->frame.byteCnt; + + // for each matrix in this frame + for(i=0; iframe.f.mtxCnt; ++i) + { + _cmFfMtx_t* mp = p->frame.mtxArray + i; + + mp->dataPtr = dp; + + // read the matrix header and data + if((rc = _cmFfReadMtx(p, mp, dp, emptyByteCnt )) != kOkFfRC ) + goto errLabel; + + // read any pad bytes + unsigned n = mp->byteCnt % 8; + + if( n ) + { + char v[8]; + if((rc = _cmFfRead(p,v,n)) != kOkFfRC ) + goto errLabel; + } + + // verify the buffer size + if(mp->byteCnt > emptyByteCnt ) + { + rc = _cmFfError(p,kBufTooSmallFfRC,0, "Matrix buffer too small to complete the read."); + goto errLabel; + } + + emptyByteCnt -= mp->byteCnt; // decrement the available buffer space + dp += mp->byteCnt; // advance the matrix data buffer pointer + + } + + if(rc==kOkFfRC && frameDescPtrPtr != NULL) + *frameDescPtrPtr = &p->frame.f; + + // verify that duplicate matrx signatures do not exist. + // (only the first of the duplicate will be accessable) + assert( _cmFrameFileCheckForDuplicateMtxId(h) == kOkFfRC ); + + p->curFrmIdx = p->nxtFrmIdx; + ++p->nxtFrmIdx; + + + errLabel: + return rc; +} + +cmFfRC_t cmFrameFileFrameSkip( cmFrameFileH_t h ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned hdrBytes = 32 - 8; // sizeof(frame hdr) - (sizeof(hdr.type) + sizeof(hdr.chkbyteCnt)) + + assert(hdrBytes<=p->frame.byteCnt); + + if((rc = _cmFfSeek( p, SEEK_CUR, p->frame.byteCnt - hdrBytes)) == kOkFfRC ) + { + ++p->nxtFrmIdx; + } + + return rc; +} + +cmFfRC_t cmFrameFileFrameLoadNext( cmFrameFileH_t h, unsigned frameTypeId, unsigned streamId, const cmFfFrame_t** frameDescPtrPtr ) +{ + cmFfRC_t rc; + if((rc = cmFrameFileFrameNext(h,frameTypeId,streamId)) != kOkFfRC ) + return rc; + + return cmFrameFileFrameLoad(h,frameDescPtrPtr); +} + +cmFfRC_t cmFrameFileFrameUpdate( cmFrameFileH_t h ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned i = 0; + off_t offs; + + if((rc = _cmFfTell(p,&offs)) != kOkFfRC ) + goto errLabel; + + // seek to the matrix data + if((rc = _cmFfSeek(p, SEEK_SET, p->frameOffset )) != kOkFfRC ) + goto errLabel; + + // for each matrix + for(i=0; iframe.f.mtxCnt; ++i) + { + const _cmFfMtx_t* m = p->frame.mtxArray + i; + + // rewrite each matrix + if((rc = _cmFrameFileWriteMtx(p, m->m.type, m->m.unitsId, m->m.fmtId, m->dataPtr, m->m.rowCnt, m->m.colCnt, false )) != kOkFfRC ) + goto errLabel; + + // cmFrameFileWriteMtx increments the matrix count - so we decrement it here + --p->frame.f.mtxCnt; + } + + // restore the file position + if((rc = _cmFfSeek(p, SEEK_SET, offs )) != kOkFfRC ) + goto errLabel; + + errLabel: + return rc; + +} + +const cmFfFrame_t* cmFrameFileFrameDesc( cmFrameFileH_t h ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + return &p->frame.f; +} + +unsigned cmFrameFileFrameLoadedIndex( cmFrameFileH_t h ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + return p->curFrmIdx; + +} + +unsigned cmFrameFileMtxIndex( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, unsigned fmtId ) +{ + cmFf_t* p = _cmFfHandleToPtr(h); + unsigned i; + + for(i=0; iframe.f.mtxCnt; ++i) + { + if( mtxTypeId==kInvalidMId || mtxTypeId == p->frame.mtxArray[i].m.type ) + if( unitsId==kInvalidUId || unitsId == p->frame.mtxArray[i].m.unitsId ) + if( fmtId==kInvalidFmtId || fmtId == p->frame.mtxArray[i].m.fmtId ) + return i; + + } + + return cmInvalidIdx; +} + +const cmFfMtx_t* cmFrameFileMtxDesc( cmFrameFileH_t h, unsigned mtxIdx ) +{ cmFf_t* p = _cmFfHandleToPtr(h); + assert( mtxIdx < p->frame.f.mtxCnt ); + return &p->frame.mtxArray[ mtxIdx ].m; +} + +void* _cmFrameFileMtxIndexDataPtr( cmFrameFileH_t h, unsigned dataFmtId, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ + if( mtxIdx == cmInvalidIdx ) + return NULL; + + cmFf_t* p = _cmFfHandleToPtr(h); + + assert( mtxIdx < p->frame.f.mtxCnt ); + assert( p->frame.mtxArray[mtxIdx].m.fmtId == dataFmtId ); + + if( descPtrPtr != NULL ) + *descPtrPtr = &p->frame.mtxArray[ mtxIdx ].m; + + return p->frame.mtxArray[mtxIdx].dataPtr; +} + + + +unsigned char* cmFrameFileMtxIndexUChar( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned char*)_cmFrameFileMtxIndexDataPtr( h, kUCharFmtId, mtxIdx, descPtrPtr ); } + +char* cmFrameFileMtxIndexChar( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (char*)_cmFrameFileMtxIndexDataPtr( h, kCharFmtId, mtxIdx, descPtrPtr ); } + +unsigned short* cmFrameFileMtxIndexUShort( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned short*) _cmFrameFileMtxIndexDataPtr( h, kUShortFmtId, mtxIdx, descPtrPtr ); } + +short* cmFrameFileMtxIndexShort( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (short*)_cmFrameFileMtxIndexDataPtr( h, kShortFmtId, mtxIdx, descPtrPtr ); } + +unsigned long* cmFrameFileMtxIndexULong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned long*)_cmFrameFileMtxIndexDataPtr( h, kULongFmtId, mtxIdx, descPtrPtr ); } + +long* cmFrameFileMtxIndexLong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (long*) _cmFrameFileMtxIndexDataPtr( h, kLongFmtId, mtxIdx, descPtrPtr ); } + +unsigned* cmFrameFileMtxIndexUInt( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned*) _cmFrameFileMtxIndexDataPtr( h, kUIntFmtId, mtxIdx, descPtrPtr ); } + +int* cmFrameFileMtxIndexInt( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (int*) _cmFrameFileMtxIndexDataPtr( h, kIntFmtId, mtxIdx, descPtrPtr ); } + +unsigned long long* cmFrameFileMtxIndexULLong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned long long*) _cmFrameFileMtxIndexDataPtr( h, kULLongFmtId, mtxIdx, descPtrPtr ); } + +long long* cmFrameFileMtxIndexLLong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (long long*) _cmFrameFileMtxIndexDataPtr( h, kLLongFmtId, mtxIdx, descPtrPtr ); } + +off_t* cmFrameFileMtxIndexOff_t( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (off_t*) _cmFrameFileMtxIndexDataPtr( h, kOff_tFmtId, mtxIdx, descPtrPtr ); } + +float* cmFrameFileMtxIndexFloat( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (float*)_cmFrameFileMtxIndexDataPtr( h, kFloatFmtId, mtxIdx, descPtrPtr ); } + +double* cmFrameFileMtxIndexDouble( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (double*)_cmFrameFileMtxIndexDataPtr( h, kDoubleFmtId, mtxIdx, descPtrPtr ); } + +char* cmFrameFileMtxIndexStringZ( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return (char*)_cmFrameFileMtxIndexDataPtr( h, kStringZFmtId, mtxIdx, descPtrPtr ); } + +void* cmFrameFileMtxIndexBlob( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ return _cmFrameFileMtxIndexDataPtr( h, kBlobFmtId, mtxIdx, descPtrPtr );} + +cmJsonH_t cmFrameFileMtxIndexJson( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ) +{ + cmFfRC_t rc = kOkFfRC; + const void* buf; + const cmFfMtx_t* dp = NULL; + cmJsRC_t jsRC; + cmJsonH_t jsH = cmJsonNullHandle; + cmFf_t* p = _cmFfHandleToPtr(h); + + if( descPtrPtr != NULL ) + *descPtrPtr = NULL; + + if( (buf= _cmFrameFileMtxIndexDataPtr( h, kJsonFmtId, mtxIdx, &dp)) == NULL ) + goto errLabel; + + if((jsRC = cmJsonInitialize( &jsH, &p->ctx )) != kOkJsRC ) + { + rc = _cmFfError(p,kJsonFailFfRC,0,"JSON object allocation failed."); + goto errLabel; + } + + if((jsRC = cmJsonDeserialize( jsH, buf, NULL )) != kOkJsRC ) + { + rc = _cmFfError(p, kJsonFailFfRC, 0, "JSON deserialization failed."); + goto errLabel; + } + + errLabel: + + if( rc != kOkFfRC ) + cmJsonFinalize(&jsH); + else + if( descPtrPtr != NULL ) + *descPtrPtr = dp; + + return jsH; +} + + + +unsigned char* cmFrameFileMtxUChar( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned char*)_cmFrameFileMtxIndexDataPtr( h, kUCharFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId, kUCharFmtId), descPtrPtr ); } + +char* cmFrameFileMtxChar( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (char*)_cmFrameFileMtxIndexDataPtr( h, kCharFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kCharFmtId), descPtrPtr ); } + +unsigned short* cmFrameFileMtxUShort( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned short*)_cmFrameFileMtxIndexDataPtr( h, kUShortFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kUShortFmtId), descPtrPtr); } + +short* cmFrameFileMtxShort( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (short*)_cmFrameFileMtxIndexDataPtr( h, kShortFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kShortFmtId), descPtrPtr); } + +unsigned long* cmFrameFileMtxULong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned long*)_cmFrameFileMtxIndexDataPtr( h, kULongFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kULongFmtId), descPtrPtr ); } + +long* cmFrameFileMtxLong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (long*)_cmFrameFileMtxIndexDataPtr( h, kLongFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kLongFmtId), descPtrPtr ); } + +unsigned* cmFrameFileMtxUInt( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned*)_cmFrameFileMtxIndexDataPtr( h, kUIntFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kUIntFmtId), descPtrPtr ); } + +int* cmFrameFileMtxInt( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (int*)_cmFrameFileMtxIndexDataPtr( h, kIntFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kIntFmtId), descPtrPtr ); } + +unsigned long long* cmFrameFileMtxULLong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (unsigned long long*)_cmFrameFileMtxIndexDataPtr( h, kULLongFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kULLongFmtId), descPtrPtr ); } + +long long* cmFrameFileMtxLLong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (long long*)_cmFrameFileMtxIndexDataPtr( h, kLLongFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kLLongFmtId), descPtrPtr ); } + +off_t* cmFrameFileMtxOff_t( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (off_t*)_cmFrameFileMtxIndexDataPtr( h, kOff_tFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kOff_tFmtId), descPtrPtr ); } + +float* cmFrameFileMtxFloat( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (float*)_cmFrameFileMtxIndexDataPtr( h, kFloatFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kFloatFmtId), descPtrPtr ); } + +double* cmFrameFileMtxDouble( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (double*)_cmFrameFileMtxIndexDataPtr( h, kDoubleFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kDoubleFmtId), descPtrPtr ); } + +char* cmFrameFileMtxStringZ( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return (char*)_cmFrameFileMtxIndexDataPtr( h, kStringZFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kStringZFmtId), descPtrPtr ); } + +void* cmFrameFileMtxBlob( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ) +{ return _cmFrameFileMtxIndexDataPtr( h, kBlobFmtId, cmFrameFileMtxIndex(h,mtxTypeId,unitsId,kBlobFmtId), descPtrPtr ); } + +cmJsonH_t cmFrameFileMtxJson( cmFrameFileH_t h, unsigned mtxTypeId, const cmFfMtx_t** descPtrPtr ) +{ return cmFrameFileMtxIndexJson(h, cmFrameFileMtxIndex(h,mtxTypeId,kNoUnitsUId,kJsonFmtId), descPtrPtr ); } + + +cmFfRC_t cmFrameFileMtxSize( cmFrameFileH_t h, unsigned streamId, unsigned mtxType, unsigned unitsId, unsigned fmtId, unsigned* frmCntPtr, unsigned* rowCntPtr, unsigned* colCntPtr, unsigned* eleCntPtr ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + _cmFfToC_t* tocPtr; + _cmFfOffs_t* op; + _cmFfMtx_t mtx; + + *frmCntPtr = 0; + *eleCntPtr = 0; + *rowCntPtr = 0; + *colCntPtr = 0; + + if((tocPtr = _cmFfFindToCPtr(p, p->mtxToC, streamId, mtxType, unitsId, fmtId )) == NULL ) + { + rc = _cmFfError( p, kTocRecdNotFoundFfRC, 0, "Unable to locate the requested matrix in stream:%i mtx:%i units:%i fmt:%i.",streamId, mtxType, unitsId, fmtId ); + goto errLabel; + } + + op = tocPtr->offsList.beg; + + while(op != NULL ) + { + if((rc = _cmFfSeek(p,SEEK_SET, op->offs )) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadMtx(p,&mtx,NULL,0)) != kOkFfRC ) + goto errLabel; + + *frmCntPtr += 1; + + *eleCntPtr += mtx.m.rowCnt * mtx.m.colCnt; + + if( mtx.m.rowCnt > *rowCntPtr ) + *rowCntPtr = mtx.m.rowCnt; + + if( mtx.m.colCnt > *colCntPtr ) + *colCntPtr = mtx.m.colCnt; + + op = op->linkPtr; + } + + errLabel: + return rc; +} + +cmFfRC_t _cmFrameFileMtxLoad( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned fmtId, unsigned frmIdx, unsigned frmCnt, void* buf, unsigned bufEleCnt, unsigned* outCntPtr ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + char* dp = buf; + unsigned wordByteCnt = _cmFfIdToFmtPtr(fmtId)->wordByteCnt; + int dpn = bufEleCnt*wordByteCnt; + _cmFfToC_t* tocPtr; + _cmFfMtx_t mtx; + unsigned fi; + + if( outCntPtr != NULL ) + *outCntPtr = 0; + + if((tocPtr = _cmFfFindToCPtr(p, p->mtxToC, streamId, mtxTypeId, unitsId, fmtId )) == NULL ) + { + rc = _cmFfError( p, kTocRecdNotFoundFfRC, 0, "Unable to locate the requested matrix in stream:%i mtx:%i units:%i fmt:%i.",streamId, mtxTypeId, unitsId, fmtId ); + goto errLabel; + } + + _cmFfOffs_t* op = tocPtr->offsList.beg; + + for(fi=0; op != NULL && (frmCnt==-1 || fi<(frmIdx+frmCnt)); ++fi ) + { + if( frmIdx<=fi ) + { + if((rc = _cmFfSeek(p,SEEK_SET, op->offs )) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFfReadMtx(p,&mtx,dp,dpn)) != kOkFfRC ) + goto errLabel; + + int readByteCnt = mtx.m.rowCnt * mtx.m.colCnt * wordByteCnt; + + if( readByteCnt > dpn ) + { + rc = _cmFfError( p, kBufTooSmallFfRC, 0, "The matrix load buffer is too small."); + goto errLabel; + } + + dpn -= readByteCnt; + dp += readByteCnt; + } + + op = op->linkPtr; + } + + if( outCntPtr != NULL ) + *outCntPtr = bufEleCnt - (dpn/wordByteCnt); + + errLabel: + return rc; + +} + +cmFfRC_t cmFrameFileMtxLoadUChar( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned char* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kUCharFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadChar( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, char* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kCharFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadUShort( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned short* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kUShortFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadShort( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, short* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kShortFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadULong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned long* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kULongFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadLong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, long* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kLongFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadUInt( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned int* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kUIntFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadInt( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, int* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kIntFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadULLong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned long long* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kULLongFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadLLong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, long long* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kLLongFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadFloat( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, float* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kFloatFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadDouble( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, double* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kDoubleFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadStringZ( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, char* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kStringZFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + +cmFfRC_t cmFrameFileMtxLoadBlob( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, void* buf, unsigned eleCnt, unsigned* outCntPtr ) +{ return _cmFrameFileMtxLoad( h, streamId, mtxTypeId, unitsId, kUCharFmtId, frmIdx, frmCnt, buf, eleCnt, outCntPtr ); } + + +void _cmFrameFilePrint( cmRpt_t* rpt, const char* fmt, ... ) +{ + assert(rpt != NULL); + + va_list vl; + va_start(vl,fmt); + cmRptVPrintf(rpt,fmt,vl); + va_end(vl); +} + + +cmFfRC_t _cmFrameFileMtxReport( const cmFf_t* p, unsigned mtxIdx, cmRpt_t* rpt ) +{ + assert( mtxIdx < p->frame.f.mtxCnt ); + + const _cmFfMtx_t* mp = p->frame.mtxArray + mtxIdx; + + _cmFrameFilePrint(rpt," type:0x%x units:0x%x fmtId:0x%x rowCnt:%i colCnt:%i byteCnt:%i\n",mp->m.type,mp->m.unitsId,mp->m.fmtId,mp->m.rowCnt,mp->m.colCnt,mp->byteCnt); + + return kOkFfRC; +} + +cmFfRC_t _cmFrameFileFrameReport( const cmFf_t* p, cmRpt_t* rpt ) +{ + unsigned i; + _cmFrameFilePrint(rpt,"type:0x%x mtxCnt:%i flags:0x%x streamId:%i byteCnt:%i\n", p->frame.f.type,p->frame.f.mtxCnt,p->frame.f.flags,p->frame.f.streamId,p->frame.byteCnt); + + for(i=0; iframe.f.mtxCnt; ++i) + _cmFrameFileMtxReport(p,i,rpt); + + return kOkFfRC; +} + +void _cmFrameFileContentsReport(const cmFf_t* p, const _cmFfToC_t* tocPtr, cmRpt_t* rpt ) +{ + const _cmFfToC_t* cp = tocPtr; + unsigned i; + + for(i=0; cp != NULL; ++i ) + { + bool frmFl = cp->mtxType==kInvalidMId && cp->mtxUnitsId==kInvalidUId && cp->mtxFmtId==kInvalidFmtId; + + _cmFrameFilePrint( rpt, "%i streamId:%i ",i, cp->streamId ); + + if( !frmFl ) + _cmFrameFilePrint( rpt, "type:%i units:%i fmt:%i ",cp->mtxType, cp->mtxUnitsId, cp->mtxFmtId ); + + _cmFrameFilePrint( rpt, "cnt:%i\n", cp->offsList.cnt ); + + cp = cp->linkPtr; + } +} + +cmFfRC_t cmFrameFileReport( cmFrameFileH_t h, bool summOnlyFl, cmRpt_t* rpt ) +{ + cmFfRC_t rc = kOkFfRC; + cmFf_t* p = _cmFfHandleToPtr(h); + + if(p->writeFl ) + return _cmFfError( p, kInvalidFileModeFfRC, 0, "Cannot report on files opened in write mode."); + + _cmFrameFilePrint(rpt,"frames:%i srate:%f\n",p->f.frameCnt,p->f.srate); + + _cmFrameFilePrint(rpt,"Frame Contents:\n"); + _cmFrameFileContentsReport(p, p->frmToC, rpt ); + + _cmFrameFilePrint(rpt,"Matrix Contents:\n"); + _cmFrameFileContentsReport(p, p->mtxToC, rpt ); + + if( summOnlyFl ) + { + unsigned i; + + if((rc = cmFrameFileRewind(h)) != kOkFfRC ) + goto errLabel; + + for(i=0; cmFrameFileFrameLoadNext(h,kInvalidFrameTId,kInvalidStreamId,NULL) == kOkFfRC; ++i) + { + _cmFrameFilePrint(rpt," %i ",i); + if((rc = _cmFrameFileFrameReport(p,rpt)) != kOkFfRC ) + break; + } + + assert(i==p->f.frameCnt); + } + errLabel: + + return rc; +} + +cmFfRC_t cmFrameFileNameReport( const char* fn, bool summOnlyFl, cmCtx_t* ctx ) +{ + cmFrameFileH_t h; + cmFfRC_t rc0,rc1; + + if((rc0 = cmFrameFileOpen( &h, fn, ctx, NULL)) != kOkFfRC ) + return rc0; + + rc0 = cmFrameFileReport(h,summOnlyFl,&ctx->rpt); + + + rc1 = cmFrameFileClose(&h); + return rc0 != kOkFfRC ? rc0 : rc1; +} + +/* +void cmFrameFileVTestPrintFunc( void* userDataPtr, const char* fmt, va_list vl ) +{ + vfprintf(stdout,fmt,vl); +} +*/ + + +cmFfRC_t _cmFrameFileTestMtx( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, unsigned mtxCnt, unsigned i, bool modFl ) +{ + cmFfRC_t rc = kOkFfRC; + const cmFfMtx_t* mtxDescPtr = NULL; + unsigned j,k; + + for(j=0; jcolCnt*mtxDescPtr->rowCnt; k++) + { + printf("%2.0f ",ddp[k]); + + // if pass 1 modify the data + if( modFl ) + ++ddp[k]; + } + + } + else + { + for(k=0; kcolCnt*mtxDescPtr->rowCnt; k++) + { + printf("%2li ",dp[k]); + + // if pass 1 modify the data + if( modFl ) + ++dp[k]; + } + } + + printf("\n"); + } + + errLabel: + return rc; +} + +cmFfRC_t cmFrameFileTest2( const char* fn, cmCtx_t* ctx ) +{ + cmFfRC_t rc; + cmFrameFileH_t ffH; + const cmFfFile_t* descPtr; + + if((rc = cmFrameFileOpen(&ffH, fn, ctx, &descPtr )) != kOkFfRC ) + goto errLabel; + + + rc = cmFrameFileClose(&ffH); + errLabel: + return rc; +} + +cmFfRC_t cmFrameFileTest( const char* fn, cmCtx_t* ctx ) +{ + //return cmFrameFileTest2("/media/disk/home/kevin/temp/temp0.ft"); + + cmFfRC_t rc = kOkFfRC; + double srate = 44100; + unsigned frameType = 0x32333435; + unsigned streamId = 1; + unsigned sampleIdx = 0; + unsigned unitsId = kNoUnitsUId; + cmFrameFileH_t h; + const cmFfFile_t* fileDescPtr = NULL; + const cmFfFrame_t* frmDescPtr = NULL; + unsigned mtxType = 0x40414243; + unsigned i,j,k,m; + + if((rc = cmFrameFileCreate( &h, fn, srate, ctx )) != kOkFfRC ) + return rc; + + // create 3 frames + for(i=0; i<3; ++i,sampleIdx++) + { + if((rc = cmFrameFileFrameCreate(h, frameType, streamId, sampleIdx, 0 )) == kOkFfRC ) + { + long data[] = { 0,1,2,3,4,5,6,7,8,9,10 }; + double ddata[] = { 10,11,12,13,14,15,16,17,18,19,20 }; + unsigned n = sizeof(data)/sizeof(data[0]); + + for(j=0; jrpt )) != kOkFfRC ) + goto errLabel; + + // rewind the file + if((rc = cmFrameFileRewind(h)) != kOkFfRC ) + goto errLabel; + + // for each frame + for(i=0; cmFrameFileFrameLoadNext(h,kInvalidFrameTId,kInvalidStreamId,&frmDescPtr)==kOkFfRC; ++i) + { + if( frmDescPtr->type == kTocFrameTId ) + break; + + // print each matrix in this frame + if((rc = _cmFrameFileTestMtx( h, mtxType, unitsId, frmDescPtr->mtxCnt, i, m==0 )) != kOkFfRC ) + goto errLabel; + + // if pass 1 write the modified data back to disk + if( m == 0 ) + if((rc = cmFrameFileFrameUpdate(h)) != kOkFfRC ) + goto errLabel; + + + } // end frame loop + + + } // end pass loop + + + if((rc = cmFrameFileClose(&h)) != kOkFfRC ) + goto errLabel; + + + // + // test cmFrameFileSeek() by seeking to frame 'fi' + // + + printf("seek test\n"); + unsigned fi = 2; + + if((rc = cmFrameFileOpen( &h, fn,ctx,&fileDescPtr )) != kOkFfRC ) + goto errLabel; + + if((rc = cmFrameFileSeek( h, streamId, fi )) != kOkFfRC ) + goto errLabel; + + if((rc = cmFrameFileFrameLoadNext(h,kInvalidFrameTId,kInvalidStreamId,&frmDescPtr)) != kOkFfRC ) + goto errLabel; + + if((rc = _cmFrameFileTestMtx( h, mtxType, unitsId, frmDescPtr->mtxCnt, fi, false )) != kOkFfRC ) + goto errLabel; + + // + // test cmFrameFileMtxSize + // + unsigned frmCnt = 0; + unsigned rowCnt = 0; + unsigned colCnt = 0; + unsigned eleCnt = 0; + if((rc = cmFrameFileMtxSize(h, streamId, mtxType, unitsId, kLongFmtId, &frmCnt, &rowCnt, &colCnt, &eleCnt )) != kOkFfRC ) + goto errLabel; + + printf("frames:%i rows:%i cols:%i eles:%i\n",frmCnt,rowCnt,colCnt,eleCnt); + + if(1) + { + unsigned actualEleCnt; + unsigned eleCnt = frmCnt*rowCnt*colCnt; + long buf[ eleCnt ]; + if((rc = cmFrameFileMtxLoadLong(h, streamId, mtxType, unitsId, 0, -1, buf, eleCnt, &actualEleCnt )) == kOkFfRC ) + { + cmVOI_Print(&ctx->rpt,rowCnt,frmCnt,(int*)buf); + } + } + + + errLabel: + if( rc != kOkFfRC ) + printf("ERROR:%i\n",rc); + + if((rc = cmFrameFileClose(&h)) != kOkFfRC ) + return rc; + + return rc; + +} diff --git a/cmFrameFile.h b/cmFrameFile.h new file mode 100644 index 0000000..4cbb00d --- /dev/null +++ b/cmFrameFile.h @@ -0,0 +1,360 @@ +#ifndef cmFrameFile_h +#define cmFrameFile_h + +/* + file -> cmFfFile_t frame* + frame -> cmFfFrame_t mtx* + mtx -> cmFfMtx_t data* + */ + +#ifdef __cplusplus +extern "C" { +#endif + + + enum + { + kInvalidFrameTId = 0, + kInvalidStreamId = 0, + + kTocFrameTId = -3, + kTocStreamId = -3, + + kFileFfTId = 'FrmF', + + }; + + enum + { + kOkFfRC = 0, // 0 + kFileOpenFailFfRC, // 1 + kFileReadFailFfRC, // 2 + kFileWriteFailFfRC, // 3 + kFileSeekFailFfRC, // 4 + kFileCloseFailFfRC, // 5 + kEofFfRC, // 6 + kInvalidHandleFfRC, // 7 + kMemAllocErrFfRC, // 8 + kNotFrameFileFfRC, // 9 + kUnknownErrFfRC, // 10 + kNoMatchingFrameFfRC, // 11 + kInvalidFileModeFfRC, // 12 + kJsonFailFfRC, // 13 + kInvalidFrameIdxFfRC, // 14 + kDuplicateMtxIdFfRC, // 15 + kFileTellFailFfRC, // 16 + kLHeapFailFfRC, // 17 + kTocFrameRdFailFfRC, // 18 + kTocRecdNotFoundFfRC, // 19 + kBufTooSmallFfRC // 20 + }; + + // row data formats + enum + { + kInvalidFmtId, // 0 + kUCharFmtId, // 1 + kCharFmtId, // 2 + kUShortFmtId, // 3 + kShortFmtId, // 4 + kULongFmtId, // 5 + kLongFmtId, // 6 + kUIntFmtId, // 7 + kIntFmtId, // 8 + kLLongFmtId, // 9 + kULLongFmtId, // 10 + kOff_tFmtId, // 11 + kFloatFmtId, // 12 + kDoubleFmtId, // 13 + kStringZFmtId, // 14 + kBlobFmtId, // 15 + kJsonFmtId // 16 + }; + + enum + { + kInvalidUId , // 0 + kNoUnitsUId, // 1 + kHzUId, // 2 + kRadsUId, // 3 -pi to pi + kAmplUId, // 4 -1.0 to 1.0 + kPowUId, // 5 amp^2/2 + k10DbUId, // 6 10*log10(v) + k20DbUId, // 7 20*log10(v) + kCntUId, // 8 count of elements + kIdxUId, // 9 element index + kBfccUId, // 10 + kMfccUId, // 11 + kCepsUId, // 12 + + kD1UFl = 0x80000000 // this is a 1st difference + }; + + enum + { + kTocMId = -2, + + + kInvalidMId = 0,// 0 + kAudioMId, // 1 + kMagMId, // 2 + kPhsMId, // 3 + kFrqMId, // 4 measured bin frequecies in Hz + kTrkMId, // 5 + kRmsMId, // 6 + kBinCntMId, // 7 count of frequency domain bins in frame + kWndSmpCntMId, // 8 actual count of samples in FFT window (this is no (binCnt-1)*2) + kAudSmpCntMId, // 9 count of audio samples in frame + kBfccMId, // 10 vector of BFCC's + kBfccBandCntMId,// 11 count of coeff's BFCC vector in this frame + kMfccMId, // 12 vector of MFCC's + kCepsMId, // 13 vector of cepstral coefficients + kConstqMId, // 14 vector of constant + kDataMId // 15 blob of misc data + + }; + + typedef cmHandle_t cmFrameFileH_t; + typedef unsigned cmFfRC_t; + + // mtx desc record + typedef struct + { + unsigned type; + unsigned fmtId; + unsigned unitsId; + unsigned rowCnt; + unsigned colCnt; + } cmFfMtx_t; + + // frame desc record + typedef struct + { + unsigned type; + unsigned mtxCnt; + unsigned flags; // used internally to track time format (seconds or samples) + unsigned streamId; + + union + { + unsigned sampleIdx; + double seconds; + } time; + + } cmFfFrame_t; + + + // file desc record + typedef struct + { + cmChar_t* filenameStr; + unsigned version; + unsigned frameCnt; // count of frames in all streams + double srate; // sample rate for all frames + } cmFfFile_t; + + extern cmFrameFileH_t cmFrameFileNullHandle; + + cmFfRC_t cmFrameFileCreate( cmFrameFileH_t* hPtr, const char* fn, double srate, cmCtx_t* ctx ); + + // The fileDescPtrPtr is optional. Set to NULL to ignore. + cmFfRC_t cmFrameFileOpen( cmFrameFileH_t* hPtr, const char* fn, cmCtx_t* ctx, const cmFfFile_t** fileDescPtrPtr ); + cmFfRC_t cmFrameFileClose( cmFrameFileH_t* hPtr ); + bool cmFrameFileIsValid( cmFrameFileH_t h ); + const cmFfFile_t* cmFrameFileDesc( cmFrameFileH_t h ); + + // Return the count of frames in the requested stream. + unsigned cmFrameFileFrameCount( cmFrameFileH_t h, unsigned streamId ); + + // Create a frame in a file created via cmFrameFileCreate(). + // Set 'sampleIdx' to -1 if seconds is being used instead of samples. + cmFfRC_t cmFrameFileFrameCreate( cmFrameFileH_t h, unsigned frameType, unsigned streamId, unsigned sampleIdx, double secs ); + + // (W) Complete and write a frame created via an earilier call + // to cmFrameFileFrameCreate() + cmFfRC_t cmFrameFileFrameClose( cmFrameFileH_t h ); + + // (W) Fill a frame with matrix data. The frame must have been created + // via an earlier call to cmFrameFileCreate(). + cmFfRC_t cmFrameFileWriteMtxUChar( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned char* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxChar( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const char* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxUShort( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned short* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxShort( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const short* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxULong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned long* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxLong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const long* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxUInt( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxInt( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const int* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxULLong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const unsigned long long* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxLLong( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const long long* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxOff_t( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const off_t* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxFloat( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const float* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxDouble( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const double* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxBlob( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const void* p, unsigned rn, unsigned cn ); + cmFfRC_t cmFrameFileWriteMtxStringZ( cmFrameFileH_t h, unsigned mtxType, unsigned unitsId, const char* p ); + cmFfRC_t cmFrameFileWriteMtxJson( cmFrameFileH_t h, unsigned mtxType, cmJsonH_t jsH, const cmJsonNode_t* nodePtr ); + + // (R/W) Rewind to the first frame. Must call cmFrameFileFrameNext() + // following successful execution of this function to maintain correct + // alignment. + cmFfRC_t cmFrameFileRewind( cmFrameFileH_t h ); + + // (R/W) Seek to the frame at index 'frameIdx'. Must call cmFrameFileFrameNext() + // following successful execution of this function to maintain correct + // alignment. + cmFfRC_t cmFrameFileSeek( cmFrameFileH_t h, unsigned streamId, unsigned frameIdx ); + + // (R/W) Seek to the next frame in stream frameStreamId with type frameTypeId. + // Set frameTypeId to kInvalidFrameTId to return any frame type. + // Set frameStreamId to kInvalidStreamId to return any stream type. + // This function must be followed by a call to cmFrameFileFrameLoad() + // or cmFrameFileSkip(). + cmFfRC_t cmFrameFileFrameNext( cmFrameFileH_t h, unsigned frameTypeId, unsigned streamId ); + + // (R/W) Load the matrix data associated with the current frame. + // This function can only be called after a successful call to + // cmFrameFileFrameNext(). + // The frameDescPtrPtr is optional. Set to NULL to ignore. + cmFfRC_t cmFrameFileFrameLoad( cmFrameFileH_t h, const cmFfFrame_t** frameDescPtrPtr ); + + // (R/W) Skip over the matrix data associated with the current frame. + // This is an alternative to cmFrameFileLoad(). + cmFfRC_t cmFrameFileFrameSkip( cmFrameFileH_t h ); + + // (R/W) Combines cmFrameFileFrameNext() and cmFrameFileFrameLoad() + // into a single call. + cmFfRC_t cmFrameFileFrameLoadNext( cmFrameFileH_t h, unsigned frameTypeId, unsigned streamId, const cmFfFrame_t** frameDescPtrPtr ); + + // (R/W) Write the current frame back to disk. + cmFfRC_t cmFrameFileFrameUpdate( cmFrameFileH_t h ); + + // Return the current frame description record. + const cmFfFrame_t* cmFrameFileFrameDesc( cmFrameFileH_t h ); + + // Currently loaded frame index. + unsigned cmFrameFileFrameLoadedIndex( cmFrameFileH_t h ); + + // Return the index of the frame with type 'mtxTypeId', units 'unitsId', and format 'fmtId' + // or cmInvalidIdx if the specified frame is not found. + // Set mtxTypeId to kInvalidMId to return any mtx type. + // Set unitsId to kInvalidUId to return a mtx with any units. + // Set fmtId to kInvalidFmtId to return a mtx with any fmt. + unsigned cmFrameFileMtxIndex( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, unsigned fmtId ); + + // Return a matrix description record. + const cmFfMtx_t* cmFrameFileMtxDesc( cmFrameFileH_t h, unsigned mtxIdx ); + + // Access matrix data. + //Set descPtr to NULL if the matrix desc is not needed. + unsigned char* cmFrameFileMtxIndexUChar( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + char* cmFrameFileMtxIndexChar( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + unsigned short* cmFrameFileMtxIndexUShort( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + short* cmFrameFileMtxIndexShort( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + unsigned long* cmFrameFileMtxIndexULong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + long* cmFrameFileMtxIndexLong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + unsigned* cmFrameFileMtxIndexUInt( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + int* cmFrameFileMtxIndexInt( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + unsigned long long* cmFrameFileMtxIndexULLong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + long long* cmFrameFileMtxIndexLLong( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + off_t* cmFrameFileMtxIndexOff_t( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + float* cmFrameFileMtxIndexFloat( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + double* cmFrameFileMtxIndexDouble( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + char* cmFrameFileMtxIndexStringZ( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + void* cmFrameFileMtxIndexBlob( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + // (The caller is responsible for invoking cmJsonFinalize() to finalize the returned json handle.) + cmJsonH_t cmFrameFileMtxIndexJson( cmFrameFileH_t h, unsigned mtxIdx, const cmFfMtx_t** descPtrPtr ); + + // Return a pointer to the data, and optionally the descPtr, for a matrix with the given + // type,units and format in the current frame. + // The following functions are implmented in terms of + // cmFrameFileMtxIndexXXX() and cmFrameFileMtxIndex(). + unsigned char* cmFrameFileMtxUChar( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + char* cmFrameFileMtxChar( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + unsigned short* cmFrameFileMtxUShort( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + short* cmFrameFileMtxShort( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + unsigned long* cmFrameFileMtxULong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + long* cmFrameFileMtxLong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + unsigned* cmFrameFileMtxUInt( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + int* cmFrameFileMtxInt( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + unsigned long long* cmFrameFileMtxULLong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + long long* cmFrameFileMtxLLong( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + off_t* cmFrameFileMtxOff_t( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + float* cmFrameFileMtxFloat( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + double* cmFrameFileMtxDouble( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + char* cmFrameFileMtxStringZ( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + void* cmFrameFileMtxBlob( cmFrameFileH_t h, unsigned mtxTypeId, unsigned unitsId, const cmFfMtx_t** descPtrPtr ); + // (The caller is responsible for invoking cmJsonFinalize() to finalize the returned json handle.) + cmJsonH_t cmFrameFileMtxJson( cmFrameFileH_t h, unsigned mtxTypeId, const cmFfMtx_t** descPtrPtr ); + + + // Return the max row cnt, max col count, and total element count + // for all matrices which match the given stream/mtx/units/fmt + // combination. Note that if the returned ele count is less than + // maxRowCnt * maxColCnt * cmFrameFileFrameCount(streamId) then + // some matched matrices contain fewer than maxRowCnt/maxColCnt + // rows/columns. + cmFfRC_t cmFrameFileMtxSize( cmFrameFileH_t h, unsigned streamId, unsigned mtxType, unsigned unitsId, unsigned fmtId, unsigned* frameCntPtr, unsigned* rowCntPtr, unsigned* colCntPtr, unsigned* eleCntPtr ); + + // Load a buffer with all of the data which matches a given + // stream/mtx/unit/fmt combination in the given range of frames. + // 'frmIdx' specifies the frame index relative to the given stream id as opposed to + // and absolute frame index. + // Set frmCnt to -1 to include all frames following 'frmIdx'. + // *outCntPtr is set to the actual number of elements copied into the buffer + // The data is packed into the return buffer by copying columwise from the source. + // matrices to the buf[]. If all of the matrices are not of a fixed known size + // it may therefore be difficult to distinguish where one frames data ends and + // the next begins. + cmFfRC_t cmFrameFileMtxLoadUChar( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned char* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadChar( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, char* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadUShort( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned short* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadShort( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, short* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadULong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned long* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadLong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, long* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadUInt( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned int* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadInt( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, int* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadULLong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, unsigned long long* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadLLong( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, long long* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadOff_t( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, off_t* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadFloat( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, float* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadDouble( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, double* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadStringZ( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, char* buf, unsigned eleCnt, unsigned* outCntPtr ); + cmFfRC_t cmFrameFileMtxLoadBlob( cmFrameFileH_t h, unsigned streamId, unsigned mtxTypeId, unsigned unitsId, unsigned frmIdx, unsigned frmCnt, void* buf, unsigned eleCnt, unsigned* outCntPtr ); + + cmFfRC_t cmFrameFileReport( cmFrameFileH_t h, bool summOnlyFl, cmRpt_t* rpt ); + cmFfRC_t cmFrameFileNameReport( const char* fn, bool summOnlyFl, cmCtx_t* ctx ); + + cmFfRC_t cmFrameFileTest( const char* fn, cmCtx_t* ctx ); + +#if CM_FLOAT_SMP == 1 +#define cmFrameFileMtxLoadSample cmFrameFileMtxLoadFloat +#define cmFrameFileWriteMtxSample cmFrameFileWriteMtxFloat +#define cmFrameFileMtxIndexSample cmFrameFileMtxIndexFloat +#define cmFrameFileMtxSample cmFrameFileMtxFloat +#define kSampleFmtId kFloatFmtId +#else +#define cmFrameFileMtxLoadSample cmFrameFileMtxLoadDouble +#define cmFrameFileWriteMtxSample cmFrameFileWriteMtxDouble +#define cmFrameFileMtxIndexSample cmFrameFileMtxIndexDouble +#define cmFrameFileMtxSample cmFrameFileMtxDouble +#define kSampleFmtId kDoubleFmtId +#endif + +#if CM_FLOAT_REAL == 1 +#define cmFrameFileMtxLoadReal cmFrameFileMtxLoadFloat +#define cmFrameFileWriteMtxReal cmFrameFileWriteMtxFloat +#define cmFrameFileMtxIndexReal cmFrameFileMtxIndexFloat +#define cmFrameFileMtxReal cmFrameFileMtxFloat +#define kRealFmtId kFloatFmtId +#else +#define cmFrameFileMtxLoadReal cmFrameFileMtxLoadDouble +#define cmFrameFileWriteMtxReal cmFrameFileWriteMtxDouble +#define cmFrameFileMtxIndexReal cmFrameFileMtxIndexDouble +#define cmFrameFileMtxReal cmFrameFileMtxDouble +#define kRealFmtId kDoubleFmtId +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmGlobal.c b/cmGlobal.c new file mode 100644 index 0000000..139597f --- /dev/null +++ b/cmGlobal.c @@ -0,0 +1,2 @@ + + diff --git a/cmGlobal.h b/cmGlobal.h new file mode 100644 index 0000000..ac04953 --- /dev/null +++ b/cmGlobal.h @@ -0,0 +1,154 @@ +//{ +//( +// cmGlobal.h contains the global macros, header files, and +// typedefs availale to all other cm modules. +// +// All operating system dependencies should be resolved in this file via +// testing for OS_LINUX, OS_OSX, or OS_W32. +//) + +#ifndef cmGlobal_h +#define cmGlobal_h + +//( +#include "config.h" // created by 'configure' +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +#define CM_FLOAT_SMP 1 //< make cmSample_t = float in cmFloatTypes.h +#define CM_FLOAT_REAL 0 //< make cmReal_t = double in cmFloatTypes.h + +#ifdef NDEBUG +#define cmDEBUG_FL 0 //< Define cmDEBUG_FL as 0 when building in release mode. See \ref debug_mode. +#else +#define cmDEBUG_FL 1 //< Define cmDEBUG_FL as 1 when building in debug mode. See \ref debug_mode. +#endif + + + // Perform byte swapping on 16 bit values. +#define cmSwap16(x) \ + (((((unsigned short)(x)) & 0x00ff) << 8) | ((((unsigned short)(x)) & 0xff00) >> 8)) + +#ifdef OS_LINUX +#include // gcc specific +#include + + // Perform byte swapping on 32 bit values on systems were is available. +#define cmSwap32(x) (bswap_32(x)) + + // Perform byte swapping on 64 bit values on systems were is available. +#define cmSwap64(x) (bswap_64(x)) + + +#endif + + +#ifdef OS_OSX +#include + + // Perform byte swapping on 32 bit values on systems were is not available. +#define cmSwap32(x) \ + ((((unsigned)((x) & 0x000000FF)) << 24) | \ + (((unsigned)((x) & 0x0000FF00)) << 8) | \ + (((unsigned)((x) & 0x00FF0000)) >> 8) | \ + (((unsigned)((x) & 0xFF000000)) >> 24)) + + // Perform byte swapping on 64 bit values on systems were is not available. +#define cmSwap64(x) \ + (((((unsigned long long)(x))<<56) & 0xFF00000000000000ULL) | \ + ((((unsigned long long)(x))<<40) & 0x00FF000000000000ULL) | \ + ((((unsigned long long)(x))<<24) & 0x0000FF0000000000ULL) | \ + ((((unsigned long long)(x))<< 8) & 0x000000FF00000000ULL) | \ + ((((unsigned long long)(x))>> 8) & 0x00000000FF000000ULL) | \ + ((((unsigned long long)(x))>>24) & 0x0000000000FF0000ULL) | \ + ((((unsigned long long)(x))>>40) & 0x000000000000FF00ULL) | \ + ((((unsigned long long)(x))>>56) & 0x00000000000000FFULL)) + +#endif + +#define cmAllFlags(f,m) (((f) & (m)) == (m)) //< Test if all of a group 'm' of binary flags in 'f' are set. +#define cmIsFlag(f,m) (((f) & (m)) ? true : false) //< Test if any one of a the bits in 'm' is also set in 'f'. +#define cmIsNotFlag(f,m) (cmIsFlag(f,m)==false) //< Test if none of the bits in 'm' are set in 'f'. +#define cmSetFlag(f,m) ((f) | (m)) //< Return 'f' with the bits in 'm' set. +#define cmClrFlag(f,m) ((f) & (~(m))) //< Return 'f' with the bits in 'm' cleared. +#define cmTogFlag(f,m) ((f)^(m)) //< Return 'f' with the bits in 'm' toggled. +#define cmEnaFlag(f,m,b) (b) ? cmSetFlag(f,m) : cmClrFlag(f,m) //< \brief Set or clear bits in 'f' based on bits in 'm' and the state of 'b'. + //< + //< If 'b' == 0 then return 'f' with the bits in 'm' cleared. + //< otherwise return 'f' with the bits in 'm' set. + + +#define cmMin(v0,v1) ((v0)<(v1) ? (v0) : (v1)) //< Return the minimum arg. +#define cmMax(v0,v1) ((v0)>(v1) ? (v0) : (v1)) //< Return the maximum arg. + + +#define cmStringNullGuard(p) ((p)==NULL?"":(p)) //< If 'p'==NULL return the static string "" otherwise return 'p'. + + // Default return code indicating successful function completion. +#define cmOkRC (0) + + // Default directory separator character for unix based systems. +#define cmPathSeparatorChar ("/") + +#define cmInvalidIdx (0xffffffff) //< cm wide value indicating a invalid or NULL index. +#define cmInvalidId (cmInvalidIdx) //< cm wide value indicating an invalid or NULL numeric id. +#define cmInvalidCnt (cmInvalidIdx) //< cm wide value indicating a invalid or NULL count of items. + +#define cmSTATIC_NULL_HANDLE {NULL} //< Default NULL value for cmHandle_t + + // Generic data type for implementing opaque object handles. + /* + typedef struct cmHandle_str + { + void* h; + } cmHandle_t; + */ + +#define cmHandle_t struct { void* h; } + +#define cmHandlesAreEqual( a, b ) ((a).h == (b).h) //< Test if two cmHandle_t values are equivalent. +#define cmHandlesAreNotEqual( a, b ) (!cmHandlesAreEqual(a,b)) //< Test if two cmHandle_t value are not equivalent. + + // Data type commonly used as a function return value. Functions returning cmRC_t values + // return cmOkRC (0) to indicate successful completion or some other value to indicate + // some kind of exceptional conidtion. In general the condition indicates an unexpected condition + // such as resource exhaution, or a missing file. + typedef unsigned cmRC_t; + + // A data type which indicates a system dependent error. This is generally an abstraction for an 'errno' + // like code. + typedef int cmSysErrCode_t; // same as errno + + + // cmChar_t is a data type used to indicate that a char is being used to hold human readable + // text. Eventually this type will be used to locate and handle unicode based strings. + typedef char cmChar_t; + + + typedef unsigned int cmUInt32_t; //< This typedef is used to indicate that the type must be an unsigned 32 bit integer. + typedef unsigned short cmUInt16_t; //< This typedef is used to indicate that hte type must be an unsigned 16 bit integer. + +#ifdef __cplusplus +} +#endif + +//) +//} + +#endif diff --git a/cmGnuPlot.c b/cmGnuPlot.c new file mode 100644 index 0000000..acba5d5 --- /dev/null +++ b/cmGnuPlot.c @@ -0,0 +1,1121 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmRpt.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmGnuPlot.h" + +#include +#include +#include +#include // read/write/close + +enum +{ + kX_PdFl = 0x01, // the data set contains explicit x coordinates + kY_PdFl = 0x02, // the data set contains explicit y coordinates + kZ_PdFl = 0x04, // the data set contains explicit z coordinates + kImpulse_PdFl = 0x08, // plot using the gnuplot impulse style + + kInvalidLineId = -2, + kSolidLineId = -1, + kDashedLineId = 0 + +}; + +typedef struct +{ + unsigned flags; // see kXXX_PdFl + char* legendStrH; // plot legend label for this data set + char* colorStrH; // string containing a gnuplot color spec + double* xyzMtxPtr; // data to be plotted contained in a column major mtx with 1,2, or 3 columns + unsigned rn; // + unsigned cn; // + int lineId; // gnuplot line style id + int pointId; // gnuplot point style id +} cmPlotData; + + +//---------------------------------------------------------------------------------------------------------- +enum +{ + kTitleIdx = 0, + kXLabelIdx, + kYLabelIdx, + kZLabelIdx, + kPlotStrCnt +}; + +enum +{ + kXMinRangeIdx, + kXMaxRangeIdx, + kYMinRangeIdx, + kYMaxRangeIdx, + kZMinRangeIdx, + kZMaxRangeIdx, + kRangeCnt +}; + +// There is one cmPlot per sub-plot. These records are held in cmPlotPage.plotPtrArray +typedef struct +{ + cmChar_t* strArray[ kPlotStrCnt ]; // an array of various labels and titles + double range[ kRangeCnt ]; // a set of range limits for each dimension (used to automatically fill in coord values when they are not explicitely given) + unsigned rowIdx; // the plot page row index of this sub-plot + unsigned colIdx; // the plot page col index of this sub-plot + cmPlotData** dataPtrArray; // pointer to data sets containing data for this sub-plot + unsigned dataCnt; +} cmPlot; + +//---------------------------------------------------------------------------------------------------------- + +// The plotter contains a single cmPlotPage (pointed to by _cmpp). +typedef struct +{ + unsigned rowCnt; // number of rows of sub-plots + unsigned colCnt; // number of columns of sub-plots + cmChar_t* titleStrH; // page title + int pipeFd[2]; // communication pipe with gnuplot process + int pid; // process id of the gnuplot process + cmPlot** plotPtrArray; // vector of sub-plots + unsigned plotCnt; // + unsigned curPlotIdx; // the sub-plot currently receiving plotting commands and data +} cmPlotPage; + + + +cmPlotPage _cmPlotPage = { 0,0,NULL,{-1,-1},-1,NULL,0,cmInvalidIdx}; +cmPlotPage* _cmpp = NULL; + + +void _cmPrintf( int fd, const char* fmt, ... ) +{ + const int bufCnt = 255; + char buf[bufCnt+1]; + buf[bufCnt]='\0'; + va_list vl; + va_start(vl,fmt); + int n = vsnprintf(buf,bufCnt,fmt,vl); + assert( n < 255 ); + + write(fd,buf,n); + + va_end(vl); + +} + +// unexpected event signal handler +void cmPlotSignalHandler( int sig ) +{ + switch( sig ) + { + case SIGCHLD: + if( _cmpp != NULL ) + _cmpp->pid = -1; + break; + case SIGBUS: + case SIGSEGV: + case SIGTERM: + cmPlotFinalize(); + break; + } +} + +unsigned _cmPlotError( cmPlotPage* p, unsigned rc, bool sysErrFl, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + + fprintf(stderr,"cmPlot Error:"); + + vfprintf(stderr,fmt,vl); + + + if( sysErrFl ) + fprintf(stderr," System Msg:%s\n",strerror(errno)); + + va_end(vl); + return rc; +} + + +//---------------------------------------------------------------------------------------------------------- +void _cmPlotDataInit( cmPlotData* p ) +{ + p->flags = 0; + p->legendStrH = NULL; + p->colorStrH = NULL; + p->xyzMtxPtr = NULL; + p->rn = 0; + p->cn = 0; + p->lineId = kInvalidLineId; + p->pointId = kInvalidPlotPtId; +} + +void _cmPlotDataCons( cmPlotData* p, unsigned flags, const char* legendStr, const char* colorStr, double* mtxPtr, unsigned rn, unsigned cn, unsigned styleFlags ) +{ + p->flags = flags + (cmIsFlag(styleFlags,kImpulsePlotFl) ? kImpulse_PdFl : 0); + p->legendStrH = cmMemAllocStr(legendStr); + p->colorStrH = cmMemAllocStr(colorStr); + p->xyzMtxPtr = mtxPtr; + p->rn = rn; + p->cn = cn; + p->lineId = ((styleFlags & kPlotLineMask) >> kPlotLineShift) - 2; // convert from the interface style flags to gnuplot id's + p->pointId = styleFlags & kPlotPtMask; +} + +void _cmPlotDataFree( cmPlotData* p ) +{ + cmMemPtrFree(&p->legendStrH); + cmMemPtrFree(&p->colorStrH); + //cmDM_Free(&p->xyzMtxPtr); + cmMemPtrFree(&p->xyzMtxPtr); +} + +/* +bool _cmPlotDataFreeFE( unsigned i, cmPlotData* p, void *vp ) +{ + _cmPlotDataFree(p); + return true; +} +*/ + + +//---------------------------------------------------------------------------------------------------------- +void _cmPlotInit( cmPlot* p ) +{ + unsigned i; + for(i=0; istrArray[i] = NULL; + + for(i=0; irange[i] = 0; + + p->rowIdx = cmInvalidIdx; + p->colIdx = cmInvalidIdx; + p->dataPtrArray = NULL; + p->dataCnt = 0; +} + +void _cmPlotCons( cmPlot* p, unsigned ri, unsigned ci ) +{ + p->rowIdx = ri; + p->colIdx = ci; + assert( p->dataPtrArray == NULL ); + //p->dataPtrArray = cmPlotDataVect_AllocEmpty(); +} + +void _cmPlotInsertData( cmPlot* p, cmPlotData* rp) +{ + cmPlotData* nrp = cmMemAlloc(cmPlotData,1); + *nrp = *rp; + + p->dataPtrArray = cmMemResizeP( cmPlotData*, p->dataPtrArray, p->dataCnt + 1 ); + p->dataPtrArray[ p->dataCnt ] = nrp; + ++p->dataCnt; +} + +void _cmPlotClearData( cmPlot* p ) +{ + unsigned i; + + // release the strings + for(i=0; istrArray[i]); + + // release the plot data + for(i=0; idataCnt; ++i) + { + _cmPlotDataFree( p->dataPtrArray[i] ); + cmMemPtrFree( &p->dataPtrArray[i] ); + } + + // set the data cnt to 0 + p->dataCnt = 0; + +} + + +void _cmPlotFree( cmPlot* p ) +{ + _cmPlotClearData(p); + cmMemPtrFree(&p->dataPtrArray); +} + + +//---------------------------------------------------------------------------------------------------------- +void _cmPlotPageInit( cmPlotPage* rp ) +{ + rp->rowCnt = 0; + rp->colCnt = 0; + rp->titleStrH = NULL; + rp->pipeFd[0] = -1; + rp->pipeFd[1] = -1; + rp->pid = -1; + rp->plotPtrArray = NULL; + rp->curPlotIdx = cmInvalidIdx; +} + +cmRC_t _cmPlotPageCons( cmPlotPage* rp, int pid, int inFd, int outFd, const char* terminalStr ) +{ + cmRC_t rc = kOkPlRC; + + rp->pid = pid; + rp->pipeFd[0] = inFd; + rp->pipeFd[1] = outFd; + rp->plotPtrArray = NULL; //cmPlotVect_AllocEmpty(); + + if(terminalStr != NULL ) + _cmPrintf( outFd, "set terminal %s\n",terminalStr ); + + return rc; +} + +void _cmPlotPageSetup( cmPlotPage* rp, const char* title, unsigned rowCnt, unsigned colCnt ) +{ + unsigned i,ri, ci; + + rp->titleStrH = cmMemResizeStr(rp->titleStrH,title); // acStringAssign(&rp->titleStrH,title); + rp->rowCnt = rowCnt; + rp->colCnt = colCnt; + rp->curPlotIdx = rowCnt*colCnt > 0 ? 0 : cmInvalidIdx; + + + // free any resources held by each plot and empty the plot array + for(i=0; iplotCnt; ++i) + { + _cmPlotFree(rp->plotPtrArray[i]); + cmMemPtrFree( &rp->plotPtrArray[i] ); + } + rp->plotCnt = 0; + + + // insert rowCnt*colCnt blank plot records + + + // allocate the plotVect[] + rp->plotPtrArray = cmMemResizeZ( cmPlot*, rp->plotPtrArray, rowCnt*colCnt ); + rp->plotCnt = rowCnt * colCnt; + + + // initialize each cmPlot record + for(ri=0,i=0; riplotPtrArray[i] = cmMemAllocZ(cmPlot,1); + + _cmPlotInit( rp->plotPtrArray[i]); + _cmPlotCons( rp->plotPtrArray[i], ri, ci); + } +} + +cmRC_t _cmPlotPageFree( cmPlotPage* rp ) +{ + unsigned i; + cmRC_t rc = kOkPlRC; + + cmMemPtrFree(&rp->titleStrH); + //acStringDelete( &rp->titleStrH ); + + // if the plot process was successfully started - stop it here + if( rp->pid > 0) + { + int rc; + kill(rp->pid,SIGKILL); + wait(&rc); + rp->pid = -1; + } + + + + // close the pipe input to the plot process + for(i=0; i<2; ++i) + { + if( rp->pipeFd[i] != -1 ) + if( close(rp->pipeFd[i]) == -1 ) + rc = _cmPlotError(rp,kPipeCloseFailedPlRC,true,"Pipe %i close() failed.",i); + + rp->pipeFd[i] = -1; + } + + // deallocate the plot array + if( rp->plotPtrArray != NULL ) + { + for(i=0; iplotCnt; ++i) + { + _cmPlotFree(rp->plotPtrArray[i] ); + cmMemPtrFree(&rp->plotPtrArray[i]); + } + + cmMemPtrFree(&rp->plotPtrArray); + rp->plotCnt = 0; + } + + return rc; +} +//---------------------------------------------------------------------------------------------------------- + + +cmRC_t cmPlotInitialize( const char* terminalStr ) +{ + cmRC_t rc = kOkPlRC; + + // if this is the first call to this function + if( _cmpp == NULL ) + { + struct sigaction sa; + + _cmpp = &_cmPlotPage; + + _cmPlotPageInit(_cmpp); + + sa.sa_handler = cmPlotSignalHandler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGBUS, &sa, NULL) == -1) + { + rc = _cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGBUS) failed."); + goto errLabel; + } + + sa.sa_handler = cmPlotSignalHandler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGSEGV, &sa, NULL) == -1) + { + rc = _cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGSEGV) failed."); + goto errLabel; + } + + sa.sa_handler = cmPlotSignalHandler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGTERM, &sa, NULL) == -1) + { + rc = _cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGTERM) failed."); + goto errLabel; + } + + sa.sa_handler = cmPlotSignalHandler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGCHLD, &sa, NULL) == -1) + { + rc = _cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGCHLD) failed."); + goto errLabel; + } + + } + else // if this is the second or greater call to this function + { + if((rc = cmPlotFinalize()) != kOkPlRC ) + return rc; + } + + int pipeFD[2]; + + // create the pipe + if( pipe(pipeFD) == -1 ) + { + rc = _cmPlotError(_cmpp,kPipeFailedPlRC,true,"pipe() failed."); + goto errLabel; + } + + int pid; + + // create the child proces + switch( pid = fork() ) + { + case -1: + printf("Error\n"); + rc = _cmPlotError(_cmpp,kForkFailedPlRC,true,"fork() failed."); + goto errLabel; + break; + + case 0: + + close(fileno(stdin)); // close stdin + dup(pipeFD[0]); // replace stdin with the pipe input + + + execlp("gnuplot","gnuplot",NULL); // start gnuplot + + // under normal conditions execlp() should never return + rc = _cmPlotError(_cmpp,kExecFailedPlRC,true,"exec() failed."); + + goto errLabel; + break; + + default: + // normal return for parent process + rc = _cmPlotPageCons(_cmpp,pid,pipeFD[0],pipeFD[1], terminalStr ); + break; + } + + return rc; + + errLabel: + + cmPlotFinalize(); + return rc; +} + +cmRC_t cmPlotFinalize() +{ + cmRC_t rc = kOkPlRC, rc0; + struct sigaction sa; + + if( _cmpp == NULL ) + return kOkPlRC; + + // install some unexpected event signal handlers to clean up if the application + // process crashes prior to calling cmPlotFinalize(). This will prevent unconnected gnuplot + // processes from being left in the process list. + + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + + if( sigaction( SIGCHLD,&sa,NULL) == -1 ) + rc =_cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGCHLD) restore failed."); + + if( sigaction( SIGTERM,&sa,NULL) == -1 ) + rc =_cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGTERM) restore failed."); + + if( sigaction( SIGSEGV,&sa,NULL) == -1 ) + rc =_cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGSEGV) restore failed."); + + if( sigaction( SIGBUS,&sa,NULL) == -1 ) + rc =_cmPlotError(_cmpp,kSignalFailedPlRC,true,"sigaction(SIGBUS) restore failed."); + + + // restore the child termination signal handler + signal(SIGCHLD,SIG_DFL); + + rc0 = _cmPlotPageFree(_cmpp); + + return rc==kOkPlRC ? rc0 : rc; +} + + + +cmRC_t cmPlotInitialize2( const char* terminalStr, const char* title, unsigned rowCnt, unsigned colCnt ) +{ + cmRC_t rc; + if((rc = cmPlotInitialize(terminalStr)) != cmOkRC ) + return rc; + return cmPlotSetup(title,rowCnt,colCnt); +} + + +cmRC_t cmPlotSetup( const char* title, unsigned rowCnt, unsigned colCnt ) +{ + _cmPlotPageSetup( _cmpp,title,rowCnt,colCnt); + return kOkPlRC; +} + + +// called to locate a cmPlot given plot row/col indexes +unsigned _cmRowColToPlotIndex( cmPlotPage* p, unsigned ri, unsigned ci ) +{ + unsigned i; + + for(i=0; i<_cmpp->plotCnt; ++i) + if( _cmpp->plotPtrArray[i]->rowIdx==ri && _cmpp->plotPtrArray[i]->colIdx==ci ) + return i; + + + + _cmPlotError(_cmpp,kPlotNotFoundPlRC,false,"No plot exists at row:%i and col:%i\n",ri,ci); + + return cmInvalidIdx; +} + +cmRC_t cmPlotSelectSubPlot( unsigned ri, unsigned ci ) +{ + unsigned i; + + if((i= _cmRowColToPlotIndex( _cmpp, ri, ci ) ) != cmInvalidIdx ) + _cmpp->curPlotIdx = i; + + return kOkPlRC; +} + +cmPlot* _cmPlotGetCurPlotPtr() +{ + if( _cmpp->curPlotIdx == cmInvalidIdx ) + { + _cmPlotError(_cmpp,kNoCurPlotPlRC,false,"No plot exists for the current page."); + assert(0); + return NULL; + }; + + assert( _cmpp->curPlotIdx < _cmpp->plotCnt ); + cmPlot* p = _cmpp->plotPtrArray[_cmpp->curPlotIdx]; + + assert( p != NULL ); + + return p; +} + +cmRC_t cmPlotSetLabels( const char* titleStr, const char* xLabelStr, const char* yLabelStr, const char* zLabelStr ) +{ + cmPlot* p = _cmPlotGetCurPlotPtr(); + + p->strArray[kTitleIdx] = cmMemAllocStr( titleStr ); // acStringAssign( &p->strArray[ kTitleIdx ], titleStr ); + p->strArray[kXLabelIdx] = cmMemAllocStr( xLabelStr ); // acStringAssign( &p->strArray[ kXLabelIdx ], xLabelStr ); + p->strArray[kYLabelIdx] = cmMemAllocStr( yLabelStr ); // acStringAssign( &p->strArray[ kYLabelIdx ], yLabelStr ); + p->strArray[kZLabelIdx] = cmMemAllocStr( zLabelStr ); //acStringAssign( &p->strArray[ kZLabelIdx ], zLabelStr ); + + return kOkPlRC; +} + + +cmRC_t cmPlotSetRange( double xMin, double xMax, double yMin, double yMax, double zMin, double zMax ) +{ + cmPlot* p = _cmPlotGetCurPlotPtr(); + + if( xMin != 0 || xMax != 0 ) + { + p->range[ kXMinRangeIdx ] = xMin; + p->range[ kXMaxRangeIdx ] = xMax; + } + + if( yMin != 0 || yMax != 0 ) + { + p->range[ kYMinRangeIdx ] = yMin; + p->range[ kYMaxRangeIdx ] = yMax; + } + + if( zMin != 0 || zMax != 0 ) + { + p->range[ kZMinRangeIdx ] = zMin; + p->range[ kZMaxRangeIdx ] = zMax; + } + + return kOkPlRC; + +} + +void cmPlot2DLine( const float* x, const float* y, unsigned n, const char* color, int lineType, int lineWidth, int pointType ) +{ + //char cmd[] = "reset\nset size 1,1\nset origin 0,0\nset multiplot layout 1,1\nplot '-' binary array=16 format='%float' origin=(16,0) dx=10 using 1 with lines\n"; + char cmd[] = "reset\nset size 1,1\nset origin 0,0\nset multiplot layout 1,1\nplot '-' binary record=16 format='%float' using 1:2 with lines\n"; + char cmd2[] = "unset multiplot\n"; + + unsigned i; + + int rc = write(_cmpp->pipeFd[1],cmd,strlen(cmd)); + + //int rc = fprintf(fp,"%s",cmd); + + for( i=0; ipipeFd[1],x+i,sizeof(float)); + write(_cmpp->pipeFd[1],y+i,sizeof(float)); + } + + + + write(_cmpp->pipeFd[1],cmd2,strlen(cmd2)); + + printf("%i %s",rc,cmd); +} + +void cmPlot2DLine1( const float* x, const float* y, unsigned n, const char* color, int lineType, int lineWidth, int pointType ) +{ + //char cmd[] = "reset\nset size 1,1\nset origin 0,0\nset multiplot layout 1,1\nplot '/home/kevin/src/gnuplot/data1.bin' binary record=16x2\n"; + char cmd[] = "reset\nset size 1,1\nset origin 0,0\nset multiplot layout 1,1\nplot '-' binary record=16 format='%float' using 1:2 with lines\n"; + char cmd2[] = "unset multiplot\n"; + + unsigned i; + + int rc = write(_cmpp->pipeFd[1],cmd,strlen(cmd)); + + //int rc = fprintf(fp,"%s",cmd); + + for( i=0; ipipeFd[1],x+i,sizeof(float)); + write(_cmpp->pipeFd[1],y+i,sizeof(float)); + } + + + + write(_cmpp->pipeFd[1],cmd2,strlen(cmd2)); + + printf("%i %s",rc,cmd); +} + + + + +/// Clear the current current subplot +cmRC_t cmPlotClear() +{ + cmPlot* p = _cmPlotGetCurPlotPtr(); + _cmPlotClearData(p); + return kOkPlRC; +} + + +void _cmPlotInsertDataRecd( cmPlot* p, unsigned flags, const char* legendStr, const char* colorStr, unsigned styleFlags, double* mtxPtr, unsigned rn, unsigned cn ) +{ + cmPlotData r; + + _cmPlotDataInit(&r); + _cmPlotDataCons(&r,flags,legendStr,colorStr,mtxPtr,rn,cn,styleFlags); + _cmPlotInsertData(p,&r); + +} + +cmRC_t cmPlotLineF( const char* legendStr, const float* x, const float* y, const float* z, unsigned n, const char* colorStr, unsigned styleFlags ) +{ + cmPlot* p = _cmPlotGetCurPlotPtr(); + unsigned flags = 0; + unsigned rn = 0; + unsigned ri = 0; + const float* array[3] = {x,y,z}; + unsigned i; + + // determine which data vectors were provided + for(i=0; i<3; ++i) + if( array[i] != NULL ) + { + ++rn; + switch(i) + { + case 0: flags = cmSetFlag(flags,kX_PdFl); break; + case 1: flags = cmSetFlag(flags,kY_PdFl); break; + case 2: flags = cmSetFlag(flags,kZ_PdFl); break; + default: + {assert(0);} + } + } + + // create the matrix to hold the data + double* mtxPtr = cmMemAlloc(double,rn*n); + + // copy the data into the matrix + for(i=0; i<3; ++i) + if( array[i] != NULL ) + { + unsigned ci; + for(ci=0; cirowCnt,_cmpp->colCnt); + + for(ri=0; ri<_cmpp->rowCnt; ++ri) + for(ci=0; ci<_cmpp->colCnt; ++ci) + { + + // get the plot at ri,ci + unsigned plotIdx = _cmRowColToPlotIndex(_cmpp,ri,ci); + assert( plotIdx != cmInvalidIdx ); + cmPlot* p = _cmpp->plotPtrArray[plotIdx]; + + // get the count of data sets assigned to this plot + unsigned dataCnt = p->dataCnt; + + + if( dataCnt > 0 ) + { + bool printPlotKeywordFl = false; + + // note which ranges are valid + bool isXRangeFl = p->range[ kXMinRangeIdx ] != 0 || p->range[ kXMaxRangeIdx != 0 ]; + bool isYRangeFl = p->range[ kYMinRangeIdx ] != 0 || p->range[ kYMaxRangeIdx != 0 ]; + bool isZRangeFl = p->range[ kZMinRangeIdx ] != 0 || p->range[ kZMaxRangeIdx != 0 ]; + + // set the plot title + if( p->strArray[kTitleIdx] != NULL ) + _cmPrintf(fd,"set title '%s'\n",(p->strArray[kTitleIdx])); + else + { + // if this is a one plot page use the page title as the plot title + if( _cmpp->titleStrH != NULL && _cmpp->rowCnt==1 && _cmpp->colCnt == 1 ) + _cmPrintf(fd,"set title '%s'\n", _cmpp->titleStrH ); + } + + // set the plot x label + if( p->strArray[kXLabelIdx] != NULL ) + _cmPrintf(fd,"set xlabel '%s'\n",(p->strArray[kXLabelIdx])); + + // set the plot y label + if( p->strArray[kYLabelIdx] != NULL ) + _cmPrintf(fd,"set ylabel '%s'\n",(p->strArray[kYLabelIdx])); + + + for(di=0; didataPtrArray[di]; + + unsigned eleCnt = dp->cn; //acDM_Cols(dp->xyzMtxPtr); + unsigned dimCnt = dp->rn; //acDM_Rows(dp->xyzMtxPtr); + + if( eleCnt == 0 || dimCnt==0 ) + continue; + + + // must defer printing the 'plot' command until we are sure there is a non-empty matrix to print + if( printPlotKeywordFl == false ) + { + _cmPrintf(fd,"plot "); + printPlotKeywordFl = true; + } + + bool useXRangeFl = (cmIsFlag(dp->flags,kX_PdFl)==false) && isXRangeFl; + bool useYRangeFl = (cmIsFlag(dp->flags,kY_PdFl)==false) && isYRangeFl; + bool useZRangeFl = (cmIsFlag(dp->flags,kZ_PdFl)==false) && isZRangeFl; + bool useRangeFl = useXRangeFl | useYRangeFl | useZRangeFl; + + _cmPrintf(fd," '-' binary %s=%i format='%%double' ", (dimCnt==1) && useRangeFl ? "array":"record",eleCnt); + + if( (dimCnt == 1) && (useXRangeFl || useYRangeFl) ) + { + _cmPrintf(fd," origin=(%f,%f) ", useXRangeFl ? p->range[ kXMinRangeIdx ] : 0, useYRangeFl ? p->range[ kYMinRangeIdx ] : 0); + + if( useXRangeFl ) + _cmPrintf(fd, " dx=%f ", (p->range[ kXMaxRangeIdx ] - p->range[ kXMinRangeIdx ]) / eleCnt ); + + if( useYRangeFl ) + _cmPrintf(fd, " dy=%f ", (p->range[ kYMaxRangeIdx ] - p->range[ kYMinRangeIdx ]) / eleCnt ); + + _cmPrintf(fd," using %i ", 1 ); + } + else + _cmPrintf(fd," using %i:%i ", dimCnt==1 ? 0 : 1, dimCnt==1 ? 1 : 2 ); + + if( dp->legendStrH != NULL ) + _cmPrintf(fd," title '%s' ", dp->legendStrH); + else + _cmPrintf(fd, " notitle "); + + + + bool impulseFl = cmIsFlag(dp->flags,kImpulse_PdFl ); + + if( impulseFl || (dp->lineId != kInvalidLineId) || (dp->pointId != kInvalidPlotPtId) ) + { + + _cmPrintf(fd," with "); + + if( impulseFl ) + _cmPrintf(fd,"impulses"); + else + { + if( dp->lineId != kInvalidLineId ) + _cmPrintf(fd,"lines"); + + if( dp->pointId != kInvalidPlotPtId ) + _cmPrintf(fd,"points pt %i ", dp->pointId); + } + + if( dp->colorStrH != NULL ) + _cmPrintf(fd," lt rgb '%s' ", dp->colorStrH ); + + } + + if( di+1 < dataCnt ) + _cmPrintf(fd,","); + + + + + else + { + _cmPrintf(fd,"\n"); + + // for each data set contained in this plot + for(di=0; didataPtrArray[di]; + //acDM* mp = dp->xyzMtxPtr; + unsigned eleCnt = dp->cn; //acDM_Cols(mp); + const double* ddp = dp->xyzMtxPtr; //acDM_ConstPtr(mp); + + // if we are printing the output to the console instead of sending it too gnuplot + if( fd == fileno(stdout) ) + { + if( printDataFl ) + { + unsigned i = 0; + for(i=0; irn*eleCnt*sizeof(double)); + } + + } + + } + + } // if dataCnt > 0 + } // for ci + } // for ri + + /* + _cmPrintf(fd,"\n"); + + for(ri=0; ri<_cmpp->rowCnt; ++ri) + for(ci=0; ci<_cmpp->colCnt; ++ci) + { + // get the plot at ri,ci + cmPlot* p = cmPlotVect_Ptr(_cmpp->plotPtrArray,_acRowColToPlotIndex(_cmpp,ri,ci)); + + // get the count of data sets assigned to this plot + unsigned dataCnt = cmPlotDataVect_Count(p->dataPtrArray); + + // for each data set contained in this plot + for(di=0; didataPtrArray,di); + acDM* mp = dp->xyzMtxPtr; + unsigned eleCnt = acDM_Cols(mp); + const double* ddp = acDM_ConstPtr(mp); + + // if we are printing the output to the console instead of sending it too gnuplot + if( fd == fileno(stdout) ) + { + if( printDataFl ) + { + unsigned i = 0; + for(i=0; ipipeFd[1],false); + +} + +cmRC_t cmPlotPrint( bool printDataFl ) +{ + return _cmPlotDraw(fileno(stdout),printDataFl); +} + +cmRC_t cmPlotDrawAndPrint( bool printDataFl ) +{ + cmPlotDraw(); + return _cmPlotDraw(fileno(stdout),printDataFl); +} + + +// ncl - min column value +// nch - max column value +// nrl - min row value +// nrh - max row value + +int fwrite_matrix(FILE* fout, float**m, int nrl, int nrh, int ncl, int nch, float* row_title, float* column_title) + +{ + int j; + float length; + int col_length; + int status; + + + // calc the number of columns + length = (float)(col_length = nch-ncl+1); + + printf("cols:%i %f\n",col_length,length); + + // write the number of columns + if((status = fwrite((char*)&length,sizeof(float),1,fout))!=1) + { + fprintf(stderr,"fwrite 1 returned %d\n",status); + return(0); + } + + // write the column titles + fwrite((char*)column_title,sizeof(float),col_length,fout); + + + // write the row_title followed by the data on each line + for(j=nrl; j<=nrh; j++) + { + + fwrite( (char*)&row_title[j], sizeof(float), 1, fout); + fwrite( (char*)(m[j]+ncl), sizeof(float), col_length,fout); + printf("%i %li\n",j,ftell(fout)); + } + + + return(1); +} + + + +// Generate a 'matrix-binary' data file for use with: plot "data0.bin" binary +void cmPlotWriteBinaryMatrix() +{ + const char fn[] = "/home/kevin/src/gnuplot/data0.bin"; + + unsigned rn = 2; // row cnt + unsigned cn = 16; // col cnt + float srate = 16; + float d[rn*cn]; + //float* m[rn]; + float c[cn]; + //float r[rn]; + unsigned i; + + for(i=0; ivext (inside msDnObj->parent->wxt) + cmGrVPt_t msVPt; // cur ms pt in same coords as msDnObj->vext (inside msDnObj->parent->wxt) + cmGrVSz_t msDnVOffs; + + bool selValidFl; + cmGrVPt_t sel0Pt; + cmGrVPt_t sel1Pt; + + cmGrVPt_t localPt; + cmGrVPt_t globalPt; + + cmGrVExt_t vext; // view virtual extents + cmGrPExt_t pext; // view physical extents + cmGrObj_t* objs; // object tree + cmGrObj_t* rootObj; // current root object + unsigned char* img; // temporary image inversion buffer + cmGrCbFunc_t cbFunc; // + void* cbArg; // + cmGrSync_t* syncs; // + cmGrColorMap_t* maps; // color maps +} cmGr_t; + +cmGrH_t cmGrNullHandle = cmSTATIC_NULL_HANDLE; +cmGrObjH_t cmGrObjNullHandle = cmSTATIC_NULL_HANDLE; + + +cmGrKeyMap_t _cmGrKeyMap[] = +{ + { 0, 0, 0 }, + { 1, 0, 0 }, + { 2, 0, 0 }, + { 3, 0, 0 }, + { 4, 0, 0 }, + { 5, 0, kHomeGrId}, + { 6, 0, kPageUpGrId}, + { 7, 0, kEndGrId}, + { 8, 8, kBackSpaceGrId }, + { 9, 9, kTabGrId }, + + { 10, 0, kPageDownGrId}, + { 11, 0, kLeftGrId}, + { 12, 0, kUpGrId}, + { 13, 13, kEnterGrId }, + { 14, 0, kRightGrId}, + { 15, 0, kDownGrId}, + { 16, 0, kInsertGrId}, + { 17, 0, kPrintGrId}, + { 18, 0, kScrollLockGrId}, + { 19, 0, kPauseGrId}, + { 20, 0, kMenuGrId}, + + + { 21, 0, kLShiftGrId}, + { 22, 0, kRShiftGrId}, + { 23, 0, kLCtrlGrId}, + { 24, 0, kRCtrlGrId}, + { 25, 0, kLAltGrId}, + { 26, 0, kRAltGrId}, + { 27, 27, kEscapeGrId }, + { 28, 0, kLSuperGrId}, + { 29, 0, kRSuperGrId}, + { 30, 0, kNumLockGrId}, + { 31, 0, kCapsLockGrId}, + + { 32, 32, kSpaceGrId }, + { 33, 33, kExclMarkGrId }, + { 34, 34, kDQuoteGrId }, + { 35, 35, kPoundGrId }, + { 36, 36, kDollarGrId }, + { 37, 37, kPercentGrId }, + { 38, 38, kAmpersandGrId }, + { 39, 39, kApostropheGrId }, + + { 40, 40, kLParenGrId }, + { 41, 41, kRParenGrId }, + { 42, 42, kAsteriskGrId }, + { 43, 43, kPlusGrId }, + { 44, 44, kCommaGrId }, + { 45, 45, kHyphenGrId }, + { 46, 46, kPeriodGrId }, + { 47, 47, kForwardSlashGrId }, + { 48, 48, k0GrId }, + { 49, 49, k1GrId }, + + { 50, 50, k2GrId }, + { 51, 51, k3GrId }, + { 52, 52, k4GrId }, + { 53, 53, k5GrId }, + { 54, 54, k6GrId }, + { 55, 55, k7GrId }, + { 56, 56, k8GrId }, + { 57, 57, k9GrId }, + { 58, 58, kColonGrId }, + { 59, 59, kSemiColonGrId }, + + { 60, 60, kLesserGrId }, + { 61, 61, kEqualGrId }, + { 62, 62, kGreaterGrId }, + { 63, 63, kQMarkGrId }, + { 64, 64, kAtGrId }, + { 65, 65, kA_GrId }, + { 66, 66, kB_GrId }, + { 67, 67, kC_GrId }, + { 68, 68, kD_GrId }, + { 69, 69, kE_GrId }, + + { 70, 70, kF_GrId }, + { 71, 71, kG_GrId }, + { 72, 72, kH_GrId }, + { 73, 73, kI_GrId }, + { 74, 74, kJ_GrId }, + { 75, 75, kK_GrId }, + { 76, 76, kL_GrId }, + { 77, 77, kM_GrId }, + { 78, 78, kN_GrId }, + { 79, 79, kO_GrId }, + + { 80, 80, kP_GrId }, + { 81, 81, kQ_GrId }, + { 82, 82, kR_GrId }, + { 83, 83, kS_GrId }, + { 84, 84, kT_GrId }, + { 85, 85, kU_GrId }, + { 86, 86, kV_GrId }, + { 87, 87, kW_GrId }, + { 88, 88, kX_GrId }, + { 89, 89, kY_GrId }, + + { 90, 90, kZ_GrId }, + { 91, 91, kLBracketGrId }, + { 92, 92, kBackSlashGrId }, + { 93, 93, kRBracketGrId }, + { 94, 94, kCaretGrId }, + { 95, 95, kUnderScoreGrId }, + { 96, 96, kAccentGrId }, + { 97, 97, ka_GrId }, + { 98, 98, kb_GrId }, + { 99, 99, kc_GrId }, + + { 100, 100, kd_GrId }, + { 101, 101, ke_GrId }, + { 102, 102, kf_GrId }, + { 103, 103, kg_GrId }, + { 104, 104, kh_GrId }, + { 105, 105, ki_GrId }, + { 106, 106, kj_GrId }, + { 107, 107, kk_GrId }, + { 108, 108, kl_GrId }, + { 109, 109, km_GrId }, + + { 110, 110, kn_GrId }, + { 111, 111, ko_GrId }, + { 112, 112, kp_GrId }, + { 113, 113, kq_GrId }, + { 114, 114, kr_GrId }, + { 115, 115, ks_GrId }, + { 116, 116, kt_GrId }, + { 117, 117, ku_GrId }, + { 118, 118, kv_GrId }, + { 119, 119, kw_GrId }, + + { 120, 120, kx_GrId }, + { 121, 121, ky_GrId }, + { 122, 122, kz_GrId }, + { 123, 123, kLBraceGrId }, + { 124, 124, kPipeGrId }, + { 125, 125, kRBraceGrId }, + { 126, 126, kTildeGrId }, + { 127, 127, kDeleteGrId }, + + { 128, 42, kNP_MultGrId }, + { 129, 43, kNP_PlusGrId }, + { 130, 45, kNP_MinusGrId }, + { 131, 46, kNP_DecPtGrId}, + { 132, 47, kNP_DivGrId}, + { 133, 48, kNP_0GrId}, + { 134, 49, kNP_1GrId}, + { 135, 50, kNP_2GrId}, + { 136, 51, kNP_3GrId}, + { 137, 52, kNP_4GrId}, + { 138, 53, kNP_5GrId}, + { 139, 54, kNP_6GrId}, + { 140, 55, kNP_7GrId}, + { 141, 56, kNP_8GrId}, + { 142, 57, kNP_9GrId}, + { 143, 61, kNP_EqualGrId}, + { 144, 13, kNP_EnterGrId}, + { 145, 0, kFunc_1GrId}, + { 146, 0, kFunc_2GrId}, + { 147, 0, kFunc_3GrId}, + { 148, 0, kFunc_4GrId}, + { 149, 0, kFunc_5GrId}, + { 150, 0, kFunc_6GrId}, + { 151, 0, kFunc_7GrId}, + { 152, 0, kFunc_8GrId}, + { 153, 0, kFunc_9GrId}, + { 154, 0, kFunc_10GrId}, + { 155, 0, kFunc_11GrId}, + { 156, 0, kFunc_12GrId}, + { 157, 0, kBrightUpGrId}, + { 158, 0, kBrightDnGrId}, + { 159, 0, kAudio_PrevGrId}, + { 160, 0, kAudio_PlayGrId}, + { 161, 0, kAudio_NextGrId}, + { 162, 0, kAudio_MuteGrId}, + { 163, 0, kAudio_DnGrId }, + { 164, 0, kAudio_UpGrId }, + { 165, 0, kEjectGrId }, + { cmInvalidIdx, 0, cmInvalidId} +}; + +void _cmGrKeyMapValidate() +{ + unsigned i; + for(i=0; _cmGrKeyMap[i].idx != cmInvalidIdx; ++i) + { + assert( _cmGrKeyMap[i].idx == i ); + } +} + +cmGrKeyMap_t* _cmGrFindKeyMap( unsigned keycode ) +{ + // printable ascii codes match their indexes + if( 32 <= keycode && keycode <= 126 ) + return _cmGrKeyMap + keycode; + + unsigned i; + for(i=0; i<32; ++i) + if( _cmGrKeyMap[i].keycode == keycode ) + return _cmGrKeyMap + i; + + for(i=127; _cmGrKeyMap[i].idx != cmInvalidIdx; ++i) + if( _cmGrKeyMap[i].keycode == keycode ) + return _cmGrKeyMap + i; + + assert(0); + return NULL; +} + + + +bool _cmGrSetViewExtents( cmGr_t* p, cmGrV_t minx, cmGrV_t miny, cmGrV_t maxx, cmGrV_t maxy ); +bool _cmGrSetViewExtentsE( cmGr_t* p, const cmGrVExt_t* e ) +{ return _cmGrSetViewExtents(p, cmGrVExtMinX(e), cmGrVExtMinY(e), cmGrVExtMaxX(e), cmGrVExtMaxY(e) ); } + +//==================================================================================================== +// Expand cmGrVExt_t e0 to hold e1. +// Return true if e0 is actually changed. +bool cmGrVExtExpandToContain(cmGrVExt_t* e0, const cmGrVExt_t* e1) +{ + bool fl = false; + + if( cmGrVExtIsNullOrEmpty(e0) ) + { + *e0 = *e1; + return true; + } + + assert( cmGrVExtIsNorm(e0) && cmGrVExtIsNorm(e1) ); + + // min-x + if( cmGrVExtMinX(e0) > cmGrVExtMinX(e1) ) + { + cmGrVExtSetMinX(e0, cmGrVExtMinX(e1)); + fl = true; + } + + // min-y + if( cmGrVExtMinY(e0) > cmGrVExtMinY(e1) ) + { + cmGrVExtSetMinY(e0, cmGrVExtMinY(e1)); + fl = true; + } + + // max-y + if( cmGrVExtMaxY(e0) < cmGrVExtMaxY(e1) ) + { + cmGrVExtSetMaxY(e0, cmGrVExtMaxY(e1)); + fl = true; + } + + // max-x + if( cmGrVExtMaxX(e0) < cmGrVExtMaxX(e1) ) + { + cmGrVExtSetMaxX(e0, cmGrVExtMaxX(e1)); + fl = true; + } + + + return fl; +} + +bool cmGrVExtContain( const cmGrVExt_t* e0, cmGrVExt_t* e1 ) +{ + bool fl = false; + + assert( cmGrVExtIsNorm(e0) && cmGrVExtIsNorm(e1) ); + // e1 must be able to fit inside e0 + assert( e1->sz.w <= e0->sz.w && e1->sz.h <= e0->sz.h ); + + // if left edge of e1 is to left of e0 + if( cmGrVExtMinX(e1) < cmGrVExtMinX(e0) ) + { + cmGrVExtSetMinX(e1,cmGrVExtMinX(e0)); + fl = true; + } + + // if right edge of e1 is to right of e0 + if( cmGrVExtMaxX(e1) > cmGrVExtMaxX(e0) ) + { + cmGrVExtSetMaxX(e1,cmGrVExtMaxX(e0)); + fl = true; + } + + // if the bottom edge of e1 is below the bottom edge of e0 + if( cmGrVExtMinY(e1) < cmGrVExtMinY(e0) ) + { + cmGrVExtSetMinY(e1,cmGrVExtMinY(e0)); + fl = true; + } + + // if top edge of e1 is above the top edge of e0 + if( cmGrVExtMaxY(e1) > cmGrVExtMaxY(e0) ) + { + cmGrVExtSetMaxY(e1,cmGrVExtMaxY(e0)); + fl = true; + } + + return fl; +} + +void cmGrPExtIntersect( cmGrPExt_t* r, const cmGrPExt_t* e0, const cmGrPExt_t* e1 ) +{ + if( cmGrPExtR(e0) < cmGrPExtL(e1) || cmGrPExtL(e0) > cmGrPExtR(e1) + || cmGrPExtB(e0) < cmGrPExtT(e1) || cmGrPExtT(e0) > cmGrPExtB(e1) ) + { + cmGrPExtSetEmpty(r); + return; + } + + cmGrPExtSetD(r, + cmMax( cmGrPExtL(e0), cmGrPExtL(e1) ), + cmMax( cmGrPExtT(e0), cmGrPExtT(e1) ), + cmMin( cmGrPExtR(e0), cmGrPExtR(e1) ), + cmMin( cmGrPExtB(e0), cmGrPExtB(e1) ) ); + +} + +void cmGrVExtIntersect( cmGrVExt_t* r, const cmGrVExt_t* e0, const cmGrVExt_t* e1 ) +{ + if( cmGrVExtMaxX(e0) < cmGrVExtMinX(e1) || cmGrVExtMinX(e0) > cmGrVExtMaxX(e1) + || cmGrVExtMaxY(e0) < cmGrVExtMinY(e1) || cmGrVExtMinY(e0) > cmGrVExtMaxY(e1) ) + { + cmGrVExtSetEmpty(r); + return; + } + + cmGrVExtSetD(r, + cmMax( cmGrVExtMinX(e0), cmGrVExtMinX(e1) ), + cmMax( cmGrVExtMinY(e0), cmGrVExtMinY(e1) ), + cmMin( cmGrVExtMaxX(e0), cmGrVExtMaxX(e1) ), + cmMin( cmGrVExtMaxY(e0), cmGrVExtMaxY(e1) ) ); +} + + +//==================================================================================================== +//==================================================================================================== +cmGr_t* _cmGrHandleToPtr( cmGrH_t h ) +{ + cmGr_t* p = (cmGr_t*)h.h; + assert( p != NULL ); + return p; +} + + +cmGrObj_t* _cmGrObjHandleToPtr( cmGrObjH_t h ) +{ + cmGrObj_t* p = (cmGrObj_t*)h.h; + assert( p != NULL ); + return p; +} + +//==================================================================================================== +unsigned cmGrColorMapCount( cmGrH_t grH ) +{ + cmGr_t* p = _cmGrHandleToPtr(grH); + cmGrColorMap_t* cmp = p->maps; + unsigned n = 0; + for(; cmp!=NULL; cmp=cmp->link) + ++n; + return n; +} + +cmGrColorMap_t* _cmGrColorMapFromIndex( cmGr_t* p, unsigned idx ) +{ + unsigned i = 0; + cmGrColorMap_t* cmp = p->maps; + for(; cmp!=NULL; ++i,cmp=cmp->link) + if( i == idx ) + break; + return cmp; +} + +cmGrColorMap_t* _cmGrColorMapFromId( cmGr_t* p, unsigned id ) +{ + cmGrColorMap_t* cmp = p->maps; + for(; cmp!=NULL; cmp=cmp->link) + if( cmp->id == id ) + break; + return cmp; +} + +unsigned cmGrColorMapId( cmGrH_t grH, unsigned mapIdx ) +{ + cmGr_t* p = _cmGrHandleToPtr(grH); + cmGrColorMap_t* cmp; + if((cmp = _cmGrColorMapFromIndex(p,mapIdx)) == NULL ) + return cmInvalidId; + return cmp->id; +} + +const cmChar_t* cmGrColorMapLabel( cmGrH_t grH, unsigned id ) +{ + cmGr_t* p = _cmGrHandleToPtr(grH); + cmGrColorMap_t* cmp; + if((cmp = _cmGrColorMapFromId(p,id)) == NULL ) + return NULL; + return cmp->label; +} + +unsigned _cmGrColorMapRegister( cmGr_t* p, cmChar_t* label, const cmGrColor_t* array, unsigned cnt ) +{ + // locate an available id + unsigned id = 0; + while( _cmGrColorMapFromId(p,id)!=NULL ) + ++id; + + cmGrColorMap_t* cmp = cmLhAllocZ(p->lhH,cmGrColorMap_t,1); + cmp->label = cmLhAllocStr(p->lhH,label); + cmp->id = id; + cmp->map = cmLhAllocZ(p->lhH,cmGrColor_t,cnt); + cmp->cnt = cnt; + cmp->link = p->maps; + p->maps = cmp; + + memcpy(cmp->map,array,sizeof(cmGrColor_t)*cnt); + + return id; +} + +unsigned cmGrColorMapRegister( cmGrH_t grH, cmChar_t* label, const cmGrColor_t* array, unsigned cnt ) +{ return _cmGrColorMapRegister( _cmGrHandleToPtr(grH),label, array, cnt ); } + +cmGrColor_t* cmGrColorMap( cmGrH_t grH, unsigned mapId ) +{ + cmGr_t* p = _cmGrHandleToPtr(grH); + cmGrColorMap_t* cmp; + if((cmp = _cmGrColorMapFromId(p,mapId)) == NULL ) + return NULL; + return cmp->map; +} + +unsigned cmGrColorMapEleCount( cmGrH_t grH, unsigned mapId ) +{ + cmGr_t* p = _cmGrHandleToPtr(grH); + cmGrColorMap_t* cmp; + if((cmp = _cmGrColorMapFromId(p,mapId)) == NULL ) + return cmInvalidId; + return cmp->cnt; +} + +void _cmGrRgbInitDefaultColorMap( cmGr_t* p ) +{ + unsigned map[] = + { + 0x000000, // black + 0x00008b, // dark blue + 0x0000ff, // blue + 0x008080, // teal + 0x00ffff, // cyan + 0x00ff7f, // spring green + 0x00ff00, // green + 0x7cfc00, // lawn green + 0xffff00, // yellow + 0xff7f7f, // pink + 0xff00ff, // magenta + 0xff007f, // + 0xff0000, // red + 0x7f0000, // + 0xffffff // white + }; + + unsigned n = sizeof(map)/sizeof(unsigned); + + _cmGrColorMapRegister(p,"default",map,n); +} + + + +//==================================================================================================== +// Object Callback Functions +//==================================================================================================== + +void _cmGrObjSetupFuncArgs( cmGrObjFuncArgs_t* a, cmGr_t* p, cmGrObj_t* op ) +{ + cmGrH_t h; + cmGrObjH_t oh; + + h.h = p; + oh.h = op; + + a->ctx = &p->ctx; + a->grH = h; + a->objH = oh; + a->msDnPPt = p->msDnPPt; + a->msDnVPt = p->msDnVPt; + a->msDnVOffs = p->msDnVOffs; + a->msVPt = p->msVPt; + + oh.h = p->msDnObj; + a->msDnObjH = oh; +} + +cmGrRC_t _cmGrObjCbCreate( cmGr_t* p, cmGrObj_t* op ) +{ + cmGrRC_t rc = kOkGrRC; + + if( op->f.createCbFunc != NULL ) + { + cmGrObjFuncArgs_t a; + _cmGrObjSetupFuncArgs(&a,p,op); + + a.cbArg = op->f.createCbArg; + if((rc = op->f.createCbFunc(&a)) != kOkGrRC ) + rc = cmErrMsg(&p->err,kAppErrGrRC,"An application object (id=%i) failed on 'create'",op->id); + } + + return rc; +} + +void _cmGrObjCbDestroy( cmGr_t* p, cmGrObj_t* op ) +{ + if( op->f.destroyCbFunc != NULL ) + { + cmGrObjFuncArgs_t a; + + _cmGrObjSetupFuncArgs(&a,p,op); + a.cbArg = op->f.destroyCbArg; + + op->f.destroyCbFunc(&a); + } +} + +void _cmGrObjCbRender( cmGr_t* p, cmGrDcH_t dcH, const cmGrObj_t* op ) +{ + if( op->f.renderCbFunc != NULL ) + { + cmGrObjFuncArgs_t a; + _cmGrObjSetupFuncArgs(&a,p,(cmGrObj_t*)op); + a.cbArg = op->f.renderCbArg; + + op->f.renderCbFunc(&a, dcH ); + } +} + +int _cmGrObjCbDistance( cmGr_t* p, const cmGrObj_t* op, int px, int py ) +{ + int d = INT_MAX; + + if( op->f.distanceCbFunc != NULL ) + { + cmGrObjFuncArgs_t a; + _cmGrObjSetupFuncArgs(&a,p,(cmGrObj_t*)op); + a.cbArg = op->f.distanceCbArg; + + d = op->f.distanceCbFunc(&a,px,py); + } + return d; +} + +bool _cmGrObjCbEvent( cmGr_t* p, cmGrObj_t* op, unsigned flags, unsigned key, int px, int py ) +{ + bool fl = false; + if( op->f.eventCbFunc != NULL ) + { + cmGrObjFuncArgs_t a; + _cmGrObjSetupFuncArgs(&a,p,op); + a.cbArg = op->f.eventCbArg; + + fl = op->f.eventCbFunc(&a,flags,key,px,py); + } + return fl; +} + +void _cmGrObjCbVExt( cmGr_t* p, const cmGrObj_t* op, cmGrVExt_t* vext ) +{ + if( op->f.vextCbFunc == NULL ) + cmGrVExtSetEmpty(vext); + else + { + cmGrObjFuncArgs_t a; + _cmGrObjSetupFuncArgs(&a,p,(cmGrObj_t*)op); + a.cbArg = op->f.vextCbArg; + + op->f.vextCbFunc(&a,vext); + } +} + +bool _cmGrObjCbIsInside( cmGr_t* p, const cmGrObj_t* op, int px, int py, cmGrV_t vx, cmGrV_t vy ) +{ + bool fl = false; + if( op->f.isInsideCbFunc != NULL ) + { + cmGrObjFuncArgs_t a; + _cmGrObjSetupFuncArgs(&a,p,(cmGrObj_t*)op); + a.cbArg = op->f.isInsideCbArg; + fl = op->f.isInsideCbFunc(&a,px,py,vx,vy); + } + return fl; +} + +//==================================================================================================== +// Object Private Functions +//==================================================================================================== +// Return true if pp is an ancestor (parent,grand-parent,great-grand-parent,...) of cp. +bool _cmGrObjIsAncestor( cmGrObj_t* pp, cmGrObj_t* cp ) +{ + cmGrObj_t* tp = cp->parent; + for(; tp != NULL; tp=tp->parent ) + if( tp == pp ) + break; + + return tp!=NULL; +} + +// Append 'op' as the right-most child of 'pp'. +void _cmGrObjAppendChild( cmGrObj_t* pp, cmGrObj_t* cp) +{ + cp->parent = pp; + + if( pp->children == NULL ) + { + pp->children = cp; + cp->lsib = NULL; + cp->rsib = NULL; + } + else + { + cmGrObj_t* op = pp->children; + while( op->rsib != NULL ) + op = op->rsib; + + op->rsib = cp; + cp->rsib = NULL; + cp->lsib = op; + } +} + +// Insert 'op' on the left of 'rp'. +void _cmGrObjInsertOnLeft( cmGrObj_t* op, cmGrObj_t* rp ) +{ + op->parent = rp->parent; + op->rsib = rp; + op->lsib = rp->lsib; + + if( rp->lsib == NULL ) + { + assert( rp->parent == rp->parent->children); + rp->parent->children = op; + } + else + { + rp->lsib->rsib = op; + } + + rp->lsib = op; +} + +// Insert 'op' on the right of 'lp'. +// 'pp' is the parent of 'lp'. 'pp' must be given explicitely to cover +// the case where lp is NULL - in which case the new parent for op +// cannot be determined from lp. +void _cmGrObjInsertOnRight( cmGrObj_t* op, cmGrObj_t* lp, cmGrObj_t* pp ) +{ + op->parent = pp; + + if( lp == NULL ) + { + assert( pp != NULL && pp->children==NULL ); + pp->children = op; + op->lsib = NULL; + op->rsib = NULL; + return; + } + + assert( lp->parent == pp ); + + op->lsib = lp; + op->rsib = lp->rsib; + + + if( lp->rsib != NULL ) + lp->rsib->lsib = op; + + lp->rsib = op; + +} + +// Unlink 'op' from the tree but leave it's children attached. +void _cmGrObjUnlink( cmGrObj_t * op ) +{ + if( op->parent != NULL && op->parent->children == op ) + op->parent->children = op->parent->children->rsib; + + if( op->rsib != NULL ) + op->rsib->lsib = op->lsib; + + if( op->lsib != NULL ) + op->lsib->rsib = op->rsib; + + op->parent = NULL; + op->rsib = NULL; + op->lsib = NULL; +} + +// Free 'op' and all of its children. +// 'op' must be unlinked before calling this function +cmGrRC_t _cmGrObjFree( cmGr_t* p, cmGrObj_t* op ) +{ + cmGrRC_t rc = kOkGrRC; + + // go to the deepest child + if( op->children != NULL ) + if((rc = _cmGrObjFree(p,op->children)) != kOkGrRC ) + return rc; + + // go right + if( op->rsib != NULL ) + if((rc = _cmGrObjFree(p,op->rsib)) != kOkGrRC ) + return rc; + + // inform the application that we are destroying this object + _cmGrObjCbDestroy(p,op); + + _cmGrObjUnlink(op); + + cmMemFree(op); + + return rc; +} + +cmGrRC_t _cmGrObjUnlinkAndFree( cmGr_t* p, cmGrObj_t* op ) +{ + cmGrRC_t rc = kOkGrRC; + cmGrObj_t* rsib = op->rsib; + cmGrObj_t* par = op->parent; + + _cmGrObjUnlink(op); + + // if the free fails ... + if((rc = _cmGrObjFree(p,op)) != kOkGrRC ) + { + // ... then restore the objects position + if( rsib == NULL ) + _cmGrObjInsertOnLeft(op,rsib); + else + _cmGrObjAppendChild(par,op); + } + + return rc; +} + +// Return kL,T,R,BGrFl indicating which directions of wext are in violation of op->wlimitExt. +// Return's 0 if no limits are in violation +unsigned _cmGrObjWorldLimitsTestViolation( const cmGrObj_t* op, const cmGrVExt_t* wext ) +{ + unsigned violFlags = 0; + + if( cmIsFlag( op->wlimitFlags, kLeftGrFl) ) + if( cmGrVExtMinX(wext) < cmGrVExtMinX(&op->wlimitExt) ) + violFlags = kLeftGrFl; + + if( cmIsFlag( op->wlimitFlags, kTopGrFl) ) + if( cmGrVExtMaxY(wext) < cmGrVExtMaxY(&op->wlimitExt) ) + violFlags = kTopGrFl; + + if( cmIsFlag( op->wlimitFlags, kRightGrFl) ) + if( cmGrVExtMaxX(wext) > cmGrVExtMaxX(&op->wlimitExt) ) + violFlags = kRightGrFl; + + if( cmIsFlag( op->wlimitFlags, kBottomGrFl) ) + if( cmGrVExtMinY(wext) > cmGrVExtMinY(&op->wlimitExt) ) + violFlags = kBottomGrFl; + + return violFlags; +} + +// If op has world extent limits then apply them to 'ext'. +// Extent directions in 'ext' which do not have limits in 'op' are unchanged. +// Extent directions in 'ext' which have limits are set to the limit. +void _cmGrObjApplyExtLimits( cmGrObj_t* op, cmGrVExt_t* ext ) +{ + if( cmIsFlag(op->wlimitFlags,kLeftGrFl) ) + cmGrVExtSetMinX(ext,cmGrVExtMinX(&op->wlimitExt)); + + if( cmIsFlag(op->wlimitFlags,kBottomGrFl) ) + cmGrVExtSetMinY(ext,cmGrVExtMinY(&op->wlimitExt)); + + if( cmIsFlag(op->wlimitFlags,kTopGrFl) ) + cmGrVExtSetMaxY(ext,cmGrVExtMaxY(&op->wlimitExt)); + + if( cmIsFlag(op->wlimitFlags,kRightGrFl) ) + cmGrVExtSetMaxX(ext,cmGrVExtMaxX(&op->wlimitExt)); + +} + + +// Return the outside extents of the children of 'op'. +// Returns false if there are no children and leaves wext set to NULL. +bool _cmGrObjChildExts( cmGr_t* p, const cmGrObj_t* op, cmGrVExt_t* ext ) +{ + cmGrVExtSetNull(ext); + + op = op->children; + + if( op == NULL ) + return false; + + _cmGrObjCbVExt(p,op,ext); + + for(op=op->rsib; op!=NULL; op=op->rsib) + { + cmGrVExt_t e; + _cmGrObjCbVExt(p,op,&e); + cmGrVExtExpandToContain(ext,&e); + } + + return true; +} + +cmGrRC_t _cmGrObjSetWorldExt( cmGr_t* p, cmGrObj_t* op, const cmGrVExt_t* wext ); + +// The world extents changed to 'ref_wext' on an object. +// Examine all 'sync'ed' objects and expand them to be contained by 'ref_wext'. +cmGrRC_t _cmGrSyncWorldExtentsExpand( cmGr_t* p, const cmGrVExt_t* ref_wext ) +{ + cmGrRC_t rc = kOkGrRC; + + // apply changes to synch targets + cmGrSync_t* sp = p->syncs; + for(; sp!=NULL; sp=sp->link) + if( cmIsFlag(sp->flags,kWorldSyncGrFl) ) + { + // get the target ROOT object + cmGrObj_t* top = _cmGrObjHandleToPtr(cmGrRootObjH(sp->grH)); + bool fl = false; + cmGrVExt_t top_wext = top->wext; + + //printf("sync %i ",top->id); + //cmGrVExtPrint("top_wext",&top_wext); + //cmGrVExtPrint("ref_wext",ref_wext); + + // if horz sync was requested ... + if( !fl && cmIsFlag(sp->flags,kHorzSyncGrFl) ) + { + if( cmGrVExtIsNullOrEmpty(&top_wext) ) + { + cmGrVExtSetMinX(&top_wext,cmGrVExtMinX(ref_wext)); + cmGrVExtSetMaxX(&top_wext,cmGrVExtMaxX(ref_wext)); + fl = true; + } + else + { + // ... and the target needs to expand and can expand + if( cmGrVExtMinX(&top_wext) > cmGrVExtMinX(ref_wext) && cmIsNotFlag(top->wlimitFlags,kLeftGrFl) ) + { + cmGrVExtSetMinX(&top_wext,cmGrVExtMinX(ref_wext)); // .. expand the view + fl = true; + } + + if( cmGrVExtMaxX(&top_wext) < cmGrVExtMaxX(ref_wext) && cmIsNotFlag(top->wlimitFlags,kRightGrFl) ) + { + cmGrVExtSetMaxX(&top_wext,cmGrVExtMaxX(ref_wext)); + fl = true; + } + } + } + + // if vert sync was requested ... + if( !fl && cmIsFlag(sp->flags,kVertSyncGrFl) ) + { + if( cmGrVExtIsNullOrEmpty(&top_wext) ) + { + cmGrVExtSetMinY(&top_wext,cmGrVExtMinY(ref_wext)); + cmGrVExtSetMaxY(&top_wext,cmGrVExtMaxY(ref_wext)); + fl = true; + } + else + { + if( cmGrVExtMinY(&top_wext) > cmGrVExtMinY(ref_wext) && cmIsNotFlag(top->wlimitFlags,kBottomGrFl)) + { + cmGrVExtSetMinY(&top_wext,cmGrVExtMinY(ref_wext)); + fl = true; + } + + if( cmGrVExtMaxY(&top_wext) < cmGrVExtMaxY(ref_wext) && cmIsNotFlag(top->wlimitFlags,kTopGrFl) ) + { + cmGrVExtSetMaxY(&top_wext,cmGrVExtMaxY(ref_wext)); + fl = true; + } + + } + } + + // If fl is set then top_wext contains an expanded world view + if( fl ) + { + //cmGrVExtPrint("out top_wext",&top_wext); + // this call may result in a recursion back into this function + if((rc = _cmGrObjSetWorldExt( _cmGrHandleToPtr(sp->grH), top, &top_wext )) != kOkGrRC ) + goto errLabel; + } + } + + errLabel: + return rc; +} + +cmGrRC_t _cmGrObjSetWorldExt( cmGr_t* p, cmGrObj_t* op, const cmGrVExt_t* wext ) +{ + cmGrRC_t rc = kOkGrRC; + cmGrVExt_t ce; + cmGrVExt_t we = *wext; // make a copy of the new extents to override the 'const' on 'wext'. + + // apply the world ext limits to 'we'. + _cmGrObjApplyExtLimits(op,&we); + + assert(cmGrVExtIsNorm(&we)); // assert w/h are positive + + // get the extents around all children + if( _cmGrObjChildExts(p, op, &ce ) ) + { + // if 'ce' is not entirely inside 'we' + if( cmGrVExtIsExtInside(&we,&ce) == false ) + return cmErrMsg(&p->err,kExtsErrGrRC,"The change in world extents would have resulted in child objects outside the requested world extents."); + } + + // if world extents are actually changing + if( !cmGrVExtIsEqual(&op->wext,&we) ) + { + // update the world extents for this object + op->wext = we; + op->stateFlags = cmSetFlag(op->stateFlags,kDirtyObjFl); + + //cmGrVExtPrint(cmTsPrintf("set w: %i ",op->id),&we); + + // if this is the root object + if( p->rootObj == op ) + { + // this call may result in recursion back into this function + // if two cmGr's are mutual sync targets - an infinite loop + // should be avoided by the cmGrVExtIsEqual() test above. + rc = _cmGrSyncWorldExtentsExpand(p, wext ); + } + } + + return rc; +} + +void _cmGrObjReport( cmGr_t* p, cmGrObj_t* op, cmRpt_t* rpt ) +{ + cmGrVExt_t vext; + cmRptPrintf(rpt,"id:0x%x cfg:0x%x state:0x%x\n",op->id,op->cfgFlags,op->stateFlags); + + _cmGrObjCbVExt( p, op, &vext); + cmGrVExtRpt(&vext,rpt); + cmRptPrintf(rpt,"\n"); +} + +void _cmGrObjReportR( cmGr_t* p, cmGrObj_t* op, cmRpt_t* rpt) +{ + _cmGrObjReport(p, op,rpt); + + if( op->children != NULL ) + _cmGrObjReport(p,op->children,rpt); + + if( op->rsib != NULL ) + _cmGrObjReport(p,op->rsib,rpt); +} + + +//==================================================================================================== +cmGrRC_t cmGrObjCreate( cmGrH_t h, cmGrObjH_t* ohp, cmGrObjH_t parentH, cmGrObjFunc_t* f, unsigned id, unsigned flags, const cmGrVExt_t* wext ) +{ + cmGrRC_t rc; + + if((rc = cmGrObjDestroy(h,ohp)) != kOkGrRC ) + return rc; + + cmGr_t* p = _cmGrHandleToPtr(h); + + // allocate the new object + cmGrObj_t* op = cmMemAllocZ(cmGrObj_t,1); + + op->id = id; + op->cfgFlags = flags; + op->stateFlags = 0; + op->f = *f; + + if( wext != NULL ) + { + op->wext = *wext; + + if( cmGrVExtIsNotNull(wext) ) + cmGrVExtNorm(&op->wext); + } + + // if an explicit parent was not provided + // then assign the root object as the parent + if( cmGrObjIsValid(h,parentH) == false ) + parentH.h = p->rootObj; + + // insert the object into the tree + if( cmGrObjIsValid(h,parentH) ) + _cmGrObjAppendChild(_cmGrObjHandleToPtr(parentH),op); + else + { + // no root object exits - so make this obj the root + assert(p->objs == NULL ); + p->objs = op; + } + + ohp->h = op; + + // Notify the application that an object was created. + if((rc = _cmGrObjCbCreate(p,op)) != kOkGrRC ) + goto errLabel; + + + if( f->vextCbFunc != NULL ) + { + cmGrVExt_t vext; + cmGrVExtSetEmpty(&vext); + + // get the local virtual extents for the new object + cmGrObjLocalVExt(h, *ohp, &vext ); + + if( cmGrVExtIsNotNullOrEmpty(&vext) ) + { + // expand the parents world to contain the new object + if( op->parent != NULL ) + { + cmGrVExt_t parent_wext = op->parent->wext; + if( cmGrVExtExpandToContain(&parent_wext,&vext) ) + _cmGrObjSetWorldExt(p,op->parent,&parent_wext); + + assert( cmGrVExtIsExtInside(&op->parent->wext,&vext) ); + } + + // if cfg'd to expand the view to contain new objects then do so here + if( op->parent!=NULL && op->parent->parent==NULL && cmIsFlag(p->cfgFlags,kExpandViewGrFl) && cmGrVExtIsExtInside(&p->vext,&vext) == false ) + { + cmGrVExt_t v = p->vext; + if( cmGrVExtExpandToContain(&v,&vext) ) + _cmGrSetViewExtentsE(p,&v); + + assert( cmGrVExtIsExtInside(&op->parent->wext,&p->vext)); + } + + // if the new object is inside the view extents then mark + // the object as dirty + if( cmGrVExtIsExtInside(&p->vext,&vext) ) + op->stateFlags = cmSetFlag(op->stateFlags,kDirtyObjFl); + } + } + + errLabel: + if( rc != kOkGrRC ) + cmGrObjDestroy(h,ohp); + + return rc; +} + +cmGrRC_t _cmGrObjDestroy( cmGr_t* p, cmGrObj_t* op ) +{ + if( op == NULL ) + return kOkGrRC; + + return _cmGrObjUnlinkAndFree( p, op); +} + +cmGrRC_t cmGrObjDestroy( cmGrH_t h, cmGrObjH_t* ohp ) +{ + cmGrRC_t rc = kOkGrRC; + + if( ohp==NULL || cmGrObjIsValid(h,*ohp) == false ) + return kOkGrRC; + + cmGrObj_t* op = _cmGrObjHandleToPtr(*ohp); + + if((rc = _cmGrObjDestroy(_cmGrHandleToPtr(h),op)) != kOkGrRC ) + return rc; + + ohp->h = NULL; + + return rc; +} + +cmGrRC_t cmGrObjIsValid( cmGrH_t h, cmGrObjH_t oh ) +{ return oh.h != NULL; } + + +unsigned cmGrObjId( cmGrObjH_t oh ) +{ return _cmGrObjHandleToPtr(oh)->id; } + +void cmGrObjSetId( cmGrObjH_t oh, unsigned id ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->id = id; +} + +cmGrObjH_t cmGrObjParent( cmGrObjH_t oh ) +{ + cmGrObjH_t poh; + poh.h = _cmGrObjHandleToPtr(oh)->parent; + return poh; +} + + +cmGrRC_t cmGrObjSetWorldExt( cmGrH_t h, cmGrObjH_t oh, const cmGrVExt_t* wext ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return _cmGrObjSetWorldExt(p,op,wext); +} + +void cmGrObjWorldExt( cmGrObjH_t oh, cmGrVExt_t* wext ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + *wext = op->wext; +} + +cmGrRC_t cmGrObjSetWorldLimitExt( cmGrH_t h, cmGrObjH_t oh, const cmGrVExt_t* vext, unsigned limitFlags ) +{ + cmGrRC_t rc; + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + + // store the current world extents + cmGrVExt_t lext = op->wlimitExt; + unsigned lfl = op->wlimitFlags; + + // set the new limits + op->wlimitExt = *vext; + op->wlimitFlags = limitFlags; + + cmGrVExtNorm(&op->wlimitExt); + + // attempt to apply the current world extents with the new limits + // (this may fail if their are child objects out of range of the new extents) + if( cmGrVExtIsNotNull(&op->wext ) ) + { + if((rc = cmGrObjSetWorldExt(h,oh,&op->wext)) != kOkGrRC ) + { + // we failed - restore the old limits + op->wlimitExt = lext; + op->wlimitFlags = lfl; + } + } + + return rc; +} + +void cmGrObjWorldLimitExt( cmGrObjH_t oh, cmGrVExt_t* vext, unsigned* limitFlags ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + if( vext != NULL ) + *vext = op->wlimitExt; + + if( limitFlags != NULL ) + *limitFlags = op->wlimitFlags; +} + + +void cmGrObjSetCreateCb( cmGrObjH_t oh, cmGrCreateObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.createCbFunc = cbFunc; + op->f.createCbArg = cbArg; +} + +void cmGrObjSetDestroyCb( cmGrObjH_t oh, cmGrDestroyObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.destroyCbFunc = cbFunc; + op->f.destroyCbArg = cbArg; +} + +void cmGrObjSetRenderCb( cmGrObjH_t oh, cmGrRenderObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.renderCbFunc = cbFunc; + op->f.renderCbArg = cbArg; +} + +void cmGrObjSetDistanceCb( cmGrObjH_t oh, cmGrDistanceObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.distanceCbFunc = cbFunc; + op->f.distanceCbArg = cbArg; +} + +void cmGrObjSetEventCb( cmGrObjH_t oh, cmGrEventObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.eventCbFunc = cbFunc; + op->f.eventCbArg = cbArg; +} + +void cmGrObjSetVExtCb( cmGrObjH_t oh, cmGrVExtObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.vextCbFunc = cbFunc; + op->f.vextCbArg = cbArg; +} + +void cmGrObjSetIsInsideCb( cmGrObjH_t oh, cmGrIsInsideObjCb_t cbFunc, void* cbArg ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + op->f.isInsideCbFunc = cbFunc; + op->f.isInsideCbArg = cbArg; +} + +cmGrCreateObjCb_t cmGrObjCreateCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.createCbFunc; +} + +cmGrDestroyObjCb_t cmGrObjDestroyCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.destroyCbFunc; +} + +cmGrRenderObjCb_t cmGrObjRenderCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.renderCbFunc; +} + +cmGrDistanceObjCb_t cmGrObjDistanceCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.distanceCbFunc; +} + +cmGrEventObjCb_t cmGrObjEventCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.eventCbFunc; +} + +cmGrVExtObjCb_t cmGrObjVExtCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.vextCbFunc; +} + +cmGrIsInsideObjCb_t cmGrObjIsInsideCbFunc( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.isInsideCbFunc; +} + +void* cmGrObjCreateCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.createCbArg; +} + +void* cmGrObjDestroyCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.createCbArg; +} + +void* cmGrObjRenderCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.destroyCbArg; +} + +void* cmGrObjDistanceCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.distanceCbArg; +} + +void* cmGrObjEventCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.eventCbArg; +} + +void* cmGrObjVExtCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.vextCbArg; +} + +void* cmGrObjIsInsideCbArg( cmGrObjH_t oh ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return op->f.isInsideCbArg; +} + +void cmGrObjLocalVExt( cmGrH_t h, cmGrObjH_t oh, cmGrVExt_t* vext ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return _cmGrObjCbVExt( _cmGrHandleToPtr(h), op, vext); +} + +cmGrObj_t* _cmGrObjIdToHandle( cmGrObj_t* op, unsigned id ) +{ + cmGrObj_t* rp = NULL; + if( op->id == id ) + return op; + + if( op->children != NULL ) + if((rp = _cmGrObjIdToHandle(op->children,id)) != NULL ) + return rp; + + if( op->rsib != NULL ) + if((rp = _cmGrObjIdToHandle(op->rsib,id)) != NULL ) + return rp; + + return NULL; +} + +cmGrObjH_t cmGrObjIdToHandle( cmGrH_t h, unsigned id ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + cmGrObjH_t oh = cmGrObjNullHandle; + cmGrObj_t* op; + + if((op = _cmGrObjIdToHandle(p->objs,id)) != NULL ) + oh.h = op; + return oh; +} + +// Move 'aoH' such that it is above 'boH' in the z-order. +// This means that 'boH' must be drawn before 'aoH'. +// This algorithm is designed to not break object hierarchies +// when moving objects. It achieves this by only moving +// the ancestor objects of boH and aoH at the level where +// they do not share a common ancestor. +void cmGrObjDrawAbove( cmGrObjH_t boH, cmGrObjH_t aoH ) +{ + cmGrObj_t* bp = _cmGrObjHandleToPtr(boH); + cmGrObj_t* ap = _cmGrObjHandleToPtr(aoH); + cmGrObj_t* rp = bp; + + // set rp to the root object + while( rp->parent != NULL ) + rp=rp->parent; + + while(1) + { + + cmGrObj_t* a[] = {NULL,NULL}; + cmGrObj_t* bpp = NULL; + cmGrObj_t* app = NULL; + unsigned i = 0; + + rp = rp->children; + for(; rp!=NULL; rp=rp->rsib) + { + if( bp==rp || _cmGrObjIsAncestor(rp,bp) ) + { + assert( a[i]==NULL ); + bpp = rp; + a[i++] = rp; + + if( i==2 ) + break; + } + + if( ap==rp || _cmGrObjIsAncestor(rp,ap) ) + { + assert( a[i]==NULL ); + app = rp; + a[i++] = rp; + + if( i==2 ) + break; + } + } + + assert( rp != NULL && i==2 ); + + // bpp and app share the same ancestor - keep looking + // for the level where the they do not share same ancestor + if( bpp == app ) + rp = bpp; + else + { + // if bp is not already being drawn before ap + if( a[0] != bpp ) + { + _cmGrObjUnlink(app); + _cmGrObjInsertOnRight(app,bpp,bpp->parent); + } + return; + } + + } + +} + +void cmGrObjReport( cmGrH_t h, cmGrObjH_t oh, cmRpt_t* rpt ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + _cmGrObjReport(_cmGrHandleToPtr(h),op,rpt); +} + +void cmGrObjReportR( cmGrH_t h, cmGrObjH_t oh, cmRpt_t* rpt ) +{ + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + _cmGrObjReportR(_cmGrHandleToPtr(h),op,rpt); +} + + +//==================================================================================================== +//==================================================================================================== +#define _cmGr_X_VperP(p) (p->vext.sz.w / (p->pext.sz.w-1) ) +#define _cmGr_Y_VperP(p) (p->vext.sz.h / (p->pext.sz.h-1) ) +#define _cmGr_X_PperV(p) ((p->pext.sz.w-1) / p->vext.sz.w ) +#define _cmGr_Y_PperV(p) ((p->pext.sz.h-1) / p->vext.sz.h ) + +int _cmGr_X_VtoP( cmGr_t* p, cmGrV_t vx ) +{ return p->pext.loc.x + lround( (vx - p->vext.loc.x) * _cmGr_X_PperV(p)); } + +int _cmGr_Y_VtoP(cmGr_t* p, cmGrV_t vy ) +{ return p->pext.loc.y + (p->pext.sz.h-1) + lround(-(vy - p->vext.loc.y) * _cmGr_Y_PperV(p) ); } + +cmGrV_t _cmGr_X_PtoV( cmGr_t* p, int px ) +{ return p->vext.loc.x + (px - p->pext.loc.x) * _cmGr_X_VperP(p); } + +cmGrV_t _cmGr_Y_PtoV( cmGr_t* p, int py ) +{ return p->vext.loc.y + (p->pext.loc.y + (p->pext.sz.h-1) - py) * _cmGr_Y_VperP(p); } + + + +#define _cmGrParentToLocalX( op, vext, x ) (op)->wext.loc.x + ((x) - (vext).loc.x) * (op)->wext.sz.w / (vext).sz.w +#define _cmGrParentToLocalY( op, vext, y ) (op)->wext.loc.y + ((y) - (vext).loc.y) * (op)->wext.sz.h / (vext).sz.h + +#define _cmGrLocalToParentX( op, vext, x ) (vext).loc.x + ((x) - (op)->wext.loc.x) * (vext).sz.w / (op)->wext.sz.w +#define _cmGrLocalToParentY( op, vext, y ) (vext).loc.y + ((y) - (op)->wext.loc.y) * (vext).sz.h / (op)->wext.sz.h + + +// On input x,y are in the same coord's as op->vext. +// On output pt is converted to op's internal coord system (i.e. the pt is inside op->wext) +// Using pt as the src and dst is always safe. (i.e. _cmGrLocalToParent(p,op,pt->x,pt->y,pt) is safe.) +bool _cmGrParentToLocal( cmGr_t* p, cmGrObj_t* op, cmGrV_t x, cmGrV_t y, cmGrVPt_t* pt ) +{ + cmGrVExt_t vext; + _cmGrObjCbVExt(p,op,&vext); + + if( cmGrVExtIsNullOrEmpty(&vext) ) + return false; + + pt->x = _cmGrParentToLocalX( op, vext, x); + pt->y = _cmGrParentToLocalY( op, vext, y ); + + //pt->x = op->wext.loc.x + (x - vext.loc.x) * op->wext.sz.w / vext.sz.w; + //pt->y = op->wext.loc.y + (y - vext.loc.y) * op->wext.sz.h / vext.sz.h; + return true; +} + + +// On input x,y are in the same coords as op->wext. +// On output pt is converted to be in the same coord's as op->vext (i.e.the pt is inside op->parent->wext) +// Using pt as the src and dst is always safe. (i.e. _cmGrLocalToParent(p,op,pt->x,pt->y,pt) is safe.) +void _cmGrLocalToParent( cmGr_t* p, cmGrObj_t* op, cmGrV_t x, cmGrV_t y, cmGrVPt_t* pt ) +{ + cmGrVExt_t vext; + _cmGrObjCbVExt(p,op,&vext); + + pt->x = _cmGrLocalToParentX(op,vext, x); + pt->y = _cmGrLocalToParentY(op,vext, y); + + //pt->x = vext.loc.x + (x - op->wext.loc.x) * vext.sz.w / op->wext.sz.w; + //pt->y = vext.loc.y + (y - op->wext.loc.y) * vext.sz.h / op->wext.sz.h; +} + +// On input x is in coord's inside op->wext. +// Return is in phys coord's +int _cmGrX_VtoP( cmGr_t* p, cmGrObj_t* op, cmGrV_t x ) +{ + cmGrVExt_t vext; + + for(; op->parent != NULL; op=op->parent ) + { + _cmGrObjCbVExt(p,op,&vext); + x = _cmGrLocalToParentX(op->parent,vext,x); + } + + return _cmGr_X_VtoP(p,x); +} + +// On input y is in coord's inside op->wext. +// Return is in phys coord's +int _cmGrY_VtoP( cmGr_t* p, cmGrObj_t* op, cmGrV_t y ) +{ + cmGrVExt_t vext; + + for(; op->parent != NULL; op=op->parent ) + { + _cmGrObjCbVExt(p,op,&vext); + y = _cmGrLocalToParentY(op->parent,vext,y); + } + + return _cmGr_Y_VtoP(p,y); +} + +// On input x,y are coord's inside op->wext. +// On output rp is in physical coord's +void _cmGrXY_VtoP( cmGr_t* p, cmGrObj_t* op, cmGrV_t x, cmGrV_t y, cmGrPPt_t* rp ) +{ + cmGrVPt_t pt; + cmGrVPtSet(&pt,x,y); + + for(; op->parent != NULL; op=op->parent ) + _cmGrLocalToParent(p,op->parent,pt.x,pt.y,&pt); + + rp->x = _cmGr_X_VtoP(p,pt.x); + rp->y = _cmGr_Y_VtoP(p,pt.y); +} + +// pt is converted from the root obj coord system to be in the same coord's +// as op->vext (inside of op->parent->wext) +void _cmGrXY_GlobalToLocal( cmGr_t* p, cmGrObj_t* op, cmGrVPt_t* pt ) +{ + if( op->parent != NULL ) + _cmGrXY_GlobalToLocal(p,op->parent,pt); + + // convert from parent coord to child coords + _cmGrParentToLocal(p,op,pt->x,pt->y,pt); + +} + + +// On input x,y are in physical coordindates. +// On output rp is inside op->parent->wext (the same coord's as op->vext) +void _cmGrXY_PtoV( cmGr_t* p, cmGrObj_t* op, int px, int py, cmGrVPt_t* rp ) +{ + // convert the phys points to points in the root coord system + rp->x = _cmGr_X_PtoV(p,px); + rp->y = _cmGr_Y_PtoV(p,py); + + _cmGrXY_GlobalToLocal(p, op, rp ); +} + +int cmGrX_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t x ) +{ + cmGr_t* p = _cmGrHandleToPtr(hh); + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return _cmGrX_VtoP(p,op,x); +} + +int cmGrY_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t y ) +{ + cmGr_t* p = _cmGrHandleToPtr(hh); + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + return _cmGrY_VtoP(p,op,y); +} + + +void cmGrXY_VtoP( cmGrH_t h, cmGrObjH_t oh, cmGrV_t x, cmGrV_t y, cmGrPPt_t* rp ) +{ _cmGrXY_VtoP(_cmGrHandleToPtr(h), _cmGrObjHandleToPtr(oh), x, y, rp ); } + +void cmGrXYWH_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t x, cmGrV_t y, cmGrV_t w, cmGrV_t h, cmGrPExt_t* pext ) +{ + cmGr_t* p = _cmGrHandleToPtr(hh); + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + cmGrPPt_t pt0,pt1; + _cmGrXY_VtoP(p,op,x, y, &pt0); + _cmGrXY_VtoP(p,op,x+w,y+h,&pt1); + cmGrPExtSetD(pext,pt0.x,pt0.y,pt1.x,pt1.y); +} + +void cmGrVExt_VtoP( cmGrH_t hh, cmGrObjH_t oh, const cmGrVExt_t* vext, cmGrPExt_t* pext ) +{ cmGrXYWH_VtoP(hh,oh,vext->loc.x,vext->loc.y,vext->sz.w,vext->sz.h,pext); } + +void cmGrXY_PtoV( cmGrH_t h, cmGrObjH_t oh, int x, int y, cmGrVPt_t* rp ) +{ _cmGrXY_PtoV(_cmGrHandleToPtr(h), _cmGrObjHandleToPtr(oh), x, y, rp ); } + +void cmGrXYWH_PtoV( cmGrH_t hh, cmGrObjH_t oh, int x, int y, int w, int h, cmGrVExt_t* vext ) +{ + cmGr_t* p = _cmGrHandleToPtr(hh); + cmGrObj_t* op = _cmGrObjHandleToPtr(oh); + cmGrVPt_t pt0,pt1; + _cmGrXY_PtoV(p,op,x,y,&pt0); + _cmGrXY_PtoV(p,op,x+w-1,y+h-1,&pt1); + cmGrVExtSetD(vext,pt0.x,pt0.y,pt1.x,pt1.y); +} + +void cmGrPExt_PtoV( cmGrH_t hh, cmGrObjH_t oh, const cmGrPExt_t* pext, cmGrVExt_t* vext ) +{ cmGrXYWH_PtoV(hh,oh,pext->loc.x,pext->loc.y,pext->sz.w,pext->sz.h,vext); } + + + void cmGrDrawVLine( cmGrH_t h, cmGrDcH_t dcH, cmGrObjH_t oh, cmGrV_t x0, cmGrV_t y0, cmGrV_t x1, cmGrV_t y1 ) +{ + cmGrPPt_t loc0; + cmGrPPt_t loc1; + + cmGrXY_VtoP(h,oh,x0,y0,&loc0); + cmGrXY_VtoP(h,oh,x1,y1,&loc1); + cmGrDcDrawLine(dcH,loc0.x,loc0.y,loc1.x,loc1.y); +} + +void cmGrDrawVRect( cmGrH_t hh, cmGrDcH_t dcH, cmGrObjH_t oh, cmGrV_t x, cmGrV_t y, cmGrV_t w, cmGrV_t h ) +{ + cmGrDrawVLine(hh,dcH,oh,x, y, x, y+h); + cmGrDrawVLine(hh,dcH,oh,x, y+h,x+w,y+h); + cmGrDrawVLine(hh,dcH,oh,x+w,y+h,x+w,y); + cmGrDrawVLine(hh,dcH,oh,x+w,y, x, y); +} + + +//==================================================================================================== +//==================================================================================================== + +cmGrRC_t _cmGrDestroy( cmGr_t* p ) +{ + cmGrRC_t rc = kOkGrRC; + + if( p->objs != NULL ) + { + // destroy the root object and all of it's children + if((rc = _cmGrObjDestroy(p,p->objs)) != kOkGrRC ) + return rc; + } + + cmGrSync_t* sp = p->syncs; + while( sp != NULL ) + { + cmGrH_t h; + h.h = p; + + cmGrSync_t* np = sp->link; + // break sync with any mutually sync'ed gr's to prevent + // attempted calls to this gr. + cmGrSetSync( sp->grH, h, 0 ); + cmMemFree(sp); + sp = np; + } + + p->syncs = NULL; + + cmLHeapDestroy(&p->lhH); + + cmMemPtrFree(&p->img); + + cmMemFree(p); + + return rc; +} + +void _cmGrObjRootVExt( cmGrObjFuncArgs_t* args, cmGrVExt_t* vext ) +{ + // the root object's virtual extent is the same as it's world extent + cmGrObjWorldExt( args->objH, vext ); +} + +bool _cmGrObjRootIsInside( cmGrObjFuncArgs_t* args, int px, int py, cmGrV_t vx, cmGrV_t vy ) +{ + cmGrVExt_t vext; + _cmGrObjRootVExt(args,&vext); + return cmGrVExtIsXyInside(&vext,vx,vy); +} + +void _cmGrSetCfgFlags( cmGr_t* p, unsigned flags ) +{ + p->cfgFlags = flags; + + // kSelectHorzFl and kSelectVertFl are mutually exclusive + if( cmIsFlag(p->cfgFlags,kSelectHorzGrFl) ) + p->cfgFlags = cmClrFlag(p->cfgFlags,kSelectVertGrFl); + +} + +void _cmGrCallback( cmGr_t* p, cmGrCbId_t id, unsigned eventFlags, cmGrKeyCodeId_t keycode ) +{ + if( p->cbFunc != NULL ) + { + cmGrH_t h; + h.h = p; + p->cbFunc(p->cbArg,h,id,eventFlags,keycode); + } +} + + +cmGrRC_t cmGrCreate( cmCtx_t* ctx, cmGrH_t* hp, unsigned id, unsigned cfgFlags, cmGrCbFunc_t cbFunc, void* cbArg, const cmGrVExt_t* wext ) +{ + cmGrRC_t rc = kOkGrRC; + cmGrVExt_t null_ext; + cmGrObjH_t rootObjH = cmGrObjNullHandle; + + if(( rc = cmGrDestroy(hp)) != kOkGrRC ) + return rc; + + // create the cmGr_t + cmGr_t* p = cmMemAllocZ(cmGr_t,1); + p->ctx = *ctx; + p->id = id; + p->stateFlags = kDirtyGrFl; + p->cbFunc = cbFunc; + p->cbArg = cbArg; + + _cmGrSetCfgFlags(p,cfgFlags); + + if( wext == NULL ) + { + cmGrVExtSetNull(&null_ext); + wext = &null_ext; + } + + cmErrSetup(&p->err,&ctx->rpt,"cmGr"); + + if( cmLHeapIsValid( p->lhH = cmLHeapCreate(8192,ctx)) == false ) + { + rc = cmErrMsg(&p->err,kLHeapFailGrRC,"Linked heap create failed."); + goto errLabel; + } + + cmGrVExtSetEmpty(&p->vext); + cmGrPExtSetEmpty(&p->pext); + + hp->h = p; + + // create the root object + cmGrObjFunc_t funcs; + memset(&funcs,0,sizeof(funcs)); + funcs.vextCbFunc = _cmGrObjRootVExt; + funcs.isInsideCbFunc = _cmGrObjRootIsInside; + + if((rc = cmGrObjCreate(*hp, &rootObjH, cmGrObjNullHandle, &funcs, cmInvalidId, 0, wext )) != kOkGrRC ) + { + rc = cmErrMsg(&p->err,kRootObjCreateFailGrRC,"The root object creation failed."); + goto errLabel; + } + + // create the default color map + _cmGrRgbInitDefaultColorMap(p); + + p->objs = _cmGrObjHandleToPtr(rootObjH); + p->rootObj = p->objs; + + _cmGrCallback(p,kCreateCbGrId,0,kInvalidKeyCodeGrId); + + errLabel: + if( rc != kOkGrRC ) + { + _cmGrDestroy(p); + hp->h = NULL; + } + + return rc; +} + +cmGrRC_t cmGrDestroy( cmGrH_t* hp ) +{ + cmGrRC_t rc = kOkGrRC; + if( hp==NULL || cmGrIsValid(*hp) == false ) + return kOkGrRC; + + cmGr_t* p = _cmGrHandleToPtr(*hp); + + if((rc = _cmGrDestroy(p)) != kOkGrRC ) + goto errLabel; + + hp->h = NULL; + + errLabel: + return rc; +} + +// Destroy all objects (except the root object) +cmGrRC_t _cmGrObjDestroyAll( cmGr_t* p ) +{ + cmGrObj_t* op = p->objs; + if( op != NULL ) + { + op = op->children; + while( op != NULL ) + { + cmGrObj_t* np = op->rsib; + _cmGrObjUnlinkAndFree(p,op); + op = np; + } + } + return kOkGrRC; +} + + +cmGrRC_t cmGrClear( cmGrH_t h ) +{ + cmGrRC_t rc; + cmGr_t* p = _cmGrHandleToPtr(h); + + if((rc = _cmGrObjDestroyAll(p)) != kOkGrRC ) + return rc; + + p->rootObj = p->objs; + p->stateFlags = kDirtyGrFl; + + cmGrVExtSetEmpty(&p->vext); + + p->msDnObj = NULL; + cmGrPPtSet(&p->msDnPPt,0,0); + cmGrVPtSet(&p->msDnVPt,0,0); + cmGrVPtSet(&p->msVPt,0,0); + cmGrVSzSet(&p->msDnVOffs,0,0); + + p->selValidFl = false; + cmGrVPtSet(&p->sel0Pt,0,0); + cmGrVPtSet(&p->sel1Pt,0,0); + + cmGrVPtSet(&p->localPt,0,0); + cmGrVPtSet(&p->globalPt,0,0); + + cmGrVExtSetNull(&p->rootObj->wext); + return rc; +} + + +cmGrObjH_t cmGrRootObjH( cmGrH_t h ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + cmGrObjH_t oh; + oh.h = p->rootObj; + return oh; +} + +unsigned cmGrCfgFlags( cmGrH_t h ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + return p->cfgFlags; +} + +void cmGrSetCfgFlags( cmGrH_t h, unsigned cfgFlags ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + _cmGrSetCfgFlags(p,cfgFlags); +} + + +void _cmGrObjDraw( cmGr_t* p, cmGrObj_t* op, cmGrDcH_t dcH ) +{ + _cmGrObjCbRender(p,dcH,op); + + if( op->children != NULL ) + _cmGrObjDraw(p,op->children,dcH); + + if( op->rsib != NULL ) + _cmGrObjDraw(p,op->rsib,dcH); + +} + +void _cmInvertImage( cmGr_t* p, cmGrDcH_t dcH, const cmGrPExt_t* pext ) +{ + if( cmGrPExtIsNullOrEmpty(pext) ) + return; + + cmGrPExt_t r; + cmGrPExtIntersect( &r, pext, &p->pext ); + + unsigned n = r.sz.w * r.sz.h * 3; + + if( n > 0 ) + { + unsigned i; + p->img = cmMemResizeZ(unsigned char,p->img,n); + + //p->dd.read_image(p->ddUser, p->img, r.loc.x, r.loc.y, r.sz.w, r.sz.h ); + cmGrDcReadImage( dcH, p->img, &r ); + + for(i=0; iimg[i] = 255 - p->img[i]; + + //p->dd.draw_image(p->ddUser, p->img, r.loc.x, r.loc.y, r.sz.w, r.sz.h); + cmGrDcDrawImage( dcH, p->img, &r); + } +} + +cmGrRC_t cmGrDraw( cmGrH_t h, cmGrDcH_t dcH ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + _cmGrObjDraw(p,p->rootObj,dcH); + + cmGrPExt_t pext; + cmGrVExt_t vext; + cmGrSelectExtents(h,&vext,&pext); + + if( cmGrPExtIsNotNull(&pext)) + { + + if( pext.sz.w<=1 && pext.sz.h<=1 ) + { + cmGrPExt_t ipext; + + if( !cmIsFlag(p->cfgFlags,kSelectHorzGrFl) ) + { + cmGrPExtSet(&ipext,p->pext.loc.x,pext.loc.y,p->pext.sz.w,1); + _cmInvertImage(p,dcH,&ipext); + } + + if( !cmIsFlag(p->cfgFlags,kSelectVertGrFl) ) + { + cmGrPExtSet(&ipext,pext.loc.x,p->pext.loc.y,1,p->pext.sz.h); + _cmInvertImage(p,dcH,&ipext); + } + } + else + { + _cmInvertImage(p,dcH,&pext); + } + } + + return kOkGrRC; +} + +bool _cmGrSetViewExtents( cmGr_t* p, cmGrV_t minx, cmGrV_t miny, cmGrV_t maxx, cmGrV_t maxy ) +{ + cmGrVExt_t vext; + + // load a vext with the new view extents + cmGrVExtSetD(&vext,minx,miny,maxx,maxy); + + // if the view ext is not changing + if( cmGrVExtIsEqual(&p->vext,&vext) ) + return false; + + // the root object must exist + assert( p->rootObj != NULL ); + + // the view extents must be in the world extents + if( cmGrVExtIsExtInside(&p->rootObj->wext,&vext)==false ) + { + cmErrMsg(&p->err,kInvalidCoordsGrRC,"View extent is not inside the world extents."); + return false; + } + + //cmGrVExtPrint("set vw:",&vext); + + p->vext = vext; + p->stateFlags = cmSetFlag(p->stateFlags,kDirtyGrFl); + + _cmGrCallback(p, kViewExtCbGrId,0,kInvalidKeyCodeGrId ); + + // apply changes to synch target + cmGrSync_t* sp = p->syncs; + for(; sp!=NULL; sp=sp->link) + if( cmIsFlag(sp->flags,kViewSyncGrFl) ) + { + cmGrViewExtents( sp->grH, &vext ); + bool fl = false; + + if( cmIsFlag(sp->flags,kHorzSyncGrFl) ) + { + fl = true; + vext.loc.x = p->vext.loc.x; + vext.sz.w = p->vext.sz.w; + } + + + if( cmIsFlag(sp->flags,kVertSyncGrFl) ) + { + fl = true; + vext.loc.y = p->vext.loc.y; + vext.sz.h = p->vext.sz.h; + } + + if( fl ) + cmGrSetViewExtentsE( sp->grH, &vext ); + } + //printf("s:%p %f %f %f %f\n",p,p->vext.loc.x, p->vext.loc.y, p->vext.sz.w, p->vext.sz.h ); + + return true; +} + +bool _cmGrSetScrollH( cmGr_t* p, int x ) +{ + assert( p->rootObj != NULL ); + cmGrV_t vx = p->rootObj->wext.loc.x + (x * p->vext.sz.w / p->pext.sz.w); + + return _cmGrSetViewExtents(p,vx, p->vext.loc.y, vx+p->vext.sz.w, p->vext.loc.y+p->vext.sz.h); +} + +int _cmGrScrollH( cmGr_t* p ) +{ return round((p->vext.loc.x - p->rootObj->wext.loc.x) * p->pext.sz.w / p->vext.sz.w); } + +bool _cmGrSetScrollV( cmGr_t* p, int y ) +{ + assert( p->rootObj != NULL ); + cmGrV_t vy = p->rootObj->wext.loc.y + (y * p->vext.sz.h / p->pext.sz.h); + + return _cmGrSetViewExtents(p, p->vext.loc.x, vy, p->vext.loc.x+p->vext.sz.w, vy + p->vext.sz.h); +} + +int _cmGrScrollV( cmGr_t* p ) +{ return round((p->vext.loc.y - p->rootObj->wext.loc.y) * p->pext.sz.h / p->vext.sz.h); } + +void _cmGrSetSelectPoints(cmGr_t* p, const cmGrVPt_t* pt0, const cmGrVPt_t* pt1) +{ + bool deltaFl = false; + + p->selValidFl = true; + + if( pt0 != NULL ) + { + if( cmGrVPtIsNotEqual(&p->sel0Pt,pt0) ) + { + p->sel0Pt = *pt0; + deltaFl = true; + } + + if( pt1 == NULL && cmGrVPtIsNotEqual(&p->sel1Pt,pt0) ) + { + p->sel1Pt = *pt0; + deltaFl = true; + } + } + + if( pt1 != NULL && cmGrVPtIsNotEqual(&p->sel1Pt,pt1) ) + { + p->sel1Pt = *pt1; + deltaFl = true; + } + + if( !deltaFl ) + return; + + _cmGrCallback(p,kSelectExtCbGrId,0,kInvalidKeyCodeGrId); + + // apply changes to synch target + cmGrSync_t* sp = p->syncs; + for(; sp!=NULL; sp=sp->link) + if( cmIsFlag(sp->flags,kSelectSyncGrFl) ) + { + cmGrVPt_t pt0, pt1; + cmGrSelectPoints(sp->grH, &pt0, &pt1 ); + + + bool fl = false; + + if( cmIsFlag(sp->flags,kHorzSyncGrFl) ) + { + fl = true; + pt0.x = p->sel0Pt.x; + pt1.x = p->sel1Pt.x; + } + + if( cmIsFlag(sp->flags,kVertSyncGrFl) ) + { + fl = true; + pt0.y = p->sel0Pt.y; + pt1.y = p->sel1Pt.y; + } + + if( fl ) + cmGrSetSelectPoints( sp->grH, &pt0, &pt1 ); + } + + +} + +// vx,vy are in the same coord's as op->vext +cmGrObj_t* _cmGrFindObjRec( cmGr_t* p, cmGrObj_t* op, int px, int py, cmGrV_t vx, cmGrV_t vy ) +{ + cmGrObj_t* rop = NULL; + cmGrObj_t* top; + cmGrVExt_t vext; + + // get the location of op inside op->parent->wext + _cmGrObjCbVExt(p,op,&vext); + + // is vx,vy inside op - this is equiv to: cmGrVExtIsXyInside(&vext,vx,vy) + if( _cmGrObjCbIsInside(p,op,px,py,vx,vy) ) + rop = op; + + if( op->children != NULL ) + { + cmGrVPt_t pt; + if( _cmGrParentToLocal(p,op,vx,vy,&pt) ) + if((top = _cmGrFindObjRec(p,op->children,px,py,vx,vy)) != NULL ) + rop = top; + } + + if( op->rsib != NULL ) + if((top = _cmGrFindObjRec(p,op->rsib,px,py,vx,vy)) != NULL ) + rop = top; + + + return rop; +} + + +bool cmGrEvent( cmGrH_t h, unsigned flags, cmGrKeyCodeId_t key, int px, int py ) +{ + bool fl = false; + cmGr_t* p = _cmGrHandleToPtr(h); + cmGrObj_t* op; + cmGrVPtSet(&p->localPt,0,0); + + // convert the phys points to points in the root coord system + cmGrV_t gx = _cmGr_X_PtoV(p,px); + cmGrV_t gy = _cmGr_Y_PtoV(p,py); + + cmGrVPtSet(&p->globalPt,gx,gy); + + _cmGrCallback(p,kGlobalPtCbGrId,0,kInvalidKeyCodeGrId); + + // find the obj under the mouse + if((op = _cmGrFindObjRec(p,p->rootObj,px,py,gx,gy)) != NULL ) + { + // convert gx,gy to be inside op->wext + cmGrVPtSet(&p->localPt,gx,gy); + _cmGrXY_GlobalToLocal(p,op,&p->localPt); + + _cmGrCallback(p,kLocalPtCbGrId,0,kInvalidKeyCodeGrId); + + } + + if( (flags&kEvtMask)==kKeyUpGrFl ) + _cmGrCallback(p,kKeyUpCbGrId,flags,key); + + if( (flags&kEvtMask)==kKeyDnGrFl ) + _cmGrCallback(p,kKeyDnCbGrId,flags,key); + + if( op != NULL ) + { + switch( flags & kEvtMask ) + { + case kMsDownGrFl: + + // store the phys loc. of the ms dn event + cmGrPPtSet(&p->msDnPPt,px,py); + + if( op != NULL ) + { + // if the click was in not in the root object + if( op->parent != NULL ) + { + // store the object and coord's where the mouse went down. + + cmGrVExt_t vext; + p->msDnObj = op; // set the msDnObj + + // convert the phys ms dn point to the virt point inside op->parent.wext + _cmGrXY_PtoV(p, op->parent, px, py, &p->msDnVPt ); + + // set the current ms virt pt + p->msVPt = p->msDnVPt; + + // get the ms down obj virt extents + _cmGrObjCbVExt(p,op,&vext); + + // store the offset from the lower left to the ms dn point + p->msDnVOffs.w = p->msDnVPt.x - vext.loc.x; + p->msDnVOffs.h = p->msDnVPt.y - vext.loc.y; + + // notify the object that the mouse went down + fl = _cmGrObjCbEvent(p,op,flags,key,px,py); + } + } + break; + + case kMsUpGrFl: + { + cmGrPPt_t upPPt; + cmGrPPtSet(&upPPt,px,py); + + bool clickFl = cmGrPPtIsEqual(&p->msDnPPt,&upPPt ); + + // if a click occured ... + if( clickFl ) + { + // ... and the click is in a non-root object ... + if( p->msDnObj!= NULL && p->msDnObj->parent!=NULL ) + { + // ... then generate a click event + unsigned evtFlags = cmClrFlag(flags,kMsUpGrFl) | kMsClickGrFl; + fl = _cmGrObjCbEvent(p,op,evtFlags,key,px,py); + } + else // ... else if the click occurred in the root object + { + cmGrVPt_t pt; + cmGrVPtSet(&pt,gx,gy); + + bool shFl = cmIsFlag(flags,kShiftKeyGrFl); + _cmGrSetSelectPoints( p, shFl ? NULL : &pt, shFl ? &pt : NULL ); + + } + + } + + // notify the object under the mouse that the mouse went up + if( _cmGrObjCbEvent(p,op,flags,key,px,py) ) + fl = true; + + _cmGrCallback(p,kFocusCbGrId,0,kInvalidKeyCodeGrId); + + p->msDnObj = NULL; + } + break; + + case kMsMoveGrFl: + fl = _cmGrObjCbEvent(p,op,flags,key,px,py); + break; + + case kMsWheelGrFl: + break; + + case kMsDragGrFl: + if( p->msDnObj != NULL ) + { + // set the current virtual point + _cmGrXY_PtoV(p, p->msDnObj->parent, px, py, &p->msVPt ); + + // callback the dragged object + fl = _cmGrObjCbEvent(p,p->msDnObj,flags,key,px,py); + + // if the px,py is outside the root phys extents then scroll + if( !cmGrPExtIsXyInside(&p->pext,px,py) ) + { + bool hFl = false, vFl=false; + cmGrPSz_t tot,vis,max; + cmGrPPt_t pos; + cmGrScrollExtents(h, &tot, &vis, &max, &pos ); + + if( px < cmGrPExtL(&p->pext) ) + hFl = _cmGrSetScrollH(p, cmMax(0, _cmGrScrollH(p) - (cmGrPExtL(&p->pext) - px))); + else + if( px > cmGrPExtR(&p->pext) ) + hFl = _cmGrSetScrollH(p, cmMin(max.w, _cmGrScrollH(p) + (px - cmGrPExtR(&p->pext)))); + + if( py < cmGrPExtT(&p->pext) ) + vFl = _cmGrSetScrollV(p, cmMax(0, _cmGrScrollV(p) - (cmGrPExtT(&p->pext) - py))); + else + if( py > cmGrPExtB(&p->pext) ) + vFl = _cmGrSetScrollV(p, cmMin(max.h, _cmGrScrollV(p) + (py - cmGrPExtB(&p->pext)))); + + + fl = fl || vFl || hFl; + } + + } + break; + + case kKeyDnGrFl: + case kKeyUpGrFl: + //fl = _cmGrObjCbEvent(p,p->msDnObj,flags,key,px,py); + break; + } + } + + return fl; +} + + +bool cmGrIsValid( cmGrH_t h ) +{ return h.h != NULL; } + +unsigned cmGrId( cmGrH_t h ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + return p->id; +} + +const cmGrVPt_t* cmGrGlobalPt( cmGrH_t h ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + return &p->globalPt; +} + +const cmGrVPt_t* cmGrLocalPt( cmGrH_t h ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + return &p->localPt; +} + + +bool cmGrSetViewExtents( cmGrH_t h, cmGrV_t minx, cmGrV_t miny, cmGrV_t maxx, cmGrV_t maxy ) +{ return _cmGrSetViewExtents( _cmGrHandleToPtr(h), minx,miny,maxx,maxy); } + +bool cmGrSetViewExtentsE( cmGrH_t h, const cmGrVExt_t* e ) +{ return cmGrSetViewExtents( h, cmGrVExtMinX(e), cmGrVExtMinY(e), cmGrVExtMaxX(e), cmGrVExtMaxY(e) ); } + +void cmGrViewExtents( cmGrH_t h, cmGrVExt_t* vext ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + *vext = p->vext; + + //printf("g0:%p %f %f %f %f\n",p,p->vext.loc.x, p->vext.loc.y, p->vext.sz.w, p->vext.sz.h ); + //printf("g1:%p %f %f %f %f\n",p,vext->loc.x, vext->loc.y, vext->sz.w, vext->sz.h ); +} + + +bool cmGrSetPhysExtents( cmGrH_t hh, int x, int y, int w, int h ) +{ + cmGr_t* p = _cmGrHandleToPtr(hh); + cmGrPExt_t pext; + cmGrPExtSet(&pext,x,y,w,h); + + assert( cmGrPExtIsNull(&pext)==false ); + + if( cmGrPExtIsEqual(&pext,&p->pext) ) + return false; + + p->pext = pext; + p->stateFlags = cmSetFlag(p->stateFlags,kDirtyGrFl); + + _cmGrCallback(p,kPhysExtCbGrId,0,kInvalidKeyCodeGrId); + + //cmGrPExtPrint("pext",&p->pext); + return true; +} + +bool cmGrSetPhysExtentsE(cmGrH_t h, const cmGrPExt_t* e ) +{ return cmGrSetPhysExtents(h, cmGrPExtL(e), cmGrPExtT(e), cmGrPExtW(e), cmGrPExtH(e) ); } + +void cmGrPhysExtents( cmGrH_t h, cmGrPExt_t* exts ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + *exts = p->pext; +} + + +void cmGrScrollExtents( cmGrH_t h, cmGrPSz_t* tot, cmGrPSz_t* vis, cmGrPSz_t* max, cmGrPPt_t* pos ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + + assert( p->rootObj != NULL ); + + if( tot != NULL ) + { + tot->w = round(p->rootObj->wext.sz.w * p->pext.sz.w / p->vext.sz.w); + tot->h = round(p->rootObj->wext.sz.h * p->pext.sz.h / p->vext.sz.h); + } + + if( vis != NULL ) + { + vis->w = round(p->vext.sz.w * p->pext.sz.w / p->vext.sz.w); + vis->h = round(p->vext.sz.h * p->pext.sz.h / p->vext.sz.h); + } + + if( max != NULL ) + { + max->w = tot->w - vis->w; + max->h = tot->h - vis->h; + } + + if( pos != NULL ) + { + pos->x = _cmGrScrollH(p); + pos->y = _cmGrScrollV(p); + } +} + +bool cmGrSetScrollH( cmGrH_t h, int x ) +{ return _cmGrSetScrollH( _cmGrHandleToPtr(h), x ); } + +int cmGrScrollH( cmGrH_t h ) +{ return _cmGrScrollH( _cmGrHandleToPtr(h) ); } + +bool cmGrSetScrollV( cmGrH_t h, int y ) +{ return _cmGrSetScrollV( _cmGrHandleToPtr(h), y ); } + +int cmGrScrollV( cmGrH_t h ) +{ return _cmGrScrollV( _cmGrHandleToPtr(h) ); } + +bool cmGrSelectExtents( cmGrH_t h, cmGrVExt_t* vext, cmGrPExt_t* pext ) +{ + cmGrPPt_t pt0,pt1; + cmGr_t* p = _cmGrHandleToPtr(h); + + if( !p->selValidFl ) + { + cmGrVExtSetNull(vext); + cmGrPExtSetNull(pext); + return false; + } + + if( p->sel0Pt.x == p->sel1Pt.x && p->sel0Pt.y == p->sel1Pt.y ) + cmGrVExtSet(vext, p->sel0Pt.x, p->sel0Pt.y, 0, 0); + else + { + cmGrV_t gx0=p->sel0Pt.x, gy0=p->sel0Pt.y; + cmGrV_t gx1=p->sel1Pt.x, gy1=p->sel1Pt.y; + + if( cmIsFlag(p->cfgFlags,kSelectHorzGrFl) ) + { + gy0 = cmGrVExtMaxY(&p->rootObj->wext); + gy1 = cmGrVExtMinY(&p->rootObj->wext); + } + else + if( cmIsFlag(p->cfgFlags,kSelectVertGrFl ) ) + { + gx0 = cmGrVExtMinX(&p->rootObj->wext); + gx1 = cmGrVExtMaxX(&p->rootObj->wext); + } + + cmGrVExtSetD(vext,cmMin(gx0,gx1),cmMin(gy0,gy1),cmMax(gx0,gx1),cmMax(gy0,gy1)); + + } + + _cmGrXY_VtoP(p, p->rootObj, vext->loc.x, vext->loc.y, &pt0 ); + _cmGrXY_VtoP(p, p->rootObj, vext->loc.x + vext->sz.w, vext->loc.y + vext->sz.h, &pt1 ); + + cmGrPExtSetD(pext,cmMin(pt0.x,pt1.x),cmMin(pt0.y,pt1.y),cmMax(pt0.x,pt1.x),cmMax(pt0.y,pt1.y)); + + //printf("%f %f %f %f\n",vext->loc.x,vext->loc.y,vext->sz.w,vext->sz.h); + //printf("%i %i %i %i\n",pext->loc.x,pext->loc.y,pext->sz.w,pext->sz.h); + return true; +} + +void cmGrSetSelectPoints( cmGrH_t h, const cmGrVPt_t* pt0, const cmGrVPt_t* pt1 ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + _cmGrSetSelectPoints(p,pt0,pt1); +} + +void cmGrSelectPoints( cmGrH_t h, cmGrVPt_t* pt0, cmGrVPt_t* pt1 ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + + if( pt0 != NULL ) + *pt0 = p->sel0Pt; + + if( pt1 != NULL ) + *pt1 = p->sel1Pt; +} + + +void cmGrZoom( cmGrH_t h, unsigned flags ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + bool hfl = cmIsFlag(flags,kXAxisGrFl); + bool vfl = cmIsFlag(flags,kYAxisGrFl); + bool inFl = cmIsFlag(flags,kZoomInGrFl); + bool allFl = cmIsFlag(flags,kShowAllGrFl); + double magn = 3.0; + double ratio = inFl ? 1.0/magn : magn; + cmGrVPt_t c; + cmGrVExt_t z; + cmGrVExt_t v; + cmGrVExt_t w; + + cmGrVExtSetNull(&z); + cmGrViewExtents( h,&v); // get the current view extents + cmGrObjWorldExt( cmGrRootObjH(h),&w); // get the current world extents + + + // if show all was requested ... + if( allFl ) + { + + // .. then the world ext's become the view extents. + cmGrVExtSetD(&z, + hfl ? cmGrVExtMinX(&w) : cmGrVExtMinX(&v), + vfl ? cmGrVExtMinY(&w) : cmGrVExtMinY(&v), + hfl ? cmGrVExtMaxX(&w) : cmGrVExtMaxX(&v), + vfl ? cmGrVExtMaxY(&w) : cmGrVExtMaxY(&v)); + } + else + { + + // if the selection flag is not set or the selection pt/area is not valid + if( cmIsNotFlag(flags,kSelectGrFl) || p->selValidFl==false ) + { + // center the zoom on the current view + c.x = v.loc.x + v.sz.w/2; + c.y = v.loc.y + v.sz.h/2; + } + else + { + // if the selection area is a point + if( p->sel0Pt.x == p->sel1Pt.x && p->sel0Pt.y == p->sel1Pt.y ) + { + // center the zoom on the current view + c.x = p->sel0Pt.x; + c.y = p->sel1Pt.y; + } + else + { + cmGrPExt_t dum; + + // The selection area exist - make it the new view area + // Note that f the selection area is greater than the + // current view area then this may not be a magnification. + cmGrSelectExtents(h,&z,&dum); + } + } + + // if no zoom area has been created then create + // one centered on 'c'. + if( cmGrVExtIsNull(&z) ) + { + cmGrVExt_t t; + cmGrV_t zw = v.sz.w * ratio / 2; + cmGrV_t zh = v.sz.h * ratio / 2; + + cmGrVExtSetD(&t, + hfl ? c.x-zw : cmGrVExtMinX(&v), + vfl ? c.y-zh : cmGrVExtMinY(&v), + hfl ? c.x+zw : cmGrVExtMaxX(&v), + vfl ? c.y+zh : cmGrVExtMaxY(&v)); + + cmGrVExtIntersect(&z,&t,&w); + + } + } + + //cmGrVExtPrint("w:",&w); + //cmGrVExtPrint("z:",&z); + //cmGrVExtPrint("v:",&v); + assert( cmGrVExtIsNorm(&z)); + + cmGrSetViewExtentsE(h,&z); +} + +void cmGrSetSync( cmGrH_t h, cmGrH_t syncGrH, unsigned flags ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + + // attempt to locate syncGrH on the sync list + cmGrSync_t* pp = NULL; + cmGrSync_t* sp = p->syncs; + for(; sp != NULL; sp=sp->link) + { + if( cmHandlesAreEqual(sp->grH,syncGrH) ) + break; + + pp = sp; + } + + // if the handle was not found ... + if( sp == NULL ) + { + // ... and a valid flags value was given ... + if( flags != 0 ) + { + // ... then create a new sync target. + sp = cmMemAllocZ(cmGrSync_t,1); + sp->grH = syncGrH; + sp->flags = flags; + sp->link = p->syncs; + p->syncs = sp; + } + } + else // ... otherwise syncGrH is already a sync target + { + // if flags is non-zero then update the target sync flags + if( flags != 0 ) + sp->flags = flags; + else + { + // otherwise delete the sync recd assoc'd with syncGrH + if( pp == NULL ) + p->syncs = sp->link; + else + pp->link = sp->link; + + cmMemFree(sp); + } + } +} + +void cmGrReport( cmGrH_t h,cmRpt_t* r ) +{ + cmGr_t* p = _cmGrHandleToPtr(h); + cmRpt_t* rpt = r==NULL ? p->err.rpt : r; + + cmRptPrintf(rpt,"cfg:0x%x state:0x%x\n",p->cfgFlags, p->stateFlags); + //cmRptPrintf(rpt,"World: "); cmGrVExtMaxXpt(&p->wext,rpt); cmRptPrintf(rpt,"\n"); + cmRptPrintf(rpt,"View: "); cmGrVExtRpt(&p->vext,rpt); cmRptPrintf(rpt,"\n"); + cmRptPrintf(rpt,"Phys: "); cmGrPExtRpt(&p->pext,rpt); cmRptPrintf(rpt,"\n"); + + _cmGrObjReportR(p,p->rootObj,rpt); +} diff --git a/cmGr.h b/cmGr.h new file mode 100644 index 0000000..2785706 --- /dev/null +++ b/cmGr.h @@ -0,0 +1,871 @@ +#ifndef cmGr_h +#define cmGr_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kAliceBlueGrId = 0xf0f8ff, + kAntiqueWhiteGrId = 0xfaebd7, + kAquaGrId = 0x00ffff, + kAquamarineGrId = 0x7fffd4, + kAzureGrId = 0xf0ffff, + kBeigeGrId = 0xf5f5dc, + kBisqueGrId = 0xffe4c4, + kBlackGrId = 0x000000, + kBlanchedAlmondGrId = 0xffebcd, + kBlueGrId = 0x0000ff, + kBlueVioletGrId = 0x8a2be2, + kBrownGrId = 0xa52a2a, + kBurlyWoodGrId = 0xdeb887, + kCadetBlueGrId = 0x5f9ea0, + kChartreuseGrId = 0x7fff00, + kChocolateGrId = 0xd2691e, + kCoralGrId = 0xff7f50, + kCornflowerBlueGrId = 0x6495ed, + kCornsilkGrId = 0xfff8dc, + kCrimsonGrId = 0xdc143c, + kCyanGrId = 0x00ffff, + kDarkBlueGrId = 0x00008b, + kDarkCyanGrId = 0x008b8b, + kDarkGoldenRodGrId = 0xb8860b, + kDarkGrayGrId = 0xa9a9a9, + kDarkGreyGrId = 0xa9a9a9, + kDarkGreenGrId = 0x006400, + kDarkKhakiGrId = 0xbdb76b, + kDarkMagentaGrId = 0x8b008b, + kDarkOliveGreenGrId = 0x556b2f, + kDarkorangeGrId = 0xff8c00, + kDarkOrchidGrId = 0x9932cc, + kDarkRedGrId = 0x8b0000, + kDarkSalmonGrId = 0xe9967a, + kDarkSeaGreenGrId = 0x8fbc8f, + kDarkSlateBlueGrId = 0x483d8b, + kDarkSlateGrayGrId = 0x2f4f4f, + kDarkSlateGreyGrId = 0x2f4f4f, + kDarkTurquoiseGrId = 0x00ced1, + kDarkVioletGrId = 0x9400d3, + kDeepPinkGrId = 0xff1493, + kDeepSkyBlueGrId = 0x00bfff, + kDimGrayGrId = 0x696969, + kDimGreyGrId = 0x696969, + kDodgerBlueGrId = 0x1e90ff, + kFireBrickGrId = 0xb22222, + kFloralWhiteGrId = 0xfffaf0, + kForestGreenGrId = 0x228b22, + kFuchsiaGrId = 0xff00ff, + kGainsboroGrId = 0xdcdcdc, + kGhostWhiteGrId = 0xf8f8ff, + kGoldGrId = 0xffd700, + kGoldenRodGrId = 0xdaa520, + kGrayGrId = 0x808080, + kGreyGrId = 0x808080, + kGreenGrId = 0x008000, + kGreenYellowGrId = 0xadff2f, + kHoneyDewGrId = 0xf0fff0, + kHotPinkGrId = 0xff69b4, + kIndianRedGrId = 0xcd5c5c, + kIndigoGrId = 0x4b0082, + kIvoryGrId = 0xfffff0, + kKhakiGrId = 0xf0e68c, + kLavenderGrId = 0xe6e6fa, + kLavenderBlushGrId = 0xfff0f5, + kLawnGreenGrId = 0x7cfc00, + kLemonChiffonGrId = 0xfffacd, + kLightBlueGrId = 0xadd8e6, + kLightCoralGrId = 0xf08080, + kLightCyanGrId = 0xe0ffff, + kLightGoldenRodYellowGrId = 0xfafad2, + kLightGrayGrId = 0xd3d3d3, + kLightGreyGrId = 0xd3d3d3, + kLightGreenGrId = 0x90ee90, + kLightPinkGrId = 0xffb6c1, + kLightSalmonGrId = 0xffa07a, + kLightSeaGreenGrId = 0x20b2aa, + kLightSkyBlueGrId = 0x87cefa, + kLightSlateGrayGrId = 0x778899, + kLightSlateGreyGrId = 0x778899, + kLightSteelBlueGrId = 0xb0c4de, + kLightYellowGrId = 0xffffe0, + kLimeGrId = 0x00ff00, + kLimeGreenGrId = 0x32cd32, + kLinenGrId = 0xfaf0e6, + kMagentaGrId = 0xff00ff, + kMaroonGrId = 0x800000, + kMediumAquaMarineGrId = 0x66cdaa, + kMediumBlueGrId = 0x0000cd, + kMediumOrchidGrId = 0xba55d3, + kMediumPurpleGrId = 0x9370d8, + kMediumSeaGreenGrId = 0x3cb371, + kMediumSlateBlueGrId = 0x7b68ee, + kMediumSpringGreenGrId = 0x00fa9a, + kMediumTurquoiseGrId = 0x48d1cc, + kMediumVioletRedGrId = 0xc71585, + kMidnightBlueGrId = 0x191970, + kMintCreamGrId = 0xf5fffa, + kMistyRoseGrId = 0xffe4e1, + kMoccasinGrId = 0xffe4b5, + kNavajoWhiteGrId = 0xffdead, + kNavyGrId = 0x000080, + kOldLaceGrId = 0xfdf5e6, + kOliveGrId = 0x808000, + kOliveDrabGrId = 0x6b8e23, + kOrangeGrId = 0xffa500, + kOrangeRedGrId = 0xff4500, + kOrchidGrId = 0xda70d6, + kPaleGoldenRodGrId = 0xeee8aa, + kPaleGreenGrId = 0x98fb98, + kPaleTurquoiseGrId = 0xafeeee, + kPaleVioletRedGrId = 0xd87093, + kPapayaWhipGrId = 0xffefd5, + kPeachPuffGrId = 0xffdab9, + kPeruGrId = 0xcd853f, + kPinkGrId = 0xffc0cb, + kPlumGrId = 0xdda0dd, + kPowderBlueGrId = 0xb0e0e6, + kPurpleGrId = 0x800080, + kRedGrId = 0xff0000, + kRosyBrownGrId = 0xbc8f8f, + kRoyalBlueGrId = 0x4169e1, + kSaddleBrownGrId = 0x8b4513, + kSalmonGrId = 0xfa8072, + kSandyBrownGrId = 0xf4a460, + kSeaGreenGrId = 0x2e8b57, + kSeaShellGrId = 0xfff5ee, + kSiennaGrId = 0xa0522d, + kSilverGrId = 0xc0c0c0, + kSkyBlueGrId = 0x87ceeb, + kSlateBlueGrId = 0x6a5acd, + kSlateGrayGrId = 0x708090, + kSlateGreyGrId = 0x708090, + kSnowGrId = 0xfffafa, + kSpringGreenGrId = 0x00ff7f, + kSteelBlueGrId = 0x4682b4, + kTanGrId = 0xd2b48c, + kTealGrId = 0x008080, + kThistleGrId = 0xd8bfd8, + kTomatoGrId = 0xff6347, + kTurquoiseGrId = 0x40e0d0, + kVioletGrId = 0xee82ee, + kWheatGrId = 0xf5deb3, + kWhiteGrId = 0xffffff, + kWhiteSmokeGrId = 0xf5f5f5, + kYellowGrId = 0xffff00, + kYellowGreenGrId = 0x9acd32 + }; + + + typedef enum + { + kHomeGrId = 5, // 5 + kPageUpGrId, // 6 + kEndGrId, // 7 + kBackSpaceGrId = 8, // 8 + kTabGrId = 9, // 9 + kPageDownGrId, // 10 + kLeftGrId, // 11 + kUpGrId, // 12 + kEnterGrId = 13, // 13 + kRightGrId, // 14 + kDownGrId, // 15 + kInsertGrId, // 16 + kPrintGrId, // 17 + kScrollLockGrId, // 18 + kPauseGrId, // 19 + kMenuGrId, // 20 + kLShiftGrId, // 21 + kRShiftGrId, // 22 + kLCtrlGrId, // 23 + kRCtrlGrId, // 24 + kLAltGrId, // 25 + kRAltGrId, // 26 + kEscapeGrId = 27, // 27 + kLSuperGrId, // 28 + kRSuperGrId, // 29 + kNumLockGrId, // 30 + kCapsLockGrId, // 31 + kSpaceGrId = 32, // 32 Min. printable ASCII + kExclMarkGrId, // 33 + kDQuoteGrId, // 34 + kPoundGrId, // 35 + kDollarGrId, // 36 + kPercentGrId, // 37 + kAmpersandGrId, // 38 + kApostropheGrId, // 39 + kLParenGrId, // 40 + kRParenGrId, // 41 + kAsteriskGrId, // 42 + kPlusGrId, // 43 + kCommaGrId, // 44 + kHyphenGrId, // 45 + kPeriodGrId, // 46 + kForwardSlashGrId, // 47 + k0GrId, // 48 + k1GrId, // 49 + k2GrId, // 50 + k3GrId, // 51 + k4GrId, // 52 + k5GrId, // 53 + k6GrId, // 54 + k7GrId, // 55 + k8GrId, // 56 + k9GrId, // 57 + kColonGrId, // 58 + kSemiColonGrId, // 59 + kLesserGrId, // 60 + kEqualGrId, // 61 + kGreaterGrId, // 62 + kQMarkGrId, // 63 + kAtGrId, // 64 + kA_GrId, // 65 + kB_GrId, // 66 + kC_GrId, // 67 + kD_GrId, // 68 + kE_GrId, // 69 + kF_GrId, // 70 + kG_GrId, // 71 + kH_GrId, // 72 + kI_GrId, // 73 + kJ_GrId, // 74 + kK_GrId, // 75 + kL_GrId, // 76 + kM_GrId, // 77 + kN_GrId, // 78 + kO_GrId, // 79 + kP_GrId, // 80 + kQ_GrId, // 81 + kR_GrId, // 82 + kS_GrId, // 83 + kT_GrId, // 84 + kU_GrId, // 85 + kV_GrId, // 86 + kW_GrId, // 87 + kX_GrId, // 88 + kY_GrId, // 89 + kZ_GrId, // 90 + kLBracketGrId, // 91 + kBackSlashGrId, // 92 + kRBracketGrId, // 93 + kCaretGrId, // 94 + kUnderScoreGrId, // 95 + kAccentGrId, // 96 + ka_GrId, // 97 + kb_GrId, // 98 + kc_GrId, // 99 + kd_GrId, // 100 + ke_GrId, // 101 + kf_GrId, // 102 + kg_GrId, // 103 + kh_GrId, // 104 + ki_GrId, // 105 + kj_GrId, // 106 + kk_GrId, // 107 + kl_GrId, // 108 + km_GrId, // 109 + kn_GrId, // 110 + ko_GrId, // 111 + kp_GrId, // 112 + kq_GrId, // 113 + kr_GrId, // 114 + ks_GrId, // 115 + kt_GrId, // 116 + ku_GrId, // 117 + kv_GrId, // 118 + kw_GrId, // 119 + kx_GrId, // 120 + ky_GrId, // 121 + kz_GrId, // 122 + kLBraceGrId, // 123 + kPipeGrId, // 124 + kRBraceGrId, // 125 + kTildeGrId, // 126 + kDeleteGrId, // 127 + kNP_MultGrId, // 128 + kNP_PlusGrId, // 129 + kNP_MinusGrId, // 130 + kNP_DecPtGrId, // 131 + kNP_DivGrId, // 132 + kNP_0GrId, // 133 + kNP_1GrId, // 134 + kNP_2GrId, // 135 + kNP_3GrId, // 136 + kNP_4GrId, // 137 + kNP_5GrId, // 138 + kNP_6GrId, // 139 + kNP_7GrId, // 140 + kNP_8GrId, // 141 + kNP_9GrId, // 142 + kNP_EqualGrId, // 143 + kNP_EnterGrId, // 144 + kFunc_1GrId, // 145 + kFunc_2GrId, // 146 + kFunc_3GrId, // 147 + kFunc_4GrId, // 148 + kFunc_5GrId, // 149 + kFunc_6GrId, // 150 + kFunc_7GrId, // 151 + kFunc_8GrId, // 152 + kFunc_9GrId, // 153 + kFunc_10GrId, // 154 + kFunc_11GrId, // 155 + kFunc_12GrId, // 156 + kBrightUpGrId, // 157 + kBrightDnGrId, // 158 + kAudio_PrevGrId, // 159 + kAudio_PlayGrId, // 160 + kAudio_NextGrId, // 161 + kAudio_MuteGrId, // 162 + kAudio_UpGrId, // 163 + kAudio_DnGrId, // 164 + kEjectGrId, // 165 + kInvalidKeyCodeGrId + } cmGrKeyCodeId_t; + + enum + { + kMinAsciiGrId = kSpaceGrId, + kMaxAsciiGrId = kDeleteGrId + }; + + enum + { + kOkGrRC, + kLHeapFailGrRC, + kAppErrGrRC, + kRootObjCreateFailGrRC, + kInvalidCoordsGrRC, + kExtsErrGrRC + }; + + enum + { + kLeftGrFl = 0x01, + kTopGrFl = 0x02, + kRightGrFl = 0x04, + kBottomGrFl = 0x08, + }; + + typedef enum + { + kLeftGrIdx = 0, // min-x + kTopGrIdx = 1, // max-y + kRightGrIdx = 2, // max-x + kBottomGrIdx = 3, // min-y + kAxisGrCnt = 4 + } cmGrAxisIdx_t; + + + typedef cmHandle_t cmGrH_t; + typedef cmHandle_t cmGrObjH_t; + typedef cmHandle_t cmGrDcH_t; + typedef unsigned cmGrRC_t; + + extern cmGrH_t cmGrNullHandle; + extern cmGrObjH_t cmGrObjNullHandle; + + typedef cmReal_t cmGrV_t; + + //==================================================================================================== + + // Calculate the width and height between two pixels. + // This implies that the first and last pixel are inside the valid range. +#define cmGrXtoW(x0,x1) (abs((x1)-(x0))+1) +#define cmGrWtoX(x0,w) (((x0)+(w))-1) + +#define cmGrYtoH(y0,y1) (abs((y1)-(y0))+1) +#define cmGrHtoY(y0,h) (((y0)+(h))-1) + +#define cmGrPIsXInRange(x,x0,w) ((x0)<=(x)&&(x)<=cmGrWtoX((x0),(w))) +#define cmGrPIsYInRange(y,y0,h) ((y0)<=(y)&&(y)<=cmGrHtoY((y0),(h))) + +#define cmGrVIsXInRange(x,x0,w) ((x0)<=(x)&&(x)<=((x0)+(w))) +#define cmGrVIsYInRange(y,y0,h) ((y0)<=(y)&&(y)<=((y0)+(h))) + + typedef struct + { + int x; + int y; + } cmGrPPt_t; + +#define cmGrPPtSet( p, xx, yy ) do{ (p)->x=(xx); (p)->y=(yy); }while(0) +#define cmGrPPtIsEqual(p0,p1) ((p0)->x==(p1)->x && (p0)->y==(p1)->y) +#define cmGrPPtPrint(lbl,p) printf("%s x=%i y=%i\n",(lbl),(p)->x,(p)->y) + + //==================================================================================================== + typedef struct + { + int w; + int h; + } cmGrPSz_t; + +#define cmGrPSzSet( s, ww, hh ) do{ (s)->w=(ww); (s)->h=(hh);}while(0) +#define cmGrPSzSetD( s, x0, y0, x1, y1 ) cmGrPSzSet(cmGrXtoW(x0,x1),cmGrYtoH(y0,y1)) + +#define cmGrPSzSetEmpty( s ) ((s)->w = (s)->h = 0) +#define cmGrPSzSetNull( s ) ((s)->w = (s)->h = -1) +#define cmGrPSzIsEmpty( s ) ((s)->w== 0 && (s)->h== 0) +#define cmGrPSzIsNull( s ) ((s)->w==-1 && (s)->h==-1) +#define cmGrPSzIsEqual(s0,s1) ((s0)->w==(s1)->w && (s0)->h==(s1)->h) +#define cmGrPSzPrint(lbl,s) printf("%s w=%i h=%i\n",(lbl),(s)->w,(s)->h) + + //==================================================================================================== + typedef struct + { + cmGrPPt_t loc; + cmGrPSz_t sz; + } cmGrPExt_t; + +#define cmGrPExtSet( e, x, y, w, h ) do{ cmGrPPtSet(&(e)->loc,(x),(y)); cmGrPSzSet(&(e)->sz,(w),(h)); }while(0) +#define cmGrPExtSetD(e, x0, y0, x1, y1) cmGrPExtSet(e,cmMin(x0,x1),cmMin(y0,y1),cmGrXtoW(x0,x1),cmGrYtoH(y0,y1)) + +#define cmGrPExtL(e) ((e)->loc.x) +#define cmGrPExtT(e) ((e)->loc.y) +#define cmGrPExtR(e) (cmGrWtoX((e)->loc.x,(e)->sz.w)) +#define cmGrPExtB(e) (cmGrHtoY((e)->loc.y,(e)->sz.h)) +#define cmGrPExtW(e) ((e)->sz.w) +#define cmGrPExtH(e) ((e)->sz.h) + +#define cmGrPExtSetL(e,v) ((e)->loc.x = (v)) +#define cmGrPExtSetT(e,v) ((e)->loc.y = (v)) +#define cmGrPExtSetR(e,v) cmGrPExtSetW(e,cmGrXtoW((e)->loc.x,(v))) +#define cmGrPExtSetB(e,v) cmGrPExtSetH(e,cmGrYtoH((e)->loc.y,(v))) +#define cmGrPExtSetW(e,v) ((e)->sz.w = (v)) +#define cmGrPExtSetH(e,v) ((e)->sz.h = (v)) + +#define cmGrPExtCtrX(e) ((e)->loc.x + (e)->sz.w / 2) +#define cmGrPExtCtrY(e) ((e)->loc.y + (e)->sz.h / 2) +#define cmGrPExtCtr(e,pt) do{ (pt)->x=cmGrPExtCtrX(e); (pt)->y=cmGrPExtCtrY(e); }while(0) + +#define cmGrPExtSetEmpty( e ) do{ cmGrPSzSetEmpty(&(e)->sz); cmGrPPtSet(&(e)->loc,0,0); }while(0) +#define cmGrPExtSetNull( e ) do{ cmGrPSzSetNull( &(e)->sz); cmGrPPtSet(&(e)->loc,0,0); }while(0) +#define cmGrPExtIsEmpty( e ) cmGrPSzIsEmpty( &(e)->sz ) +#define cmGrPExtIsNull( e ) cmGrPSzIsNull( &(e)->sz ) +#define cmGrPExtIsNullOrEmpty(e) (cmGrPExtIsNull(e)||cmGrPExtIsEmpty(e)) +#define cmGrPExtIsNotEmpty(e) (!cmGrPExtIsEmpty(e)) +#define cmGrPExtIsNotNull(e) (!cmGrPExtIsNull(e)) +#define cmGrPExtIsNotNullOrEmpty(e) (cmGrPExtIsNotNull(e)||cmGrPExtIsNoEmpty(e)) + +#define cmGrPExtIsEqual( e0, e1 ) (cmGrPPtIsEqual(&(e0)->loc,&(e1)->loc) && cmGrPSzIsEqual(&(e0)->sz, &(e1)->sz)) + +#define cmGrPExtIsXyInside( e, xx, yy) (cmGrPIsXInRange((xx),(e)->loc.x,(e)->sz.w) && cmGrPIsYInRange((yy), (e)->loc.y, (e)->sz.h) ) +#define cmGrPExtIsPtInside( e, pt ) (cmGrPExtIsXyInside((e),(pt)->x,(pt)->y)) +#define cmGrPExtIsExtInside(e0, e1) (cmGrPExtIsPtInside((e0),&((e1)->loc)) && cmGrPExtIsXyInside((e0), cmGrWtoX((e1)->loc.x,(e1)->sz.w), cmGrHtoY((e1)->loc.y,(e1)->sz.h))) + +#define cmGrPExtExpand(e,l,t,r,b) do{(e)->loc.x+=(l); (e)->loc.y+=(t); (e)->sz.w+=(abs(l)+abs(r)); (e)->sz.h+=(abs(t)+abs(b));}while(0) + +#define cmGrPExtRpt(e,rpt) cmRptPrintf(rpt,"x:%i y:%i w:%i h:%i",(e)->loc.x,(e)->loc.y,(e)->sz.w,(e)->sz.h) +#define cmGrPExtPrint(lbl,e) printf("%s %i %i %i %i\n",lbl,(e)->loc.x,(e)->loc.y,(e)->sz.w,(e)->sz.h) + + void cmGrPExtIntersect( cmGrPExt_t* r, const cmGrPExt_t* e0, const cmGrPExt_t* e1 ); + + + //==================================================================================================== + + typedef struct + { + cmGrV_t x; + cmGrV_t y; + } cmGrVPt_t; + +#define cmGrVPtSet( p, xx, yy ) do{ (p)->x=(xx); (p)->y=(yy); }while(0) +#define cmGrVPtIsEqual(p0,p1) ((p0)->x==(p1)->x && (p0)->y==(p1)->y) +#define cmGrVPtIsNotEqual(p0,p1) (!cmGrVPtIsEqual(p0,p1)) + + //==================================================================================================== + typedef struct + { + cmGrV_t w; + cmGrV_t h; + } cmGrVSz_t; + +#define cmGrVSzSet( s, ww, hh ) do{ (s)->w=(ww); (s)->h=(hh);}while(0) +#define cmGrVSzSetD( s, x0, y0, x1, y1 ) cmGrVSzSet((x1)-(x0),(y1)-(y0)) + +#define cmGrVSzSetEmpty( s ) ((s)->w = (s)->h = 0) +#define cmGrVSzSetNull( s ) ((s)->w = (s)->h = -1) +#define cmGrVSzIsEmpty( s ) ((s)->w== 0 && (s)->h== 0) +#define cmGrVSzIsNull( s ) ((s)->w==-1 || (s)->h==-1) +#define cmGrVSzIsEqual(s0,s1) ((s0)->w==(s1)->w && (s0)->h==(s1)->h) + + //==================================================================================================== + typedef struct + { + cmGrVPt_t loc; + cmGrVSz_t sz; + } cmGrVExt_t; + +#define cmGrVExtIsNorm( e ) ((e)->sz.w>=0 && (e)->sz.h>=0) +#define cmGrVExtNorm( e ) do{ if( cmGrVExtIsNotNull(e) ){ if((e)->sz.w<0){(e)->loc.x += (e)->sz.w; (e)->sz.w*=-1;} if((e)->sz.h<0){(e)->loc.y += (e)->sz.h; (e)->sz.h*=-1;}} }while(0) +#define cmGrVExtSet( e, x, y, w, h ) do{ cmGrVPtSet(&(e)->loc,(x),(y)); cmGrVSzSet(&(e)->sz,(w),(h)); cmGrVExtNorm(e); }while(0) +#define cmGrVExtSetD(e, x0, y0, x1, y1) cmGrVExtSet((e),(x0),(y0),(x1)-(x0),(y1)-(y0)) + + // + // l,t minx,maxy + // r,b maxx,miny + // +#define cmGrVExtMinX(e) ((e)->loc.x) +#define cmGrVExtMinY(e) ((e)->loc.y) +#define cmGrVExtMaxX(e) ((e)->loc.x + (e)->sz.w) +#define cmGrVExtMaxY(e) ((e)->loc.y + (e)->sz.h) +#define cmGrVExtW(e) ((e)->sz.w) +#define cmGrVExtH(e) ((e)->sz.h) + +#define cmGrVExtSetMinX(e,v) ((e)->loc.x = (v)) +#define cmGrVExtSetMinY(e,v) ((e)->loc.y = (v)) + + // Beware: setting maxx and maxy depends on the current value of minx and miny. + // If both minx and maxx are being changed then be sure to set minx first. + // If both miny and maxy are being changed then be sure to set miny first. +#define cmGrVExtSetMaxX(e,v) ((e)->sz.w = (v) - cmGrVExtMinX(e)) +#define cmGrVExtSetMaxY(e,v) ((e)->sz.h = (v) - cmGrVExtMinY(e)) +#define cmGrVExtSetW(e,v) ((e)->sz.w = (v)) +#define cmGrVExtSetH(e,v) ((e)->sz.h = (v)) + +#define cmGrVExtSetEmpty( e ) do{ cmGrVSzSetEmpty(&(e)->sz); cmGrVPtSet(&(e)->loc,0,0); }while(0) +#define cmGrVExtSetNull( e ) do{ cmGrVSzSetNull(&(e)->sz); cmGrVPtSet(&(e)->loc,0,0); }while(0) +#define cmGrVExtIsEmpty( e ) cmGrVSzIsEmpty(&(e)->sz) +#define cmGrVExtIsNull( e ) cmGrVSzIsNull( &(e)->sz) +#define cmGrVExtIsNullOrEmpty(e) (cmGrVExtIsNull(e)||cmGrVExtIsEmpty(e)) +#define cmGrVExtIsNotEmpty(e) (!cmGrVExtIsEmpty(e)) +#define cmGrVExtIsNotNull(e) (!cmGrVExtIsNull(e)) +#define cmGrVExtIsNotNullOrEmpty(e) (cmGrVExtIsNotNull(e)||cmGrVExtIsNotEmpty(e)) +#define cmGrVExtIsEqual( e0, e1 ) (cmGrVPtIsEqual(&(e0)->loc,&(e1)->loc) && cmGrVSzIsEqual(&(e0)->sz, &(e1)->sz)) + + +#define cmGrVExtIsXyInside( e, xx, yy) (cmGrVIsXInRange((xx),(e)->loc.x,(e)->sz.w) && cmGrVIsYInRange((yy),(e)->loc.y,(e)->sz.h)) +#define cmGrVExtIsPtInside( e, pt ) (cmGrVExtIsXyInside((e),(pt)->x,(pt)->y)) + + // e1 is inside e0 +#define cmGrVExtIsExtInside(e0, e1) (cmGrVExtIsXyInside((e0),cmGrVExtMinX(e1),cmGrVExtMinY(e1)) && cmGrVExtIsXyInside((e0), cmGrVExtMaxX(e1), cmGrVExtMaxY(e1))) + +#define cmGrVExtRpt(e,rpt) cmRptPrintf(rpt,"x:%f y:%f w:%f h:%f",(e)->loc.x,(e)->loc.y,(e)->sz.w,(e)->sz.h) +#define cmGrVExtPrint(lbl,e) printf("%s %f %f %f %f\n",lbl,(e)->loc.x,(e)->loc.y,(e)->sz.w,(e)->sz.h) + + + // Shift and expand e0 to contain e1. Return true if e0 actually changes. + bool cmGrVExtExpandToContain( cmGrVExt_t* e0, const cmGrVExt_t* e1 ); + + // Force e1 to be contained by e0 by shifting e1's location. This function + // will never change the width or height of e1. Return true if e1 is changed. + bool cmGrVExtContain( const cmGrVExt_t* e0, cmGrVExt_t* e1 ); + + // Return the intersection of 'e0' with 'e1' in 'r'. + void cmGrVExtIntersect( cmGrVExt_t* r, const cmGrVExt_t* e0, const cmGrVExt_t* e1 ); + + //==================================================================================================== + + +#define cmGrRgbToColor( r, g, b ) (((r) << 16) + ((g) << 8) + (b)) +#define cmGrColorToR( c ) (((c) >> 16) & 0x000000ff) +#define cmGrColorToG( c ) (((c) >> 8) & 0x000000ff) +#define cmGrColorToB( c ) (((c) ) & 0x000000ff) + + typedef unsigned cmGrColor_t; + enum { kGrDefaultColorMapIdx = 0, kGrDefaultColorMapId=0 }; + + unsigned cmGrColorMapCount( cmGrH_t grH ); + unsigned cmGrColorMapId( cmGrH_t grH, unsigned mapIdx ); + const cmChar_t* cmGrColorMapLabel( cmGrH_t grH, unsigned id ); + unsigned cmGrColorMapRegister( cmGrH_t grH, cmChar_t* label, const cmGrColor_t* array, unsigned cnt ); + cmGrColor_t* cmGrColorMap( cmGrH_t grH, unsigned mapId ); + unsigned cmGrColorMapEleCount( cmGrH_t grH, unsigned mapId ); + + //==================================================================================================== + typedef struct + { + cmCtx_t* ctx; // application context + cmGrH_t grH; // graphics system handle to which this graphic object belongs + cmGrObjH_t objH; // this graphics object handle + void* cbArg; // user callback arg + + cmGrPPt_t msDnPPt; // mouse down phys point + cmGrVPt_t msDnVPt; // mouse down virt point inside op->parent->wext + cmGrVSz_t msDnVOffs; // virtual offset from mouse down point to msDnObj->vext + cmGrObjH_t msDnObjH; // handle of object which recv'd mouse down + cmGrVPt_t msVPt; // cur ms virtual point + + } cmGrObjFuncArgs_t; + + + typedef cmGrRC_t (*cmGrCreateObjCb_t)( cmGrObjFuncArgs_t* args ); + typedef void (*cmGrDestroyObjCb_t)( cmGrObjFuncArgs_t* args ); + typedef bool (*cmGrRenderObjCb_t)( cmGrObjFuncArgs_t* args, cmGrDcH_t dcH ); + typedef int (*cmGrDistanceObjCb_t)( cmGrObjFuncArgs_t* args, int x, int y ); + typedef bool (*cmGrEventObjCb_t)( cmGrObjFuncArgs_t* args, unsigned flags, unsigned key, int px, int py ); + typedef void (*cmGrVExtObjCb_t)( cmGrObjFuncArgs_t* args, cmGrVExt_t* vext ); + typedef bool (*cmGrIsInsideObjCb_t)( cmGrObjFuncArgs_t* args, int px, int py, cmGrV_t vx, cmGrV_t vy ); + + typedef struct cmGrObjFunc_str + { + // User defined constructor. + cmGrCreateObjCb_t createCbFunc; + void* createCbArg; + + // User defined destructor. + cmGrDestroyObjCb_t destroyCbFunc; + void* destroyCbArg; + + // Draw the object by calling back to the cmGrDrawXXX() functions + cmGrRenderObjCb_t renderCbFunc; + void* renderCbArg; + + // Return the physical distance from a physical view location to the object. + // (NOT USED) + cmGrDistanceObjCb_t distanceCbFunc; + void* distanceCbArg; + + // Handle an event. gx,gy are in the same coord's as args.objH.vext (they are inside args.objH.parent.wext). + // Return true if the event objects dirty flag should be set. + cmGrEventObjCb_t eventCbFunc; + void* eventCbArg; + + // Return the objects location and size inside op->parent->wext + cmGrVExtObjCb_t vextCbFunc; + void* vextCbArg; + + // Return true if the point is inside this obj. vx,vy is in the the same coord's as op->vext (i.e. vx,vy is inside op->parent->wext) + // The simple answer to this call is cmGrVExtIsXyInside( *vext, vx, vy ). + // Called to determine which object is under the mouse. + cmGrIsInsideObjCb_t isInsideCbFunc; + void* isInsideCbArg; + } cmGrObjFunc_t; + + + // Create a graphic object. This function calls the user defined (*create)() function. + cmGrRC_t cmGrObjCreate( cmGrH_t h, cmGrObjH_t* hp, cmGrObjH_t parentH, cmGrObjFunc_t* f, unsigned id, unsigned flags, const cmGrVExt_t* wext ); + + // Destroy a graphic object and all of it's children. + // This function calls the user defined (*destroy)() function. + cmGrRC_t cmGrObjDestroy( cmGrH_t h, cmGrObjH_t* hp ); + + // Return true if 'oh' is a valid handle. + cmGrRC_t cmGrObjIsValid( cmGrH_t h, cmGrObjH_t oh ); + + // Return the user id associated with this object. + unsigned cmGrObjId( cmGrObjH_t oh ); + void cmGrObjSetId( cmGrObjH_t oh, unsigned id ); + + // Return the handle to the parent object. + cmGrObjH_t cmGrObjParent( cmGrObjH_t oh ); + + // An object world coord's are used to place child objects. + cmGrRC_t cmGrObjSetWorldExt( cmGrH_t h, cmGrObjH_t oh, const cmGrVExt_t* vext ); + void cmGrObjWorldExt( cmGrObjH_t oh, cmGrVExt_t* vext ); + + cmGrRC_t cmGrObjSetWorldLimitExt( cmGrH_t h, cmGrObjH_t oh, const cmGrVExt_t* vext, unsigned limitFlags ); + void cmGrObjWorldLimitExt( cmGrObjH_t oh, cmGrVExt_t* vext, unsigned* limitFlags ); + + void cmGrObjSetCreateCb( cmGrObjH_t oh, cmGrCreateObjCb_t cbFunc, void* cbArg ); + void cmGrObjSetDestroyCb( cmGrObjH_t oh, cmGrDestroyObjCb_t cbFunc, void* cbArg ); + void cmGrObjSetRenderCb( cmGrObjH_t oh, cmGrRenderObjCb_t cbFunc, void* cbArg ); + void cmGrObjSetDistanceCb( cmGrObjH_t oh, cmGrDistanceObjCb_t cbFunc, void* cbArg ); + void cmGrObjSetEventCb( cmGrObjH_t oh, cmGrEventObjCb_t cbFunc, void* cbArg ); + void cmGrObjSetVExtCb( cmGrObjH_t oh, cmGrVExtObjCb_t cbFunc, void* cbArg ); + void cmGrObjSetIsInsideCb( cmGrObjH_t oh, cmGrIsInsideObjCb_t cbFunc, void* cbArg ); + + cmGrCreateObjCb_t cmGrObjCreateCbFunc( cmGrObjH_t oh ); + cmGrDestroyObjCb_t cmGrObjDestroyCbFunc( cmGrObjH_t oh ); + cmGrRenderObjCb_t cmGrObjRenderCbFunc( cmGrObjH_t oh ); + cmGrDistanceObjCb_t cmGrObjDistanceCbFunc( cmGrObjH_t oh ); + cmGrEventObjCb_t cmGrObjEventCbFunc( cmGrObjH_t oh ); + cmGrVExtObjCb_t cmGrObjVExtCbFunc( cmGrObjH_t oh ); + cmGrIsInsideObjCb_t cmGrObjIsInsideCbFunc( cmGrObjH_t oh ); + + void* cmGrObjCreateCbArg( cmGrObjH_t oh ); + void* cmGrObjDestroyCbArg( cmGrObjH_t oh ); + void* cmGrObjRenderCbArg( cmGrObjH_t oh ); + void* cmGrObjDistanceCbArg( cmGrObjH_t oh ); + void* cmGrObjEventCbArg( cmGrObjH_t oh ); + void* cmGrObjVExtCbArg( cmGrObjH_t oh ); + void* cmGrObjIsInsideCbArg( cmGrObjH_t oh ); + + + // Same as call to user defined (*vect)(). + void cmGrObjLocalVExt( cmGrH_t h, cmGrObjH_t oh, cmGrVExt_t* vext ); + + // Given an objects id return it's handle. + cmGrObjH_t cmGrObjIdToHandle( cmGrH_t h, unsigned id ); + + // Move 'aoH' such that it is drawn above 'boH' in the z-order. + // This means that 'boH' will be drawn before 'aoH'. + void cmGrObjDrawAbove( cmGrObjH_t boH, cmGrObjH_t aoH ); + + void cmGrObjReport( cmGrH_t h, cmGrObjH_t oh, cmRpt_t* rpt ); + void cmGrObjReportR( cmGrH_t h, cmGrObjH_t oh, cmRpt_t* rpt ); // print children + + + //==================================================================================================== + // Drawing Functions - called by objects to draw themselves + + int cmGrX_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t y ); + int cmGrY_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t x ); + + void cmGrXY_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t x, cmGrV_t y, cmGrPPt_t* rp ); + void cmGrXYWH_VtoP( cmGrH_t hh, cmGrObjH_t oh, cmGrV_t x, cmGrV_t y, cmGrV_t w, cmGrV_t h, cmGrPExt_t* pext ); + void cmGrVExt_VtoP( cmGrH_t hh, cmGrObjH_t oh, const cmGrVExt_t* vext, cmGrPExt_t* pext ); + + void cmGrXY_PtoV( cmGrH_t hh, cmGrObjH_t oh, int x, int y, cmGrVPt_t* rp ); + void cmGrXYWH_PtoV( cmGrH_t hh, cmGrObjH_t oh, int x, int y, int w, int h, cmGrVExt_t* vext ); + void cmGrPExt_PtoV( cmGrH_t hh, cmGrObjH_t oh, const cmGrPExt_t* pext, cmGrVExt_t* vext ); + + void cmGrDrawVLine( cmGrH_t hh, cmGrDcH_t dcH, cmGrObjH_t oh, cmGrV_t x0, cmGrV_t y0, cmGrV_t x1, cmGrV_t y1 ); + void cmGrDrawVRect( cmGrH_t hh, cmGrDcH_t dcH, cmGrObjH_t oh, cmGrV_t x, cmGrV_t y, cmGrV_t w, cmGrV_t h ); + + //==================================================================================================== + + // Callback identifiers + typedef enum + { + kCreateCbGrId, + kDestroyCbGrId, + kLocalPtCbGrId, + kGlobalPtCbGrId, + kPhysExtCbGrId, + kViewExtCbGrId, + kSelectExtCbGrId, + kFocusCbGrId, + kKeyUpCbGrId, + kKeyDnCbGrId + } cmGrCbId_t; + + // Callback function associated with this canvas. + typedef void (*cmGrCbFunc_t)( void* arg, cmGrH_t grH, cmGrCbId_t id, unsigned evtFlags, cmGrKeyCodeId_t keycode ); + + // Configuration Flags + enum + { + kExpandViewGrFl = 0x01, // expand the view to show new objects + kSelectHorzGrFl = 0x02, // select along x-axis only + kSelectVertGrFl = 0x04 // select along y-axis only + }; + + // 'wext' is optional. + // 'id' is an arbitrary user definable identifier - although it is used + // as the view index by cmGrPage(). + cmGrRC_t cmGrCreate( + cmCtx_t* ctx, + cmGrH_t* hp, + unsigned id, + unsigned cfgFlags, + cmGrCbFunc_t cbFunc, + void* cbArg, + const cmGrVExt_t* wext ); // Optional internal world extents for this object + + // Destroy this canvas. + cmGrRC_t cmGrDestroy( cmGrH_t* hp ); + + // Remove all objects from the root object and restore the canvas to it's default state. + cmGrRC_t cmGrClear( cmGrH_t h ); + + // Get the root object handle + cmGrObjH_t cmGrRootObjH( cmGrH_t h ); + + // Get and set the configuration flags (e.g. kExpandViewGrFl | kSelectHorzGrFl | kSelectVertHorzGrFl ) + unsigned cmGrCfgFlags( cmGrH_t h ); + void cmGrSetCfgFlags( cmGrH_t h, unsigned cfgFlags ); + + // Draw the objects on the canvas. + cmGrRC_t cmGrDraw( cmGrH_t h, cmGrDcH_t dcH ); + + // event flags + enum + { + kMsDownGrFl = 0x0001, + kMsUpGrFl = 0x0002, + kMsMoveGrFl = 0x0004, + kMsWheelGrFl= 0x0008, + kMsDragGrFl = 0x0010, + kMsClickGrFl= 0x0020, + kKeyDnGrFl = 0x0040, + kKeyUpGrFl = 0x0080, + + kMsEvtMask = 0x02f, + kEvtMask = 0x00ff, + + kMsLBtnGrFl = 0x0100, + kMsCBtnGrFl = 0x0200, + kMsRBtnGrFl = 0x0400, + + kShiftKeyGrFl = 0x0800, + kAltKeyGrFl = 0x1000, + kCtlKeyGrFl = 0x2000, + }; + + // Receive a UI event. + bool cmGrEvent( cmGrH_t h, unsigned flags, cmGrKeyCodeId_t key, int x, int y ); + + // Return true if 'h' is valid. + bool cmGrIsValid( cmGrH_t h ); + + // Return the user defined 'id' set in cmGrCreate() + unsigned cmGrId( cmGrH_t h ); + + // Return the last mouse location in root object coordinates. + const cmGrVPt_t* cmGrGlobalPt( cmGrH_t h ); + + // Return the last mouse location in coordinates of the object the mouse was over. + const cmGrVPt_t* cmGrLocalPt( cmGrH_t h ); + + + // The new view extents must fit inside the world extents. + // Return true if the view extents actually changed. + bool cmGrSetViewExtents( cmGrH_t hh, cmGrV_t minx, cmGrV_t miny, cmGrV_t maxx, cmGrV_t maxy ); + bool cmGrSetViewExtentsE(cmGrH_t h, const cmGrVExt_t* ext ); + void cmGrViewExtents( cmGrH_t h, cmGrVExt_t* exts ); + + // View Location + // Return true if the phys extents actually changed. + bool cmGrSetPhysExtents( cmGrH_t hh, int x, int y, int w, int h ); + bool cmGrSetPhysExtentsE(cmGrH_t h, const cmGrPExt_t* ext ); + void cmGrPhysExtents( cmGrH_t h, cmGrPExt_t* exts ); + + // Return some scroll bar values for this canvas. + // tot=world pixels, vis=vis pixels, max=max scroll pos pos=cur scroll pos + // All return values are optional. + void cmGrScrollExtents( cmGrH_t h, cmGrPSz_t* tot, cmGrPSz_t* vis, cmGrPSz_t* max, cmGrPPt_t* pos ); + + // Return true if the view location actually changed. + bool cmGrSetScrollH( cmGrH_t h, int x ); + int cmGrScrollH( cmGrH_t h ); + bool cmGrSetScrollV( cmGrH_t h, int y ); + int cmGrScrollV( cmGrH_t h ); + + // Get the current selection extents. + // If the selection extents are not valid then the function returns false + // and sets the return extents to their null state. + bool cmGrSelectExtents( cmGrH_t h, cmGrVExt_t* vext, cmGrPExt_t* pext ); + + // Both pts are optional + void cmGrSetSelectPoints(cmGrH_t h, const cmGrVPt_t* pt0, const cmGrVPt_t* pt1 ); + void cmGrSelectPoints( cmGrH_t h, cmGrVPt_t* pt0, cmGrVPt_t* pt1 ); + + enum { kZoomInGrFl=0x01, kXAxisGrFl=0x02, kYAxisGrFl=0x04, kSelectGrFl=0x08, kShowAllGrFl=0x10 }; + + // 1) If kSelectGrFl is not set then the center 1/3 of the current view + // becomes the new view. + // 2) If kSelectGrFl is set then the selection area becomes the view. + // 3) If kSelectGrFl is set but no selection area exists then + // option 1) is selected used and using the selection point as center. + void cmGrZoom( cmGrH_t h, unsigned flags ); + + // Synchronize the 'syncGrH' horz. and/or verical, world,view,select extents to + // this gr's extents. Changes to this gr's extents will be automatically + // applied to 'syncGrH'. + // If 'syncGrH' was used in a previous call to this function then flags will + // modify the previously set flags value. + // Clear the kHorzSyncFl and kVertSyncFl to disable the synchronization. + // Set flags to 0 to prevent future sync calls. + enum { kWorldSyncGrFl=0x01, kViewSyncGrFl=0x02, kSelectSyncGrFl=0x04, kHorzSyncGrFl=0x08, kVertSyncGrFl=0x10 }; + void cmGrSetSync( cmGrH_t h, cmGrH_t syncGrH, unsigned flags ); + + + void cmGrReport( cmGrH_t h, cmRpt_t* rpt ); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmGrDevCtx.c b/cmGrDevCtx.c new file mode 100644 index 0000000..1736d92 --- /dev/null +++ b/cmGrDevCtx.c @@ -0,0 +1,530 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmGr.h" +#include "cmGrDevCtx.h" + + +cmGrDcH_t cmGrDcNullHandle = cmSTATIC_NULL_HANDLE; + +// cmGrDcRecd is used to store the state of the +// device context on the cmGrDC_t stack. +typedef struct cmGrDcRecd_str +{ + cmGrColor_t color; + unsigned fontId; + unsigned fontStyle; + unsigned fontSize; + unsigned penWidth; + unsigned penStyle; + + struct cmGrDcRecd_str* next; + struct cmGrDcRecd_str* prev; +} cmGrDcRecd_t; + +typedef struct cmGrDc_str +{ + cmErr_t err; + cmGrDev_t* dd; // device driver used by this context + void* ddArg; // user assigned device driver callback arg + cmGrDcRecd_t* list; // First recd on the stack (not the top). + cmGrDcRecd_t* cur; // Top recd on the stack. + cmGrPExt_t pext; // x,y is offset added to all drawing coordinates + // w,h is size of drawing area +} cmGrDc_t; + +// Note: recd's prior to p->cur are available. +// Recd's after p->cur are on the stack. + +cmGrDc_t* _cmGrDcHandleToPtr( cmGrDcH_t h ) +{ + cmGrDc_t* p = (cmGrDc_t*)h.h; + assert( p != NULL ); + return p; +} + +void _cmGrDcRecdPrint( const cmChar_t* label, const cmGrDcRecd_t* r ) +{ + printf("%s r:%i g:%i b:%i fid:%i fs:0x%x fsz:%i pw:%i ps:0x%x\n", + cmStringNullGuard(label), + cmGrColorToR(r->color),cmGrColorToG(r->color),cmGrColorToB(r->color), + r->fontId,r->fontStyle,r->fontSize,r->penWidth,r->penStyle); +} + +// Make a duplicate of the current record (if it exists) +// and insert it prior to the current record. +// make the new record current. +void _cmGrDcPush( cmGrDc_t* p ) +{ + if( p->cur == NULL ) + { + assert( p->list == NULL ); + cmGrDcRecd_t* r = cmMemAllocZ( cmGrDcRecd_t, 1); + p->dd->get_color( p->ddArg, &r->color ); + r->fontId = p->dd->get_font_family(p->ddArg ); + r->fontSize = p->dd->get_font_size( p->ddArg ); + r->fontStyle = p->dd->get_font_style( p->ddArg ); + r->penWidth = p->dd->get_pen_width( p->ddArg ); + r->penStyle = p->dd->get_pen_style( p->ddArg ); + p->list = r; + p->cur = r; + } + else + { + cmGrDcRecd_t* r = p->cur->prev; + + // if no prev recd exists ... + if( r == NULL ) + { + // .... then allocate one + r = cmMemAllocZ( cmGrDcRecd_t, 1 ); + *r = *p->cur; + p->cur->prev = r; + r->next = p->cur; + } + else + { + // ... otherwise use the prev one + cmGrDcRecd_t* nrp = r->next; + cmGrDcRecd_t* prp = r->prev; + *r = *p->cur; + r->next = nrp; + r->prev = prp; + } + + // make the new recd the cur recd + p->cur = r; + + // if the new recd is the first on the list + // then update the list begin pointer + if( p->cur->prev == NULL ) + p->list = p->cur; + } + + //_cmGrDcRecdPrint("push:", p->cur ); + +} + +cmGrDcRC_t _cmGrDcPop(cmGrDc_t* p ) +{ + if( p->cur==NULL || p->cur->next == NULL ) + return cmErrMsg(&p->err,kStackFaultGrDcRC,"Cannot pop the last context record off the stack."); + + p->cur = p->cur->next; + + p->dd->set_color( p->ddArg, p->cur->color ); + p->dd->set_font_family( p->ddArg, p->cur->fontId ); + p->dd->set_font_size( p->ddArg, p->cur->fontSize ); + p->dd->set_font_style( p->ddArg, p->cur->fontStyle ); + p->dd->set_pen_width( p->ddArg, p->cur->penWidth ); + p->dd->set_pen_style( p->ddArg, p->cur->penStyle ); + + //_cmGrDcRecdPrint("pop:", p->cur ); + + return kOkGrDcRC; +} + +cmGrDcRC_t _cmGrDcDestroy( cmGrDc_t* p ) +{ + cmGrDcRecd_t* rp = p->list; + while( rp!=NULL ) + { + cmGrDcRecd_t* tp = rp->next; + cmMemFree( rp ); + rp = tp; + } + + p->dd->destroy(p->ddArg); + + + cmMemFree(p); + + return kOkGrDcRC; +} + +cmGrDcRC_t cmGrDevCtxCreate( cmCtx_t* ctx, cmGrDcH_t* hp, cmGrDev_t* dd, void* ddArg, int x, int y, int w, int h ) +{ + cmGrDcRC_t rc; + if((rc = cmGrDevCtxDestroy(hp)) != kOkGrDcRC ) + return rc; + + cmGrDc_t* p = cmMemAllocZ(cmGrDc_t,1); + + cmErrSetup(&p->err,&ctx->rpt,"cmGrDevCtx"); + + p->dd = dd; + p->ddArg = ddArg; + + cmGrPExtSet(&p->pext,x,y,w,h); + + if( dd->create(ddArg,w,h) == false ) + { + cmErrMsg(&p->err,kDevDrvFailGrDcRC,"Device driver create failed."); + goto errLabel; + } + + _cmGrDcPush(p); // create the default context + + hp->h = p; + + errLabel: + if(rc != kOkGrDcRC ) + _cmGrDcDestroy(p); + + return rc; +} + +cmGrDcRC_t cmGrDevCtxDestroy( cmGrDcH_t* hp ) +{ + cmGrDcRC_t rc; + + if( hp==NULL || cmGrDevCtxIsValid(*hp)==false ) + return kOkGrDcRC; + + cmGrDc_t* p = _cmGrDcHandleToPtr(*hp); + + if((rc = _cmGrDcDestroy(p)) != kOkGrDcRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmGrDevCtxIsValid( cmGrDcH_t h ) +{ return h.h != NULL; } + +cmGrDcRC_t cmGrDevCtxResize( cmGrDcH_t h, int x, int y, int ww, int hh ) +{ + cmGrDcRC_t rc = kOkGrDcRC; + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + + // store the current drawing context state + _cmGrDcPush(p); + + if( p->dd->create(p->ddArg,ww,hh) == false ) + { + cmErrMsg(&p->err,kDevDrvFailGrDcRC,"Device driver create failed on resize."); + goto errLabel; + } + + cmGrPExtSet(&p->pext,-x,-y,ww,hh); + + errLabel: + // force the current state to be reapplied to the new drawing context + _cmGrDcPop(p); + + return rc; +} + +void cmGrDevCtxSize( cmGrDcH_t h, cmGrPExt_t* pext ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + *pext = p->pext; + pext->loc.x *= -1; + pext->loc.y *= -1; +} + +void cmGrDevCtxBeginDraw( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->begin_draw( p->ddArg ); +} + +void cmGrDevCtxEndDraw( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->end_draw( p->ddArg ); +} + +void cmGrDevCtxDraw( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw( p->ddArg, -p->pext.loc.x, -p->pext.loc.y ); +} + + +void cmGrDcPushCtx( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + _cmGrDcPush(p); +} + +void cmGrDcPopCtx( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + _cmGrDcPop(p); +} + + +unsigned cmGrDcColor( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + return p->cur->color; +} + +void cmGrDcSetColorRgb( cmGrDcH_t h, unsigned char r, unsigned char g, unsigned char b ) +{ + cmGrDcSetColor(h,cmGrRgbToColor(r,g,b)); +} + +void cmGrDcSetColor( cmGrDcH_t h, cmGrColor_t color ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->set_color( p->ddArg, color ); +} + +unsigned cmGrDcFontFamily( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + return p->cur->fontId; +} + +void cmGrDcSetFontFamily( cmGrDcH_t h, unsigned fontId ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->cur->fontId = fontId; + p->dd->set_font_family( p->ddArg, fontId ); +} + +unsigned cmGrDcFontStyle( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + return p->cur->fontStyle; +} + +void cmGrDcSetFontStyle( cmGrDcH_t h, unsigned style ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->cur->fontStyle = style; + p->dd->set_font_style( p->ddArg, style ); +} + +unsigned cmGrDcFontSize( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + return p->cur->fontSize; +} + +void cmGrDcSetFontSize( cmGrDcH_t h, unsigned size ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->cur->fontSize = size; + p->dd->set_font_size( p->ddArg, size ); +} + +unsigned cmGrDcPenWidth( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + return p->cur->penWidth; +} + +void cmGrDcSetPenWidth( cmGrDcH_t h, unsigned width ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->cur->penWidth = width; + p->dd->set_pen_width( p->ddArg, width ); +} + +unsigned cmGrDcPenStyle( cmGrDcH_t h ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + return p->cur->penStyle; +} + +void cmGrDcSetPenStyle( cmGrDcH_t h, unsigned style ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->cur->penStyle = style; + p->dd->set_pen_style( p->ddArg, style ); +} + +void cmGrDcDrawLine( cmGrDcH_t h, int x0, int y0, int x1, int y1 ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_line( p->ddArg, x0+p->pext.loc.x, y0+p->pext.loc.y, x1+p->pext.loc.x, y1+p->pext.loc.y ); +} + +void cmGrDcDrawRect( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_rect( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh ); +} + +void cmGrDcDrawRectPExt( cmGrDcH_t h, const cmGrPExt_t* pext ) +{ cmGrDcDrawRect( h, cmGrPExtL(pext), cmGrPExtT(pext), cmGrPExtW(pext), cmGrPExtH(pext) ); } + +void cmGrDcFillRect( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->fill_rect( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh ); +} + +void cmGrDcDrawEllipse( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_ellipse( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh ); +} + +void cmGrDcFillEllipse( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->fill_ellipse( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh ); +} + +void cmGrDcDrawDiamond( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_diamond( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh ); +} + +void cmGrDcFillDiamond( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->fill_diamond( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh ); +} + +void cmGrDcDrawTriangle( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh, unsigned dirFlag ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_triangle( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh, dirFlag ); +} + +void cmGrDcFillTriangle( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh, unsigned dirFlag ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->fill_triangle( p->ddArg, x+p->pext.loc.x, y+p->pext.loc.y, ww, hh, dirFlag ); +} + + + +void cmGrDcMeasure( cmGrDcH_t h, const cmChar_t* text, cmGrPSz_t* sz ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + if( text == NULL ) + cmGrPSzSet(sz,0,0); + else + { + unsigned ww,hh; + p->dd->measure_text( p->ddArg, text, &ww, &hh ); + sz->w = ww; + sz->h = hh; + } +} + +void cmGrDcDrawText( cmGrDcH_t h, const cmChar_t* text, int x, int y ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_text( p->ddArg, text, x+p->pext.loc.x, y+p->pext.loc.y ); +} + +void cmGrDcDrawTextRot( cmGrDcH_t h, const cmChar_t* text, int x, int y, int angle ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_text_rot( p->ddArg, text, x+p->pext.loc.x, y+p->pext.loc.y, angle ); +} + + +void cmGrDcReadImage( cmGrDcH_t h, unsigned char* a, const cmGrPExt_t* pext ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->read_image( p->ddArg, a, pext->loc.x+p->pext.loc.x, pext->loc.y+p->pext.loc.y, pext->sz.w, pext->sz.h ); +} + +void cmGrDcDrawImage( cmGrDcH_t h, const unsigned char* a, const cmGrPExt_t* pext ) +{ + cmGrDc_t* p = _cmGrDcHandleToPtr(h); + p->dd->draw_image( p->ddArg, a, pext->loc.x+p->pext.loc.x, pext->loc.y+p->pext.loc.y, pext->sz.w, pext->sz.h ); +} + + +void cmGrDcSetFont( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style ) +{ + cmGrDcSetFontFamily(h,fontId); + cmGrDcSetFontSize( h,size); + cmGrDcSetFontStyle( h,style); +} + +void cmGrDcFontSetAndMeasure(cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, cmGrPSz_t* sz ) +{ + cmGrDcSetFont(h,fontId,size,style); + cmGrDcMeasure(h,text,sz); +} + +void cmGrDcDrawTextJustify( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, const cmGrPExt_t* pext, unsigned flags ) +{ + int x = cmGrPExtCtrX(pext); + int y = cmGrPExtCtrY(pext); + + if( cmIsFlag(flags,kNorthJsGrFl) ) + y = cmGrPExtT(pext); + else + if( cmIsFlag(flags,kSouthJsGrFl) ) + y = cmGrPExtB(pext); + + if( cmIsFlag(flags,kEastJsGrFl) ) + x = cmGrPExtR(pext); + else + if( cmIsFlag(flags,kWestJsGrFl) ) + x = cmGrPExtL(pext); + + cmGrDcDrawTextJustifyPt(h,fontId,size,style,text,flags,x,y); +} + +void cmGrDcDrawTextJustifyPt( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, unsigned flags, int xx, int yy ) +{ + cmGrPSz_t sz; + cmGrDcFontSetAndMeasure(h, fontId, size, style, text, &sz ); + + int x,y; + if( cmIsFlag(flags,kRightJsGrFl) ) + x = xx; + else + if( cmIsFlag(flags,kLeftJsGrFl) ) + x = xx - sz.w; + else + x = xx - sz.w/2; + + if( cmIsFlag(flags,kBottomJsGrFl) ) + y = yy; + else + if( cmIsFlag(flags,kTopJsGrFl) ) + y = yy + sz.h; + else + y = yy + sz.h/2; + + cmGrDcDrawText(h, text, x+.5, y+.5 ); + + //cmGrPExt_t pext; + //cmGrDcDrawTextJustifyRect(h, fontId, size, style, text, flags, xx, yy, &pext ); + //cmGrDcDrawRectPExt(h,&pext); +} + +void cmGrDcDrawTextJustifyRect( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, unsigned flags, int xx, int yy, cmGrPExt_t* pext ) +{ + cmGrPSz_t sz; + cmGrDcFontSetAndMeasure(h, fontId, size, style, text, &sz ); + + int x,y; + if( cmIsFlag(flags,kRightJsGrFl) ) + x = xx; + else + if( cmIsFlag(flags,kLeftJsGrFl) ) + x = xx - sz.w; + else + x = xx - sz.w/2; + + if( cmIsFlag(flags,kBottomJsGrFl) ) + y = yy; + else + if( cmIsFlag(flags,kTopJsGrFl) ) + y = yy + sz.h; + else + y = yy + sz.h/2; + + cmGrPExtSet( pext, x, y-sz.h, sz.w+1, sz.h ); +} diff --git a/cmGrDevCtx.h b/cmGrDevCtx.h new file mode 100644 index 0000000..96f3ae0 --- /dev/null +++ b/cmGrDevCtx.h @@ -0,0 +1,196 @@ +#ifndef cmGrDevCtx_h +#define cmGrDevCtx_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkGrDcRC = cmOkRC, + kStackFaultGrDcRC, + kDevDrvFailGrDcRC + }; + + typedef cmRC_t cmGrDcRC_t; + + extern cmGrDcH_t cmGrDcNullHandle; + + enum + { + kSolidLsGrFl = 0x01, + kDashLsGrFl = 0x02, + kDotLsGrFl = 0x04 + }; + + enum + { + kHelveticaFfGrId, + kTimesFfGrId, + kCourierFfGrId, + + kFontFfCnt + }; + + enum + { + kNormalFsGrFl = 0x00, + kBoldFsGrFl = 0x01, + kItalicFsGrFl = 0x02 + }; + + + typedef struct cmGrDev_str + { + // return true on success + bool (*create)( void* arg, unsigned w, unsigned h ); + void (*destroy)( void* arg ); + + void (*begin_draw)( void* arg ); + void (*end_draw)( void* arg ); + void (*draw)( void* arg, int x, int y ); + + + void (*set_color)( void* arg, const cmGrColor_t c ); + void (*get_color)( void* arg, cmGrColor_t* c ); + + // Return false if the 'font' label is not recognized. + void (*set_font_family)(void* arg, unsigned fontId ); + unsigned (*get_font_family)(void* arg ); + + void (*set_font_style)( void* arg, unsigned styleFLags ); + unsigned (*get_font_style)( void* arg ); + + void (*set_font_size)( void* arg, unsigned size ); + unsigned (*get_font_size)( void* arg ); + + void (*set_pen_style)( void* arg, unsigned styleFlags ); + unsigned (*get_pen_style)( void* arg ); + + void (*set_pen_width)( void* arg, unsigned w ); + unsigned (*get_pen_width)( void* arg ); + + void (*draw_line)( void* arg, int x, int y, int x1, int y1 ); + void (*draw_rect)( void* arg, int x, int y, unsigned w, unsigned h ); + void (*fill_rect)( void* arg, int x, int y, unsigned w, unsigned h ); + + // Draw an ellipse, diamond or triangle inside the rectangle formed by l,t,w,h. + void (*draw_ellipse)( void* arg, int x, int y, unsigned w, unsigned h ); + void (*fill_ellipse)( void* arg, int x, int y, unsigned w, unsigned h ); + void (*draw_diamond)( void* arg, int x, int y, unsigned w, unsigned h ); + void (*fill_diamond)( void* arg, int x, int y, unsigned w, unsigned h ); + void (*draw_triangle)( void* arg, int x, int y, unsigned w, unsigned h, unsigned dirFlag ); + void (*fill_triangle)( void* arg, int x, int y, unsigned w, unsigned h, unsigned dirFlag ); + + // x,y identifies the left,lower text edge + void (*draw_text)( void* arg, const char* text, int x, int y ); + void (*draw_text_rot)(void* arg, const char* text, int x, int y, int angle ); + void (*measure_text)( void* arg, const char* text, unsigned* w, unsigned* h ); + + // Fill p[w*h*3] with RGB data. + void (*read_image)( void* arg, unsigned char* p, int x, int y, unsigned w, unsigned h ); + void (*draw_image)( void* arg, const unsigned char* p, int x, int y, unsigned w, unsigned h ); + + } cmGrDev_t; + + cmGrDcRC_t cmGrDevCtxCreate( cmCtx_t* ctx, cmGrDcH_t* hp, cmGrDev_t* dd, void* ddArg, int x, int y, int w, int h ); + cmGrDcRC_t cmGrDevCtxDestroy( cmGrDcH_t* hp ); + bool cmGrDevCtxIsValid( cmGrDcH_t h ); + + cmGrDcRC_t cmGrDevCtxResize( cmGrDcH_t h, int x, int y, int ww, int hh ); + void cmGrDevCtxSize( cmGrDcH_t h, cmGrPExt_t* pext ); + + void cmGrDevCtxBeginDraw( cmGrDcH_t h ); + void cmGrDevCtxEndDraw( cmGrDcH_t h ); + void cmGrDevCtxDraw( cmGrDcH_t h ); + + + void cmGrDcPushCtx( cmGrDcH_t h ); + void cmGrDcPopCtx( cmGrDcH_t h ); + + unsigned cmGrDcColor( cmGrDcH_t h ); + void cmGrDcSetColorRgb( cmGrDcH_t h, unsigned char r, unsigned char g, unsigned char b ); + void cmGrDcSetColor( cmGrDcH_t h, cmGrColor_t color ); + + unsigned cmGrDcFontFamily( cmGrDcH_t h ); + void cmGrDcSetFontFamily( cmGrDcH_t h, unsigned fontId ); + + unsigned cmGrDcFontStyle( cmGrDcH_t h ); + void cmGrDcSetFontStyle( cmGrDcH_t h, unsigned style ); + + unsigned cmGrDcFontSize( cmGrDcH_t h ); + void cmGrDcSetFontSize( cmGrDcH_t h, unsigned size ); + + unsigned cmGrDcPenWidth( cmGrDcH_t h ); + void cmGrDcSetPenWidth( cmGrDcH_t h, unsigned width ); + + unsigned cmGrDcPenStyle( cmGrDcH_t h ); + void cmGrDcSetPenStyle( cmGrDcH_t h, unsigned style ); + + void cmGrDcDrawLine( cmGrDcH_t h, int x, int y, int x1, int y1 ); + // x,y is the upper,left. + void cmGrDcDrawRect( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ); + void cmGrDcDrawRectPExt( cmGrDcH_t h, const cmGrPExt_t* pext ); + + void cmGrDcFillRect( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ); + void cmGrDcDrawEllipse( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ); + void cmGrDcFillEllipse( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ); + + void cmGrDcDrawDiamond( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ); + void cmGrDcFillDiamond( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh ); + + // Set 'dirFlag' to kTopGrFl,kBottomGrFl,kRightGrFl,kLeftGrFl to indicate + // the direction the triangle is pointeed. + void cmGrDcDrawTriangle( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh, unsigned dirFlag ); + void cmGrDcFillTriangle( cmGrDcH_t h, int x, int y, unsigned ww, unsigned hh, unsigned dirFlag ); + + void cmGrDcMeasure( cmGrDcH_t h, const cmChar_t* text, cmGrPSz_t* sz ); + void cmGrDcDrawText( cmGrDcH_t h, const cmChar_t* text, int x, int y ); + void cmGrDcDrawTextRot( cmGrDcH_t h, const cmChar_t* text, int x, int y, int angle ); + + void cmGrDcReadImage( cmGrDcH_t h, unsigned char* p, const cmGrPExt_t* pext ); + void cmGrDcDrawImage( cmGrDcH_t h, const unsigned char* p, const cmGrPExt_t* pext ); + + // + // Composite Functions + // + + void cmGrDcSetFont( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style ); + void cmGrDcFontSetAndMeasure(cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, cmGrPSz_t* sz ); + + enum + { + kLeftJsGrFl = 0x001, + kRightJsGrFl = 0x002, + kTopJsGrFl = 0x004, + kBottomJsGrFl = 0x008, + kHorzCtrJsGrFl = 0x010, + kVertCtrJsGrFl = 0x020, + + kNorthJsGrFl = 0x040, + kEastJsGrFl = 0x080, + kSouthJsGrFl = 0x100, + kWestJsGrFl = 0x200 + }; + + // Use compass (NSEW) flags to select the draw point. Defaults to center for both dir's. + // Use TBLF flags to select the text justification relative to the point. + // In effect the TBLF flags select the corner of the text to place at the location of + // the point selected by the NSEW flags. + // If neither NS flag is set then the vertical point is set to the vertical center. + // If neither EW flags is set then the horizontal point is set to the horzontal center. + void cmGrDcDrawTextJustify( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, const cmGrPExt_t* pext, unsigned flags ); + + // Use LBLF to set the justification - the text corner to match to the given point. + // If neither TL flag is given then the point is matched to the vertical center of the text. + // If neither RL flag is given then the point is matched to the horizontal center of the text. + void cmGrDcDrawTextJustifyPt( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, unsigned flags, int x, int y ); + + // Return the rectangle around the text but do not display the text. + void cmGrDcDrawTextJustifyRect( cmGrDcH_t h, unsigned fontId, unsigned size, unsigned style, const cmChar_t* text, unsigned flags, int x, int y, cmGrPExt_t* pext ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmGrPage.c b/cmGrPage.c new file mode 100644 index 0000000..8383266 --- /dev/null +++ b/cmGrPage.c @@ -0,0 +1,1471 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmErr.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmGr.h" +#include "cmGrDevCtx.h" +#include "cmGrPlot.h" +#include "cmGrPage.h" +#include "cmVectOpsTemplateMain.h" + + +enum +{ + kHashLength = 6, + kHashCharCnt = 10, + kMaxHashCnt = 10 +}; + +enum +{ + kNotValidFl = 0x01, + kDirtyFl = 0x02, // set if the layout is dirty + kFocusFl = 0x04 +}; + +typedef struct +{ + cmGrPPt_t xy0; // + cmGrPPt_t xy1; // xy1 is on iext +} cmGrPgHash_t; + +struct cmGrPgVw_str; +struct cmGrPgAxis_str; +struct cmGrPg_str; + +typedef struct cmGrPgLabelFunc_str +{ + unsigned id; + cmGrLabelFunc_t func; + void* arg; + cmChar_t* label; + struct cmGrPgLabelFunc_str* link; +} cmGrPgLabelFunc_t; + +typedef struct cmGrPgAxis_str +{ + struct cmGrPgVw_str* vp; + unsigned flags; // kHashMarkGrFl | kHashLabelGrFl + unsigned hashCnt; + unsigned hashLength; + cmChar_t* title; + + cmGrPgHash_t* hash; + + unsigned maxHashCnt; + + cmGrPPt_t titlePt; // position of the axis title + unsigned titleFontId; // title font id + unsigned titleFontStyle; // title font style + unsigned titleFontSize; // title font size + + cmGrPPt_t labelPt; // x = l/r label pos. and y=t/b label pos. + unsigned labelFontId; // label font id + unsigned labelFontStyle; // label font style + unsigned labelFontSize; // label font size + + cmGrPgLabelFunc_t* func; +} cmGrPgAxis_t; + +typedef struct cmGrPgVw_str +{ + struct cmGrPg_str* p; + cmGrH_t grH; + unsigned ri; // row index + unsigned ci; // column index + cmGrPExt_t pext; // page around outside of view + cmGrPExt_t iext; // view physical extents + cmGrPgAxis_t axis[ kAxisGrCnt ]; // + cmGrPPt_t titlePt; // lower/center position of the view title + cmChar_t* title; // view title (upper center) + unsigned fontId; // title font id + unsigned fontStyle; // title font style + unsigned fontSize; // title font size + + cmGrPgLabelFunc_t* xfunc; + cmGrPgLabelFunc_t* yfunc; +} cmGrPgVw_t; + +typedef struct cmGrPg_str +{ + cmCtx_t ctx; + cmErr_t err; + cmGrCbFunc_t cbFunc; + void* cbArg; + unsigned flags; // kNotValidFl + cmGrPExt_t pext; // outer border around all views + unsigned rn; // count of rows + unsigned cn; // count of columns + unsigned vn; // view count (rn*cn) + double* rpropV; // sum(rprop[rn]) == 1.0 pgaction of r.w() assigned to each row + double* cpropV; // sum(cprop[cn]) == 1.0 pgaction of r.h() assigned to each row + cmGrPgVw_t* viewV; // viewV[vn] + unsigned focusIdx; // focused view index + + cmGrPPt_t titlePt; // + cmChar_t* title; // page title + unsigned fontId; // title font id + unsigned fontStyle; // title font style + unsigned fontSize; // title font size + + unsigned labelFuncId; // next page label function id + cmGrPgLabelFunc_t* funcs; // linked list of value to string translation functions + +} cmGrPg_t; + +cmGrPgH_t cmGrPgNullHandle = cmSTATIC_NULL_HANDLE; +cmGrVwH_t cmGrVwNullHandle = cmSTATIC_NULL_HANDLE; +cmGrAxH_t cmGrAxNullHandle = cmSTATIC_NULL_HANDLE; + +cmGrPg_t* _cmGrPgHandleToPtr( cmGrPgH_t h ) +{ + cmGrPg_t* p = (cmGrPg_t*)h.h; + assert( p!=NULL); + return p; +} + +cmGrPgVw_t* _cmGrPgVwHandleToPtr( cmGrVwH_t h ) +{ + cmGrPgVw_t* p = (cmGrPgVw_t*)h.h; + assert( p!=NULL); + return p; +} + +cmGrPgAxis_t* _cmGrPgAxisHandleToPtr( cmGrAxH_t h ) +{ + cmGrPgAxis_t* p = (cmGrPgAxis_t*)h.h; + assert( p!=NULL); + return p; +} + + +void _cmGrPgVwAxisDestroy( cmGrPgAxis_t* ap ) +{ + ap->func = NULL; + + cmMemFree(ap->hash); + cmMemFree(ap->title); +} + +void _cmGrPgVwDestroy( cmGrPgVw_t* vp ) +{ + unsigned i; + + cmGrDestroy( &vp->grH ); + + cmMemFree(vp->title); + for(i=0; iaxis + i ); +} + +void _cmGrPgFinal( cmGrPg_t* p ) +{ + unsigned i; + for(i=0; ivn; ++i) + _cmGrPgVwDestroy( p->viewV + i ); + + cmGrPgLabelFunc_t* lfp = p->funcs; + while( lfp != NULL ) + { + cmGrPgLabelFunc_t* np = lfp->link; + cmMemFree(lfp->label); + cmMemFree(lfp); + lfp = np; + } + + p->funcs = NULL; + + cmMemFree(p->viewV); + cmMemFree(p->title); + cmMemFree(p->rpropV); + cmMemFree(p->cpropV); +} + +cmGrRC_t _cmGrPgDestroy( cmGrPg_t* p ) +{ + cmGrRC_t rc = kOkGrRC; + _cmGrPgFinal(p); + cmMemFree(p); + return rc; +} + +unsigned _cmGrPgGrHandleToVwIndex( cmGrPg_t* p, cmGrH_t grH ) +{ + unsigned i; + for(i=0; ivn; ++i) + if( cmHandlesAreEqual(p->viewV[i].grH,grH) ) + return i; + + return cmInvalidIdx; +} + +void _cmGrPageCallback( void* arg, cmGrH_t grH, cmGrCbId_t id, unsigned eventFlags, cmGrKeyCodeId_t keycode ) +{ + cmGrPg_t* p = (cmGrPg_t*)arg; + + switch(id) + { + case kCreateCbGrId: + case kDestroyCbGrId: + case kLocalPtCbGrId: + case kGlobalPtCbGrId: + case kPhysExtCbGrId: + case kViewExtCbGrId: + case kSelectExtCbGrId: + if( p->cbFunc != NULL ) + p->cbFunc(p->cbArg,grH,id,eventFlags,keycode); + break; + + case kKeyUpCbGrId: + case kKeyDnCbGrId: + { + cmGrVwH_t vwH; + cmGrPgH_t pgH; + pgH.h = p; + if(cmGrViewIsValid(vwH =cmGrPageFocusedView(pgH))) + if( p->cbFunc != NULL ) + p->cbFunc(p->cbArg,cmGrViewGrHandle(vwH),id,eventFlags,keycode); + + } + break; + + case kFocusCbGrId: + { + unsigned i; + if((i = _cmGrPgGrHandleToVwIndex(p,grH)) != cmInvalidIdx ) + { + cmGrPgH_t h; + h.h = p; + + // if the focus is changing + if( i != p->focusIdx ) + { + // inform the prev view that it is losing focus + if( p->focusIdx != cmInvalidIdx ) + cmGrPageViewFocus(h,p->focusIdx,false); + + // inform the new view that it is gaining focus + cmGrPageViewFocus(h,i,true); + + if( p->cbFunc != NULL ) + p->cbFunc(p->cbArg,grH,id,eventFlags,keycode); + } + } + } + break; + + default: + { assert(0); } + } + + +} + + + +cmGrRC_t cmGrPageCreate( cmCtx_t* ctx, cmGrPgH_t* hp, cmGrCbFunc_t cbFunc, void* cbArg ) +{ + cmGrRC_t rc; + if((rc = cmGrPageDestroy(hp)) != kOkGrRC ) + return rc; + + cmGrPg_t* p = cmMemAllocZ(cmGrPg_t,1); + + cmErrSetup(&p->err,&ctx->rpt,"cmGrPage"); + p->cbFunc = cbFunc; + p->cbArg = cbArg; + p->ctx = *ctx; + hp->h = p; + + if(rc != kOkGrRC ) + _cmGrPgDestroy(p); + return rc; +} + +cmGrRC_t cmGrPageDestroy( cmGrPgH_t* hp ) +{ + cmGrRC_t rc; + if(hp==NULL || cmGrPageIsValid(*hp) == false ) + return kOkGrRC; + + cmGrPg_t* p = _cmGrPgHandleToPtr(*hp); + + + if((rc = _cmGrPgDestroy(p)) != kOkGrRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmGrPageIsValid( cmGrPgH_t h ) +{ return h.h != NULL; } + +cmGrRC_t cmGrPageClear( cmGrPgH_t h ) +{ + cmGrRC_t rc = kOkGrRC; + unsigned i; + for(i=0; ipext, and the row/col proportion vectors +// This function calculates the outer page around each view. +// It must be called whenever the size of the window holding the page changes. +bool _cmGrPgLayoutVwPosition( cmGrPg_t* p ) +{ + enum { kVBord=4, kHBord=4 }; + + // verify that the page state is sane + if( p->rn==0 || p->cn==0 || p->pext.sz.h==0 || p->pext.sz.w==0) + goto errLabel; + + else + { + unsigned i,j; + int x=p->pext.loc.x; + int y=p->pext.loc.y; + + // calculate the total page width/height w/o internal borders + int h = p->pext.sz.h - ((p->rn - 1) * kVBord); + int w = p->pext.sz.w - ((p->cn - 1) * kHBord); + + // verify that the page area exists + if( h<=0 || w<=0 ) + goto errLabel; + + // force the row/col proportion vectors to sum to 1.0 + cmVOD_NormalizeProbability(p->rpropV,p->rn); + cmVOD_NormalizeProbability(p->cpropV,p->cn); + + // determine the row height + for(i=0; irn; ++i) + { + unsigned hh = (unsigned)floor(p->rpropV[i] * h); + + if( hh == 0 ) + goto errLabel; + + for(j=0; jvn; ++j) + if( p->viewV[j].ri == i ) + { + p->viewV[j].pext.loc.y = y; + p->viewV[j].pext.sz.h = hh; + } + + y += hh + kVBord; + } + + // determine the column width + for(i=0; icn; ++i) + { + unsigned ww = (unsigned)floor(p->cpropV[i] * w); + + if( ww == 0 ) + goto errLabel; + + for(j=0; jvn; ++j) + if( p->viewV[j].ci == i ) + { + p->viewV[j].pext.loc.x = x; + p->viewV[j].pext.sz.w = ww; + } + + x += ww + kHBord; + } + + p->flags = cmClrFlag(p->flags,kNotValidFl); + + + return true; + } + + errLabel: + p->flags = cmSetFlag(p->flags,kNotValidFl); + + return false; +} + +// Calculate the layout for a given view. +// txW = max hash label width on x-axis +// txH = hash label height on x-axis +void _cmGrPgLayoutView( cmGrPg_t* p, cmGrPgVw_t* vp, cmGrDcH_t dcH ) +{ + enum { kVBord=2, kHBord=2 }; + + int x0 = vp->pext.loc.x + kHBord; + int y0 = vp->pext.loc.y + kVBord; + int w = vp->pext.sz.w - 2*kHBord; + int h = vp->pext.sz.h - 2*kVBord; + + int y = y0; + int x = x0; + + cmGrPSz_t sz; + + + // Create a negative string with a repeating decimal to simulate an arbitrary hash label + // Use the measurements pgom this string to compute the geometry of the view layouts. + char label[ kHashCharCnt + 1]; + snprintf(label,kHashCharCnt,"%f",-4.0/3.0); + label[kHashCharCnt] = 0; + + int mlx,mrx,mty,mby; + int i; + + // add vertical space for the view title + if( vp->title != NULL ) + { + cmGrDcFontSetAndMeasure(dcH, vp->fontId, vp->fontSize, vp->fontStyle, vp->title, &sz ); + y += sz.h; + vp->titlePt.y = y; + vp->titlePt.x = vp->pext.loc.x + vp->pext.sz.w/2; + y += 2; + } + + cmGrPgAxis_t* ap = vp->axis + kTopGrIdx; + + // add vertical space for the top axis title + if( ap->title != NULL ) + { + cmGrDcFontSetAndMeasure(dcH, ap->titleFontId, ap->titleFontSize, ap->titleFontStyle, ap->title, &sz ); + y += sz.h; + ap->titlePt.y = y; + y += 4; + } + + // add vertical space for top axis hash labels + if( cmIsFlag(ap->flags,kHashLabelGrFl ) ) + { + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + y += sz.h; + ap->labelPt.y = y; + y += 1; + } + + // calc vertical space for top axis hash marks + if( cmIsFlag(ap->flags,kHashMarkGrFl ) ) + { + mty = y; + y += ap->hashLength + 1; + } + + // set the internal pext vertical location + vp->iext.loc.y = y; + + ap = vp->axis + kBottomGrIdx; + y = y0 + h - 1 - kVBord; + + // subtract vertical space for bottom axis title + if( ap->title != NULL ) + { + cmGrDcFontSetAndMeasure(dcH, ap->titleFontId, ap->titleFontSize, ap->titleFontStyle, ap->title, &sz ); + ap->titlePt.y = y; + y -= sz.h + 4; + } + + // calc vertical space for bottom axis hash label + if( cmIsFlag(ap->flags,kHashLabelGrFl) ) + { + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + ap->labelPt.y = y; + y -= sz.h + 2; + } + + // calc vertical space for bottom axis hash mark + if( cmIsFlag(ap->flags,kHashMarkGrFl) ) + { + mby = y; + y -= ap->hashLength + 1; + } + + // set the internal pext height + vp->iext.sz.h = y - vp->iext.loc.y + 1; + + + ap = vp->axis + kLeftGrIdx; + + // add horizontal space for the left axis title + if( ap->title != NULL ) + { + cmGrDcFontSetAndMeasure(dcH, ap->titleFontId, ap->titleFontSize, ap->titleFontStyle, ap->title, &sz ); + + x += sz.h; // use txH as approx of char. width for rotated title + ap->titlePt.x = x; + x += 4; + } + + // add horizontal space for left axis hash labels + if( cmIsFlag(ap->flags,kHashLabelGrFl ) ) + { + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + + ap->labelPt.x = x; + x += sz.w + 3; + } + + // calc horizontal space for left axis hash marks + if( cmIsFlag(ap->flags,kHashMarkGrFl ) ) + { + mlx = x; + x += ap->hashLength + 1; + } + + // set the internal pext horz location + vp->iext.loc.x = x; + + ap = vp->axis + kRightGrIdx; + x = x0 + w - 1 - kVBord; + + // subtract horizontal space for right axis title + if( ap->title != NULL ) + { + cmGrDcFontSetAndMeasure(dcH, ap->titleFontId, ap->titleFontSize, ap->titleFontStyle, ap->title, &sz ); + + ap->titlePt.x = x; + x -= sz.h + 2; // use txH as approx of char. width for rotated title + } + + // calc horizontal space for right axis hash label + if( cmIsFlag(ap->flags,kHashLabelGrFl) ) + { + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + + x -= sz.w + 1; + ap->labelPt.x = x; + x -= 3; + } + + // calc horizontal space for right axis hash mark + if( cmIsFlag(ap->flags,kHashMarkGrFl) ) + { + mrx = x; + x -= ap->hashLength + 1; + } + + // set the internal pext width + vp->iext.sz.w = x - vp->iext.loc.x + 1; + + + // calc the top hash count and alloc hash array + ap = vp->axis + kTopGrIdx; + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + ap->hashCnt = cmMin(ap->maxHashCnt,(vp->iext.sz.w + sz.w) / sz.w); + ap->hash = cmMemResizeZ( cmGrPgHash_t, ap->hash, vp->axis[ kTopGrIdx ].hashCnt ); + + // calc the bottom hash count and alloc hash array + ap = vp->axis + kBottomGrIdx; + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + ap->hashCnt = cmMin(ap->maxHashCnt,(vp->iext.sz.w + sz.w) / sz.w); + ap->hash = cmMemResizeZ( cmGrPgHash_t, ap->hash, vp->axis[ kBottomGrIdx ].hashCnt ); + + // calc the top hash mark line beg/end + for(i=0; i< vp->axis[ kTopGrIdx ].hashCnt; ++i) + { + int x0 = round(vp->iext.loc.x - 1 + ((i * (vp->iext.sz.w + 1.0))/(vp->axis[ kTopGrIdx ].hashCnt-1))); + + vp->axis[kTopGrIdx].hash[i].xy0.y = mty; + vp->axis[kTopGrIdx].hash[i].xy0.x = x0; + vp->axis[kTopGrIdx].hash[i].xy1.y = vp->iext.loc.y - 1; + vp->axis[kTopGrIdx].hash[i].xy1.x = x0; + } + + // calc the bottom hash mark line beg/end + for(i=0; i< vp->axis[ kBottomGrIdx ].hashCnt; ++i) + { + int x0 = round(vp->iext.loc.x - 1 + ((i * (vp->iext.sz.w + 1.0))/(vp->axis[ kBottomGrIdx ].hashCnt-1))); + + vp->axis[kBottomGrIdx].hash[i].xy0.y = mby; + vp->axis[kBottomGrIdx].hash[i].xy0.x = x0; + vp->axis[kBottomGrIdx].hash[i].xy1.y = cmGrPExtB(&vp->iext) + 1; + vp->axis[kBottomGrIdx].hash[i].xy1.x = x0; + } + + // calc the left hash count and alloc hash array + ap = vp->axis + kLeftGrIdx; + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + ap->hashCnt = cmMin(ap->maxHashCnt,(vp->iext.sz.h + sz.h) / sz.h); + ap->hash = cmMemResizeZ( cmGrPgHash_t, ap->hash, vp->axis[ kLeftGrIdx ].hashCnt ); + + // calc right hash count and alloc hash array + ap = vp->axis + kRightGrIdx; + cmGrDcFontSetAndMeasure(dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle, label, &sz ); + ap->hashCnt = cmMin(ap->maxHashCnt,(vp->iext.sz.h + sz.h) / sz.h); + ap->hash = cmMemResizeZ( cmGrPgHash_t, ap->hash, vp->axis[ kRightGrIdx ].hashCnt ); + + + // calc the left hash mark beg/end + for(i=0; i< vp->axis[ kLeftGrIdx ].hashCnt; ++i) + { + int y0 = round(vp->iext.loc.y - 1 + ((i * (vp->iext.sz.h + 1.0))/(vp->axis[ kLeftGrIdx ].hashCnt-1))); + + vp->axis[kLeftGrIdx].hash[i].xy0.x = mlx; + vp->axis[kLeftGrIdx].hash[i].xy0.y = y0; + vp->axis[kLeftGrIdx].hash[i].xy1.x = vp->iext.loc.x - 1; + vp->axis[kLeftGrIdx].hash[i].xy1.y = y0; + } + + // calc the right hash mark beg/end + for(i=0; i< vp->axis[ kRightGrIdx ].hashCnt; ++i) + { + int y0 = round(vp->iext.loc.y - 1 + ((i * (vp->iext.sz.h + 1.0))/(vp->axis[ kRightGrIdx ].hashCnt-1))); + + vp->axis[kRightGrIdx].hash[i].xy0.x = mrx; + vp->axis[kRightGrIdx].hash[i].xy0.y = y0; + vp->axis[kRightGrIdx].hash[i].xy1.x = cmGrPExtR(&vp->iext) + 1; + vp->axis[kRightGrIdx].hash[i].xy1.y = y0; + } + + // change the location of the view to match vp->iext + if( cmGrIsValid( vp->grH ) ) + cmGrSetPhysExtentsE( vp->grH, &vp->iext ); + +} + +void _cmGrPageLayout( cmGrPg_t* p, cmGrDcH_t dcH ) +{ + unsigned i; + + if( cmIsNotFlag(p->flags,kDirtyFl) ) + return; + + cmGrDcSetFontFamily(dcH,kHelveticaFfGrId); + cmGrDcSetFontSize(dcH,10); + + // Create a negative string with a repeating decimal to simulate an arbitrary hash label + // Use the measurements pgom this string to compute the geometry of the view layouts. + char label[ kHashCharCnt + 1]; + cmGrPSz_t sz; + snprintf(label,kHashCharCnt,"%f",-4.0/3.0); + label[kHashCharCnt] = 0; + cmGrDcMeasure(dcH,label,&sz); + + _cmGrPgLayoutVwPosition(p); + + for(i=0; ivn; ++i) + { + cmGrPgVw_t* vp = p->viewV + i; + + _cmGrPgLayoutView(p, vp, dcH ); + } + + p->flags = cmClrFlag(p->flags,kDirtyFl); +} + +cmGrRC_t cmGrPageInit( cmGrPgH_t h, const cmGrPExt_t* r, unsigned rn, unsigned cn, cmGrDcH_t dcH ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + unsigned i; + + _cmGrPgFinal(p); + + if( rn*cn > 0 ) + { + p->pext = *r; + p->rn = rn; + p->cn = cn; + p->vn = rn*cn; + p->rpropV = cmMemAllocZ(double, rn); + p->cpropV = cmMemAllocZ(double, cn); + p->viewV = cmMemAllocZ(cmGrPgVw_t, p->vn); + p->flags = kDirtyFl; + p->focusIdx = cmInvalidIdx; + p->fontId = kHelveticaFfGrId; + p->fontStyle = kNormalFsGrFl; + p->fontSize = 16; + + // setup the view defaults + for(i=0; ivn; ++i) + { + cmGrPgVw_t* vp = p->viewV + i; + + vp->p = p; + vp->ri = i % p->rn; + vp->ci = i / p->rn; + vp->fontId = kHelveticaFfGrId; + vp->fontStyle = kNormalFsGrFl; + vp->fontSize = 14; + + unsigned j; + for(j=0; jaxis + j; + + ap->vp = p->viewV + i; + ap->maxHashCnt = kMaxHashCnt; + ap->hashLength = kHashLength; + ap->flags = kHashMarkGrFl | kHashLabelGrFl; + ap->titleFontId = kHelveticaFfGrId; + ap->titleFontStyle = kNormalFsGrFl; + ap->titleFontSize = 10; + ap->labelFontId = kHelveticaFfGrId; + ap->labelFontStyle = kNormalFsGrFl; + ap->labelFontSize = 10; + } + + } + + // setup the default row proportions + for(i=0; irn; ++i) + p->rpropV[i] = 1.0/p->rn; + + // setup the default col proportions + for(i=0; icn; ++i) + p->cpropV[i] = 1.0/p->cn; + + // _cmGrPgLayoutVw() needs to be called. + p->flags = cmSetFlag(p->flags,kDirtyFl); + + // layout the page + _cmGrPageLayout(p, dcH ); + + // notify the application that views have been created + for(i=0; iviewV + i; + + // Set the 'id' assoc'd with this views cmGrH_t handle to 'i'. + // This will allow the grH to return the index of the plot. + if( cmGrCreate(&p->ctx,&vp->grH,i,kExpandViewGrFl,_cmGrPageCallback,p,NULL) == kOkGrRC ) + { + //if( p->rspdr != NULL ) + // p->rspdr->on_view_create( p->rspdrArg, i ); + } + } + } + return kOkGrRC; +} + + + +cmGrRC_t cmGrPageResize( cmGrPgH_t h, const cmGrPExt_t* r, cmGrDcH_t dcH ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + p->pext = *r; + + // _cmGrPgLayoutVw() needs to be called. + p->flags = cmSetFlag(p->flags,kDirtyFl); + + // layout the page + _cmGrPageLayout(p, dcH ); + + return kOkGrRC; +} + +void cmGrPageRect( cmGrPgH_t h, cmGrPExt_t* r ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + *r = p->pext; +} + +unsigned cmGrPageViewCount( cmGrPgH_t h ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + return p->vn; +} + +void _cmGrViewSetTitle(cmGrPgVw_t* vp, const cmChar_t* title ) +{ + if( title == vp->title || (title != NULL && vp->title!=NULL && strcmp(title,vp->title)==0 )) + return; + + cmMemPtrFree(&vp->title); + if( title != NULL ) + { + assert( vp->title == NULL ); + vp->title = cmMemAllocStr(title); + } + + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); +} + +void _cmGrAxisSetTitle( cmGrPgAxis_t* ap, const cmChar_t* title ) +{ + if( title == ap->title || (title != NULL && ap->title!=NULL && strcmp(title,ap->title)==0 )) + return; + + cmMemPtrFree(&ap->title); + if( title != NULL ) + { + assert( ap->title == NULL ); + ap->title = cmMemAllocStr(title); + } + + ap->vp->p->flags = cmSetFlag(ap->vp->p->flags, kDirtyFl); + +} + + +void _cmGrPgDrawHashMarks( cmGrPg_t* p, cmGrPgVw_t* vp, cmGrDcH_t dcH, unsigned lineColor ) +{ + int i,j; + cmGrDcSetColor(dcH, lineColor ); + cmGrDcSetPenWidth( dcH, 1 ); + + for(j=0; jaxis + j; + + if( cmIsFlag(ap->flags, kHashMarkGrFl) ) + for(i=0; ihashCnt; ++i) + { + cmGrPgHash_t* hp = ap->hash + i; + cmGrDcDrawLine(dcH, hp->xy0.x, hp->xy0.y, hp->xy1.x, hp->xy1.y); + } + } + + // draw border 1 pixel outside of iext + cmGrDcDrawRect(dcH,vp->iext.loc.x-1,vp->iext.loc.y-1,vp->iext.sz.w+2,vp->iext.sz.h+2); + + //printf("pgm: x:%i y:%i\n", vp->iext.loc.x, vp->iext.loc.y); + + // draw the view border + //cmGrDcDrawRect(dcH,vp->pext.loc.x,vp->pext.loc.y,vp->pext.sz.w,vp->pext.sz.h); + +} + +void _cmGrPgHashValueToLabel( cmGrPgAxis_t* ap, cmChar_t* label, unsigned labelCharCnt, cmGrV_t value ) +{ + if( ap->func == NULL ) + snprintf(label,labelCharCnt,"%f",value); + else + { + ap->func->func( ap->func->arg, label, labelCharCnt, value ); + } +} + +void _cmGrPgDrawHashLabels( cmGrPg_t* p, cmGrPgVw_t* vp, cmGrDcH_t dcH, unsigned textColor ) +{ + int i; + cmGrVExt_t vext; + char s[ kHashCharCnt+1 ]; + s[kHashCharCnt]=0; + + cmGrViewExtents( vp->grH, &vext ); + //cmGrVExtPrint("hash:",&vext); + + cmGrDcSetColor(dcH, textColor ); + + cmGrPgAxis_t* ap = vp->axis + kLeftGrIdx; + cmGrDcSetFont( dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle ); + + // draw the left axis hash labels + if( cmIsFlag(ap->flags, kHashLabelGrFl ) ) + { + for(i=0; iaxis[ kLeftGrIdx ].hashCnt; ++i) + { + cmGrPSz_t sz; + double v = vext.loc.y + vext.sz.h - (i * vext.sz.h / (ap->hashCnt-1) ); + _cmGrPgHashValueToLabel(ap,s,kHashCharCnt,v); + cmGrDcMeasure(dcH,s,&sz); + + int y = ap->hash[i].xy0.y; + cmGrDcDrawText(dcH, s, ap->labelPt.x, y + (sz.h/2) ); + } + } + + ap = vp->axis + kRightGrIdx; + cmGrDcSetFont( dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle ); + + // draw the right axis hash labels + if( cmIsFlag(ap->flags, kHashLabelGrFl )) + { + for(i=0; ihashCnt; ++i) + { + cmGrPSz_t sz; + double v = vext.loc.y + vext.sz.h - (i * vext.sz.h / (ap->hashCnt-1) ); + _cmGrPgHashValueToLabel(ap,s,kHashCharCnt,v); + cmGrDcMeasure(dcH,s,&sz); + + int y = ap->hash[i].xy0.y; + cmGrDcDrawText(dcH, s, ap->labelPt.x, y + (sz.h/2)); + } + } + + + // draw the top axis hash labels + ap = vp->axis + kTopGrIdx; + cmGrDcSetFont( dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle ); + + if( cmIsFlag(ap->flags, kHashLabelGrFl ) ) + { + for(i=0; ihashCnt; ++i) + { + cmGrPSz_t sz; + double v = vext.loc.x + (i * vext.sz.w / (ap->hashCnt-1)); + _cmGrPgHashValueToLabel(ap,s,kHashCharCnt,v); + cmGrDcMeasure(dcH,s,&sz); + + cmGrDcDrawText(dcH, s, ap->hash[i].xy0.x - sz.w/2, ap->labelPt.y ); + } + } + + // draw the bottom axis hash labels + ap = vp->axis + kBottomGrIdx; + cmGrDcSetFont( dcH, ap->labelFontId, ap->labelFontSize, ap->labelFontStyle ); + + if( cmIsFlag(ap->flags, kHashLabelGrFl ) ) + { + for(i=0; ihashCnt; ++i) + { + cmGrPSz_t sz; + double v = vext.loc.x + (i * vext.sz.w / (ap->hashCnt-1)); + _cmGrPgHashValueToLabel(ap,s,kHashCharCnt,v); + cmGrDcMeasure(dcH,s,&sz); + + cmGrDcDrawText(dcH, s, ap->hash[i].xy0.x - sz.w/2, ap->labelPt.y ); + } + } + +} + +void _cmGrPgDrawAxisTitles( cmGrPg_t* p, cmGrPgVw_t* vp, cmGrDcH_t dcH ) +{ + unsigned i; + for(i=0; iaxis + i; + + if( ap->title != NULL ) + { + cmGrPSz_t sz; + cmGrDcFontSetAndMeasure( dcH, ap->titleFontId, ap->titleFontSize, ap->titleFontStyle, ap->title, &sz ); + + if( i==kBottomGrIdx || i==kTopGrIdx ) + { + int x = vp->iext.loc.x + (vp->iext.sz.w/2) - (sz.w/2); + cmGrDcDrawText( dcH, ap->title, x, ap->titlePt.y ); + } + + if( i==kLeftGrIdx || i==kRightGrIdx ) + { + int y = vp->iext.loc.y + (vp->iext.sz.h/2) + (sz.w/2); + cmGrDcDrawTextRot( dcH, ap->title, ap->titlePt.x, y, 90 ); + } + } + } +} + +void cmGrPageViewFocus( cmGrPgH_t h, unsigned vwIdx, bool enableFl ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + assert( vwIdx < p->vn ); + if( enableFl ) + p->focusIdx = vwIdx; + else + { + if( p->focusIdx == vwIdx ) + p->focusIdx = cmInvalidId; + } +} + +cmGrVwH_t cmGrPageFocusedView( cmGrPgH_t h ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrVwH_t vwH = cmGrVwNullHandle; + + if( p->focusIdx != cmInvalidIdx ) + vwH.h = p->viewV + p->focusIdx; + + return vwH; +} + + +void cmGrPageLayout( cmGrPgH_t h, cmGrDcH_t dcH ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrDcPushCtx(dcH); + _cmGrPageLayout(p,dcH); + cmGrDcPopCtx(dcH); +} + +void cmGrPageDraw( cmGrPgH_t h, cmGrDcH_t dcH ) +{ + unsigned i; + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + + cmGrDcPushCtx(dcH); + + _cmGrPageLayout(p,dcH); + + cmGrDcSetColor(dcH,kBlackGrId); + + // for each view + for(i=0; ivn; ++i) + { + cmGrPgVw_t* vp = p->viewV + i; + + unsigned lineColor = p->focusIdx==i ? kRedGrId : kBlackGrId; + _cmGrPgDrawHashMarks( p, vp, dcH, lineColor ); + _cmGrPgDrawHashLabels( p, vp, dcH, kBlackGrId); + _cmGrPgDrawAxisTitles( p, vp, dcH ); + + if( vp->title != NULL ) + { + cmGrPSz_t sz; + cmGrDcFontSetAndMeasure(dcH,vp->fontId,vp->fontSize,vp->fontStyle,vp->title,&sz); + cmGrDcDrawText(dcH,vp->title, vp->titlePt.x - sz.w/2, vp->titlePt.y ); + } + + } + + cmGrDcPopCtx(dcH); + + +} + +cmGrPgLabelFunc_t* _cmGrPageLabelIndexToRecd( cmGrPg_t* p, unsigned idx ) +{ + cmGrPgLabelFunc_t* lfp = p->funcs; + unsigned i; + for(i=0; ilink; + + return lfp; +} + +cmGrPgLabelFunc_t* _cmGrPageLabelIdToRecd( cmGrPg_t* p, unsigned id ) +{ + cmGrPgLabelFunc_t* lfp = p->funcs; + for(; lfp!=NULL; lfp=lfp->link) + if( lfp->id == id ) + return lfp; + + return lfp; +} + +unsigned cmGrPageLabelFuncRegister( cmGrPgH_t h, cmGrLabelFunc_t func, void* arg, const cmChar_t* label ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = cmMemAllocZ(cmGrPgLabelFunc_t,1); + + lfp->id = p->labelFuncId++; + lfp->func = func; + lfp->arg = arg; + lfp->label = cmMemAllocStr(label); + lfp->link = p->funcs; + p->funcs = lfp; + return lfp->id; +} + +unsigned cmGrPageLabelFuncCount( cmGrPgH_t h ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = p->funcs; + unsigned n = 0; + + for(; lfp != NULL; lfp=lfp->link ) + ++n; + return n; +} + +unsigned cmGrPageLabelFuncIndexToId( cmGrPgH_t h, unsigned index ) +{ + cmGrPgLabelFunc_t* lfp; + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + + if((lfp = _cmGrPageLabelIndexToRecd(p,index)) == NULL ) + return cmInvalidId; + return lfp->id; +} + +unsigned cmGrPageLabelFuncLabelToId( cmGrPgH_t h, const cmChar_t* label ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = p->funcs; + unsigned i; + for(i=0; lfp!=NULL; lfp=lfp->link,++i) + if( lfp->label!=NULL && strcmp(label,lfp->label)==0 ) + return lfp->id; + + return cmInvalidId; +} + + +cmGrLabelFunc_t cmGrPageLabelFunc( cmGrPgH_t h, unsigned id ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = _cmGrPageLabelIdToRecd(p,id); + assert( lfp != NULL ); + return lfp->func; +} + +const cmChar_t* cmGrPageLabelFuncLabel( cmGrPgH_t h, unsigned id ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = _cmGrPageLabelIdToRecd(p,id); + assert( lfp != NULL ); + return lfp->label; +} + +void* cmGrPageLabelFuncArg( cmGrPgH_t h, unsigned id ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = _cmGrPageLabelIdToRecd(p,id); + assert( lfp != NULL ); + return lfp->arg; +} + + +cmGrVwH_t cmGrPageViewHandle( cmGrPgH_t h, unsigned vwIdx ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(h); + cmGrVwH_t vwH; + assert( vwIdx < p->vn ); + vwH.h = p->viewV + vwIdx; + return vwH; +} + +cmGrVwH_t cmGrPageGrHandleToView( cmGrPgH_t pgH, cmGrH_t grH ) +{ + cmGrPg_t* p = _cmGrPgHandleToPtr(pgH); + cmGrVwH_t vwH = cmGrVwNullHandle; + int i; + for(i=0; ivn; ++i) + if( cmHandlesAreEqual(grH,p->viewV[i].grH) ) + { + vwH.h = p->viewV + i; + break; + } + return vwH; +} + + +bool cmGrViewIsValid( cmGrVwH_t h ) +{ return h.h != NULL; } + +cmGrRC_t cmGrViewInit( cmGrVwH_t h, cmGrH_t grH, const cmChar_t* vwTitle, const cmChar_t* xLabel, const cmChar_t* yLabel ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + + vp->grH = grH; + + _cmGrViewSetTitle( vp, vwTitle ); + _cmGrAxisSetTitle( vp->axis + kBottomGrIdx, xLabel ); + _cmGrAxisSetTitle( vp->axis + kLeftGrIdx, yLabel ); + + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); + + return kOkGrRC; +} + +cmGrRC_t cmGrViewClear( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + cmGrH_t grH = cmGrViewGrHandle(h); + assert( cmGrIsValid(grH) ); + cmGrRC_t rc = cmGrClear( grH ); + + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); + + return rc; +} + +cmGrRC_t cmGrViewPExt( cmGrVwH_t h, cmGrPExt_t* pext ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + *pext = vp->iext; + return kOkGrRC; +} + +bool cmGrViewHasFocus( cmGrVwH_t h ) +{ + if( cmGrViewIsValid(h) == false ) + return false; + + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + + if( vp->p->focusIdx == cmInvalidIdx ) + return false; + + return cmGrId(vp->grH) == vp->p->focusIdx; +} + +cmGrH_t cmGrViewGrHandle( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + return vp->grH; +} + +void cmGrViewSetCfg( cmGrVwH_t h, unsigned cfgFlags ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + cmGrSetCfgFlags( vp->grH, cfgFlags ); + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); +} + +unsigned cmGrViewCfg( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + return cmGrCfgFlags( vp->grH ); +} +void cmGrViewSetTitle( cmGrVwH_t h, const cmChar_t* title ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + _cmGrViewSetTitle( vp, title ); +} +const cmChar_t* cmGrViewTitle( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + return vp->title; +} +void cmGrViewSetFontFamily( cmGrVwH_t h, unsigned id ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + if( vp->fontId != id ) + { + vp->fontId = id; + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); + } + +} +unsigned cmGrViewFontFamily( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + return vp->fontId; +} +void cmGrViewSetFontStyle( cmGrVwH_t h, unsigned flags ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + if( vp->fontStyle != flags ) + { + vp->fontStyle = flags; + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); + } +} +unsigned cmGrViewFontStyle( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + return vp->fontStyle; +} +void cmGrViewSetFontSize( cmGrVwH_t h, unsigned size ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + if( vp->fontSize != size ) + { + vp->fontSize = size; + vp->p->flags = cmSetFlag(vp->p->flags, kDirtyFl); + } +} +unsigned cmGrViewFontSize( cmGrVwH_t h ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + return vp->fontSize; +} + +void cmGrViewSetLabelFunc( cmGrVwH_t h, cmGrAxisIdx_t axisId, unsigned pgLabelFuncId ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + cmGrPgLabelFunc_t** lfpp = NULL; + + switch( axisId ) + { + case kLeftGrIdx: + case kRightGrIdx: + lfpp = &vp->yfunc; + break; + + case kTopGrIdx: + case kBottomGrIdx: + lfpp = &vp->xfunc; + break; + + default: + { assert(0); } + } + + assert( lfpp != NULL ); + + *lfpp = _cmGrPageLabelIdToRecd(vp->p, pgLabelFuncId ); +} + +const cmChar_t* cmGrViewValue( cmGrVwH_t h, cmGrViewValueId_t id, cmChar_t* buf, unsigned bufCharCnt ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + cmGrPgLabelFunc_t* lfp = NULL; + cmGrH_t grH = vp->grH; + cmGrV_t v; + cmGrVPt_t pt0,pt1; + + switch( id ) + { + case kLocalX_VwId: + v = cmGrLocalPt(grH)->x; + lfp = vp->xfunc; + break; + + case kLocalY_VwId: + v = cmGrLocalPt(grH)->y; + lfp = vp->yfunc; + break; + + case kGlobalX_VwId: + v = cmGrGlobalPt(grH)->x; + lfp = vp->xfunc; + break; + + case kGlobalY_VwId: + v = cmGrGlobalPt(grH)->y; + lfp = vp->yfunc; + break; + + case kSelX0_VwId: + cmGrSelectPoints(grH,&pt0,NULL); + v = pt0.x; + lfp = vp->xfunc; + break; + + case kSelY0_VwId: + cmGrSelectPoints(grH,&pt0,NULL); + v = pt0.y; + lfp = vp->yfunc; + break; + + case kSelX1_VwId: + cmGrSelectPoints(grH,NULL,&pt1); + v = pt1.x; + lfp = vp->xfunc; + break; + + case kSelY1_VwId: + cmGrSelectPoints(grH,NULL,&pt1); + v = pt1.y; + lfp = vp->yfunc; + break; + + case kSelW_VwId: + cmGrSelectPoints(grH,&pt0,&pt1); + v = fabs(pt1.x - pt0.x); + lfp = vp->xfunc; + break; + + case kSelH_VwId: + cmGrSelectPoints(grH,&pt0,&pt1); + v = fabs(pt1.y - pt0.y); + lfp = vp->yfunc; + break; + + default: + { assert(0); } + } + + if( bufCharCnt > 0 ) + { + buf[0]=0; + if( lfp != NULL ) + lfp->func( lfp->arg, buf, bufCharCnt, v ); + else + snprintf(buf,bufCharCnt,"%f",v); + } + + return buf; +} + + +cmGrAxH_t cmGrViewAxisHandle( cmGrVwH_t h, cmGrAxisIdx_t axisIdx ) +{ + cmGrPgVw_t* vp = _cmGrPgVwHandleToPtr(h); + cmGrAxH_t axH; + assert( axisIdx < kAxisGrCnt ); + axH.h = vp->axis + axisIdx; + return axH; +} + +bool cmGrAxisIsValid( cmGrAxH_t h ) +{ return h.h != NULL; } + +void cmGrAxisSetCfg( cmGrAxH_t h, unsigned flags ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + if( ap->flags != flags ) + { + ap->flags = flags; + ap->vp->p->flags = cmSetFlag(ap->vp->p->flags, kDirtyFl); + } +} + +unsigned cmGrAxisCfg( cmGrAxH_t h ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + return ap->flags; +} + +void cmGrAxisSetTitle( cmGrAxH_t h, const cmChar_t* title ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + _cmGrAxisSetTitle(ap,title); +} + +const cmChar_t* cmGrAxisTitle( cmGrAxH_t h ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + return ap->title; +} + +void cmGrAxisSetTitleFontFamily( cmGrAxH_t h, unsigned id ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + if( ap->titleFontId != id ) + { + ap->titleFontId = id; + ap->vp->p->flags = cmSetFlag(ap->vp->p->flags, kDirtyFl); + } +} + +unsigned cmGrAxisTitleFontFamily( cmGrAxH_t h ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + return ap->titleFontId; +} + +void cmGrAxisTitleSetFontStyle( cmGrAxH_t h, unsigned flags ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + if( ap->titleFontStyle != flags ) + { + ap->titleFontStyle = flags; + ap->vp->p->flags = cmSetFlag(ap->vp->p->flags, kDirtyFl); + } +} + +unsigned cmGrAxisTitleFontStyle( cmGrAxH_t h ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + return ap->titleFontStyle; +} + +void cmGrAxisTitleSetFontSize( cmGrAxH_t h, unsigned size ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + if( ap->titleFontSize != size ) + { + ap->titleFontSize = size; + ap->vp->p->flags = cmSetFlag(ap->vp->p->flags, kDirtyFl); + } +} + +unsigned cmGrAxisTitleFontSize( cmGrAxH_t h ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + return ap->titleFontSize; +} + +void cmGrAxisSetLabelFunc( cmGrAxH_t h, unsigned id ) +{ + cmGrPgAxis_t* ap = _cmGrPgAxisHandleToPtr(h); + + ap->func = _cmGrPageLabelIdToRecd( ap->vp->p, id ); +} diff --git a/cmGrPage.h b/cmGrPage.h new file mode 100644 index 0000000..4e905b4 --- /dev/null +++ b/cmGrPage.h @@ -0,0 +1,164 @@ +#ifndef cmGrPage_h +#define cmGrPage_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kHashMarkGrFl = 0x10, + kHashLabelGrFl= 0x20 + }; + + typedef cmHandle_t cmGrPgH_t; + typedef cmHandle_t cmGrVwH_t; + typedef cmHandle_t cmGrAxH_t; + + extern cmGrPgH_t cmGrPgNullHandle; + extern cmGrVwH_t cmGrVwNullHandle; + extern cmGrAxH_t cmGrAxNullHandle; + + // Create a cmGrPage object. + cmGrRC_t cmGrPageCreate( cmCtx_t* ctx, cmGrPgH_t* hp, cmGrCbFunc_t cbFunc, void* cbArg ); + + // Destroy and release the resources assoc'd with a cmGrPage object; + cmGrRC_t cmGrPageDestroy( cmGrPgH_t* hp ); + + // Return true if the cmGrPage object handle is valid + bool cmGrPageIsValid( cmGrPgH_t h ); + + // Remove all objects from the page. + cmGrRC_t cmGrPageClear( cmGrPgH_t h ); + + // Intialize the count of rows and columns and setup the default layout. + cmGrRC_t cmGrPageInit( cmGrPgH_t h, const cmGrPExt_t* r, unsigned rn, unsigned cn, cmGrDcH_t dcH ); + + // Update the position of the views on the page. + cmGrRC_t cmGrPageResize( cmGrPgH_t h, const cmGrPExt_t* r, cmGrDcH_t dcH ); + + // Return the current page size and loc'n as set by cmGrPageInit() or cmGrPageResize(). + void cmGrPageRect( cmGrPgH_t h, cmGrPExt_t* r ); + + // Return the count of plot views contained by this page. (rn*cn) + unsigned cmGrPageViewCount( cmGrPgH_t h ); + + // Enable or disable the focus for a given view. + // Note that the focused view is the view which is the target of controller + // buttons and scrollbars. This does not refer to the focused object. + // Set 'enableFl' if the view is receiving the focus. + // Clear 'enableFl' if the view is losing focus. + void cmGrPageViewFocus( cmGrPgH_t h, unsigned vwIdx, bool enableFl ); + + // Return the view which currently has the focus or cmGrVwNullHandle if + // no view has the focus. + cmGrVwH_t cmGrPageFocusedView( cmGrPgH_t h ); + + // + void cmGrPageLayout( cmGrPgH_t h, cmGrDcH_t dcH ); + + // Draw the page. + void cmGrPageDraw( cmGrPgH_t h, cmGrDcH_t dcH ); + + typedef void (*cmGrLabelFunc_t)( void* arg, cmChar_t* label, unsigned labelCharCnt, cmGrV_t value ); + // Returns id of the new page label function. + unsigned cmGrPageLabelFuncRegister( cmGrPgH_t h, cmGrLabelFunc_t func, void* arg, const cmChar_t* label ); + unsigned cmGrPageLabelFuncCount( cmGrPgH_t h ); + unsigned cmGrPageLabelFuncIndexToId( cmGrPgH_t h, unsigned index ); + unsigned cmGrPageLabelFuncLabelToId( cmGrPgH_t h, const cmChar_t* label ); + cmGrLabelFunc_t cmGrPageLabelFunc( cmGrPgH_t h, unsigned id ); + const cmChar_t* cmGrPageLabelFuncLabel( cmGrPgH_t h, unsigned id ); + void* cmGrPageLabelFuncArg( cmGrPgH_t h, unsigned id ); + + + // Get a view handle from the view index. + cmGrVwH_t cmGrPageViewHandle( cmGrPgH_t h, unsigned vwIdx ); + + // Get a view handle from to cmGrH_t. + cmGrVwH_t cmGrPageGrHandleToView( cmGrPgH_t h, cmGrH_t grH ); + + bool cmGrViewIsValid( cmGrVwH_t h ); + + // Initialize a plot view. title,xLabel, and yLabel are optional. + cmGrRC_t cmGrViewInit( cmGrVwH_t h, cmGrH_t grH, const cmChar_t* vwTitle, const cmChar_t* xLabel, const cmChar_t* yLabel ); + + // Remove all objects from the view. + cmGrRC_t cmGrViewClear( cmGrVwH_t h ); + + // Get the plot views physical extents. This function will return the + // current view location/size only after a call to cmGrPageDraw(). + // See the implementation note at the top of this file. + cmGrRC_t cmGrViewPExt( cmGrVwH_t h, cmGrPExt_t* pext ); + + bool cmGrViewHasFocus( cmGrVwH_t h ); + + // Get the cmGrH_t associated with a view. + cmGrH_t cmGrViewGrHandle( cmGrVwH_t h ); + + // kExpandViewGrFl | kSelectHorzGrFl | kSelectVertGrFl + void cmGrViewSetCfg( cmGrVwH_t h, unsigned cfgFlags ); + unsigned cmGrViewCfg( cmGrVwH_t h ); + void cmGrViewSetTitle( cmGrVwH_t h, const cmChar_t* title ); + const cmChar_t* cmGrViewTitle( cmGrVwH_t h ); + void cmGrViewSetFontFamily( cmGrVwH_t h, unsigned id ); + unsigned cmGrViewFontFamily( cmGrVwH_t h ); + void cmGrViewSetFontStyle( cmGrVwH_t h, unsigned flags ); + unsigned cmGrViewFontStyle( cmGrVwH_t h ); + void cmGrViewSetFontSize( cmGrVwH_t h, unsigned size ); + unsigned cmGrViewFontSize( cmGrVwH_t h ); + + // Assign a translation function to be used with cmGrViewValue(). + // cmLeftGrIdx or cmGrRightGrIdx is used to assign y axis translation functions. + // cmTopGrIdx or cmGrBottomGrIdx is used to assign x axis translation functions. + // 'pgLabelFuncId' must be a valid page label function id as returned from cmGrPageLabelFuncRegister(). + // or cmGrPageLabelFuncIndexToId(). + void cmGrViewSetLabelFunc( cmGrVwH_t h, cmGrAxisIdx_t axisId, unsigned pgLabelFuncId ); + + typedef enum + { + kLocalX_VwId, + kLocalY_VwId, + kGlobalX_VwId, + kGlobalY_VwId, + kSelX0_VwId, + kSelY0_VwId, + kSelX1_VwId, + kSelY1_VwId, + kSelW_VwId, + kSelH_VwId + } cmGrViewValueId_t; + const cmChar_t* cmGrViewValue( cmGrVwH_t h, cmGrViewValueId_t id, cmChar_t* buf, unsigned bufCharCnt ); + + // Get an axis handle. + cmGrAxH_t cmGrViewAxisHandle( cmGrVwH_t h, cmGrAxisIdx_t axisIdx ); + + bool cmGrAxisIsValid( cmGrAxH_t h ); + // kHashMarkGrFl | kHashLabelGrFl + void cmGrAxisSetCfg( cmGrAxH_t h, unsigned cfgFlags ); + unsigned cmGrAxisCfg( cmGrAxH_t h ); + void cmGrAxisSetTitle( cmGrAxH_t h, const cmChar_t* title ); + const cmChar_t* cmGrAxisTitle( cmGrAxH_t h ); + void cmGrAxisTitleSetFontFamily( cmGrAxH_t h, unsigned id ); + unsigned cmGrAxisTitleFontFamily( cmGrAxH_t h ); + void cmGrAxisTitleSetFontStyle( cmGrAxH_t h, unsigned flags ); + unsigned cmGrAxisTitleFontStyle( cmGrAxH_t h ); + void cmGrAxisTitleSetFontSize( cmGrAxH_t h, unsigned size ); + unsigned cmGrAxisTitleFontSize( cmGrAxH_t h ); + + void cmGrAxisLabelSetFontFamily( cmGrAxH_t h, unsigned id ); + unsigned cmGrAxisLabelFontFamily( cmGrAxH_t h ); + void cmGrAxisLabelSetFontStyle( cmGrAxH_t h, unsigned flags ); + unsigned cmGrAxisLabelFontStyle( cmGrAxH_t h ); + void cmGrAxisLabelSetFontSize( cmGrAxH_t h, unsigned size ); + unsigned cmGrAxisLabelFontSize( cmGrAxH_t h ); + + // Assign a translation function for the value on this axis. + // 'pgLabelFuncId' must be a valid page label function id as returned from cmGrPageLabelFuncRegister(). + // or cmGrPageLabelFuncIndexToId(). + void cmGrAxisSetLabelFunc( cmGrAxH_t h, unsigned pgLabelFuncId ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmGrPlot.c b/cmGrPlot.c new file mode 100644 index 0000000..c2cd461 --- /dev/null +++ b/cmGrPlot.c @@ -0,0 +1,1223 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmGr.h" +#include "cmGrDevCtx.h" + +#include "cmGrPlot.h" +#include "cmVectOpsTemplateMain.h" + + +//------------------------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------------------------ + + + +struct cmGrPl_str; + + +typedef struct cmGrPlotObj_str +{ + cmGrH_t grH; // the canvas this object is drawn on + cmGrObjH_t grObjH; // the grObj this object is based on + cmGrPlObjTypeId_t typeId; + unsigned cfgFlags; + unsigned stateFlags; + cmGrVExt_t vext; + + cmChar_t* label; + unsigned labelFlags; + int labelAngle; + cmGrColor_t labelColor; + + int loffs; + int toffs; + int roffs; + int boffs; + + cmGrColor_t drawColors[ kMaxPlGrId ]; + cmGrColor_t fillColors[ kMaxPlGrId ]; + unsigned fontId; + unsigned fontSize; + unsigned fontStyle; + void* userPtr; + cmGrPlotCbFunc_t cbFunc; + void* cbArg; + + struct cmGrPl_str* p; // owning plot object manager + struct cmGrPlotObj_str* parent; // containing object + struct cmGrPlotObj_str* xAnchor; // x-location reference object + struct cmGrPlotObj_str* yAnchor; // y-location reference object + struct cmGrPlotObj_str* next; + struct cmGrPlotObj_str* prev; + +} cmGrPlotObj_t; + + +typedef struct cmGrPl_str +{ + cmCtx_t* ctx; // + cmErr_t err; // + cmGrPlotObj_t* list; // plot object linked list + cmGrPlotObj_t* fop; // focused object ptr +} cmGrPl_t; + +cmGrPlH_t cmGrPlNullHandle = cmSTATIC_NULL_HANDLE; +cmGrPlObjH_t cmGrPlObjNullHandle = cmSTATIC_NULL_HANDLE; + +//------------------------------------------------------------------------------------------------------------------ +// Plot Private Functions +//------------------------------------------------------------------------------------------------------------------ + +cmGrPl_t* _cmGrPlHandleToPtr( cmGrPlH_t h ) +{ + cmGrPl_t* p = (cmGrPl_t*)h.h; + assert(p!=NULL); + return p; +} + +cmGrPlotObj_t* _cmGrPlObjHandleToPtr( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = (cmGrPlotObj_t*)oh.h; + assert( op!=NULL); + return op; +} + +cmGrPlRC_t _cmGrPlotObjDelete( cmGrPlotObj_t* op ) +{ + if( op==NULL || cmGrObjIsValid( op->grH, op->grObjH)==false ) + return kOkGrPlRC; + + cmGrPl_t* p = op->p; + + // destroy the cmGrObj - which will call _cmGrPlotObjDestroy() + if( cmGrObjDestroy( op->grH, &op->grObjH ) != kOkGrRC ) + return cmErrMsg( &p->err, kGrFailGrPlRC, "Delete failed on the object label='%s' id=%i\n",cmStringNullGuard( op->label ), cmGrObjId(op->grObjH) ); + + return kOkGrPlRC; +} + +void _cmGrPlotObjUnlink( cmGrPlotObj_t* op ) +{ + cmGrPl_t* p = op->p; + + if( op->next != NULL ) + op->next->prev = op->prev; + + if( op->prev != NULL ) + op->prev->next = op->next; + + if( p->list == op ) + p->list = op->next; +} + +void _cmGrPlotObjLink( cmGrPl_t* p, cmGrPlotObj_t* op ) +{ + if( p->list != NULL ) + p->list->prev = op; + + op->next = p->list; + op->prev = NULL; + p->list = op; +} + + +// Destroy all objects +cmGrPlRC_t _cmGrPlotClear( cmGrPl_t* p ) +{ + cmGrPlRC_t rc = kOkGrPlRC; + cmGrPlotObj_t* op = p->list; + + while( op!=NULL ) + { + cmGrPlotObj_t* t = op->next; + + if((rc = _cmGrPlotObjDelete(op)) != kOkGrPlRC ) + break; + + op = t; + } + + return rc; +} + +// Destroy the plot mgr +cmGrPlRC_t _cmGrPlotDestroy( cmGrPl_t* p ) +{ + cmGrPlRC_t rc; + + if((rc = _cmGrPlotClear(p)) != kOkGrPlRC ) + return rc; + + cmMemFree(p); + + return kOkGrPlRC; +} + +bool _cmGrPlotObjIsVisible( cmGrPlotObj_t* op ) +{ return cmIsNotFlag(op->cfgFlags,kNoDrawGrPlFl); } + +bool _cmGrPlotObjIsEnabled( cmGrPlotObj_t* op ) +{ + // invisible objects are never enabled + if( _cmGrPlotObjIsVisible(op) == false ) + return false; + + return cmIsFlag(op->stateFlags,kEnabledGrPlFl); +} + +bool _cmGrPlotObjIsFocused(cmGrPlotObj_t* op) +{ return _cmGrPlotObjIsEnabled(op) && op->p->fop==op; } + +bool _cmGrPlotObjIsSelected(cmGrPlotObj_t* op) +{ return _cmGrPlotObjIsFocused(op) || cmIsFlag(op->stateFlags,kSelectGrPlFl); } + +void _cmGrPlotObjSetupCbArg( cmGrPlotCbArg_t* a, cmGrPlotObj_t* op, cmGrPlCbSelId_t selId ) +{ + cmGrPlObjH_t oH; + oH.h = op; + + memset(a,0,sizeof(a)); + a->ctx = op->p->ctx; + a->cbArg = op->cbArg; + a->selId = selId; + a->objH = oH; +} + +bool _cmGrPlotObjCb( cmGrPlotObj_t* op, cmGrPlCbSelId_t selId, unsigned deltaFlags ) +{ + if( op->cbFunc != NULL ) + { + cmGrPlotCbArg_t a; + + _cmGrPlotObjSetupCbArg(&a,op,kPreEventCbSelGrPlId); + a.deltaFlags = deltaFlags; + return op->cbFunc(&a); + } + + return true; +} + +void _cmGrPlotObjSetFocus( cmGrPlotObj_t* op ) +{ + // if 'op' is not enabled then it cannot receive the focus + if( _cmGrPlotObjIsEnabled(op) == false ) + return; + + // if the focus cannot be set on 'op' - then try op->parent + for(; op!=NULL; op=op->parent) + if( cmIsNotFlag(op->cfgFlags,kNoFocusGrPlFl) && cmIsNotFlag(op->cfgFlags,kNoDrawGrPlFl) ) + break; + + if( op != NULL ) + { + if( op->p->fop != NULL ) + { + // if the application callback returns false then do no release focus from the current object + if(_cmGrPlotObjCb(op->p->fop, kStateChangeGrPlId, kFocusGrPlFl ) == false ) + return; + + op->p->fop = NULL; + } + + // if the application callback returns false then do not give focus to the selected object + if(_cmGrPlotObjCb(op, kStateChangeGrPlId, kFocusGrPlFl ) == false ) + return; + + op->p->fop = op; + } + +} + +void _cmGrPlotObjSetSelect( cmGrPlotObj_t* op, bool clearFl ) +{ + // if the object is disabled or no selectable + if( _cmGrPlotObjIsEnabled(op)==false || cmIsFlag(op->cfgFlags,kNoSelectGrPlFl | kNoDrawGrPlFl) ) + return; + + unsigned stateFlags = op->stateFlags; + + // if the application callback returns false then do change the select state of the object + if(_cmGrPlotObjCb(op, kStateChangeGrPlId, kSelectGrPlFl ) == false ) + return; + + if( clearFl ) + { + cmGrObjH_t parentObjH = cmGrObjParent(op->grObjH); + cmGrPlotObj_t* cop = op->p->list; + + // clear the select flag on all objects that share op->parent + for(; cop!=NULL; cop=cop->next) + if( cmHandlesAreEqual(cmGrObjParent(cop->grObjH),parentObjH) ) + cop->stateFlags = cmClrFlag(cop->stateFlags,kSelectGrPlFl); + } + + op->stateFlags = cmTogFlag(stateFlags,kSelectGrPlFl); + +} + + +const cmGrColor_t _cmGrPlotColor( cmGrPlotObj_t* op, cmGrColor_t* array ) +{ + if( _cmGrPlotObjIsFocused(op) ) + return array[kFocusPlGrId]; + + if( _cmGrPlotObjIsSelected(op) ) + return array[kSelectPlGrId]; + + if( _cmGrPlotObjIsEnabled(op) ) + return array[kEnablePlGrId]; + + return array[kDisablePlGrId]; +} + +unsigned _cmGrPlotObjTriShapeToFlags( unsigned typeId) +{ + switch(typeId) + { + case kUTriGrPlId: return kTopGrFl; + case kDTriGrPlId: return kBottomGrFl; + case kLTriGrPlId: return kLeftGrFl; + case kRTriGrPlId: return kRightGrFl; + default: + { assert(0); } + } + return 0; +} + +//------------------------------------------------------------------------------------------------------------------ +// Plot Object Callback Functions +//------------------------------------------------------------------------------------------------------------------ + +cmGrRC_t _cmGrPlotObjCreate( cmGrObjFuncArgs_t* args ) +{ + cmGrPlotObj_t* op = args->cbArg; + _cmGrPlotObjCb(op,kCreatedCbSelGrPlId,0); + + // return kOkGrRC to indicate that the create was successful + return kOkGrRC; +} + +void _cmGrPlotObjDestroy( cmGrObjFuncArgs_t* args ) +{ + cmGrPlotObj_t* op = args->cbArg; + + // TODO: is it possible to prevent destruction by returning + // 'false' from the used defined callback. This feature is + // slightly complicated by the fact + // that in some circumstances the destroy request is not + // optional - for example when the program is closing. + _cmGrPlotObjCb(op,kDestroyedCbSelGrPlId,0); + + _cmGrPlotObjUnlink( op ); + cmMemFree(op->label); + cmMemFree(op); +} + +void _cmGrPlotObjGetVExt( cmGrPlotObj_t* op, cmGrVExt_t* vext ) +{ + switch( op->typeId ) + { + case kStarGrPlId: + case kCrossGrPlId: + case kPlusGrPlId: + case kDiamondGrPlId: + case kUTriGrPlId: + case kDTriGrPlId: + case kLTriGrPlId: + case kRTriGrPlId: + case kRectGrPlId: + case kLineGrPlId: + case kEllipseGrPlId: + { + *vext = op->vext; + } + break; + + case kHLineGrPlId: + case kVLineGrPlId: + { + cmGrVExt_t wext; + cmGrObjH_t oh = cmGrObjParent(op->grObjH); + cmGrObjWorldExt(oh,&wext); + + vext->loc.x = op->typeId==kHLineGrPlId ? wext.loc.x : op->vext.loc.x; + vext->loc.y = op->typeId==kVLineGrPlId ? wext.loc.y : op->vext.loc.y; + vext->sz.w = op->typeId==kHLineGrPlId ? wext.sz.w : op->vext.sz.w; + vext->sz.h = op->typeId==kVLineGrPlId ? wext.sz.h : op->vext.sz.h; + } + break; + + default: + { assert(0); } + } + + // add up the anchor offsets until the first object in the container + cmGrPlotObj_t* ap = op->xAnchor; + for(; ap!=NULL; ap=ap->xAnchor) + { + vext->loc.x += ap->vext.loc.x; + if( ap->xAnchor==ap->parent) + break; + } + ap = op->yAnchor; + for(; ap!=NULL; ap=ap->yAnchor) + { + vext->loc.y += ap->vext.loc.y; + if( ap->yAnchor==ap->parent) + break; + } +} + +void _cmGrPlotObjVExt( cmGrObjFuncArgs_t* args, cmGrVExt_t* vext ) +{ + cmGrPlotObj_t* op = args->cbArg; + _cmGrPlotObjGetVExt(op, vext); +} + +bool _cmGrPlotObjRender( cmGrObjFuncArgs_t* args, cmGrDcH_t dcH ) +{ + cmGrPlotObj_t* op = args->cbArg; + cmGrPExt_t pext; + cmGrVExt_t vext; + + if( !_cmGrPlotObjIsVisible(op) ) + return false; + + // get the virtual extents of this object + _cmGrPlotObjVExt( args, &vext ); + + // convert the virtual ext's to phys ext's + cmGrVExt_VtoP( op->grH, op->grObjH, &vext, &pext); + + // expand the ext's according to the physical offsets + cmGrPExtExpand(&pext,op->loffs,op->toffs,op->roffs,op->boffs); + + switch( op->typeId ) + { + case kLineGrPlId: + cmGrDcSetColor( dcH, _cmGrPlotColor(op,op->drawColors) ); + cmGrDcDrawLine( dcH, cmGrPExtL(&pext), cmGrPExtT(&pext), cmGrPExtR(&pext), cmGrPExtB(&pext) ); + break; + + case kStarGrPlId: + case kCrossGrPlId: + case kPlusGrPlId: + case kEllipseGrPlId: + case kDiamondGrPlId: + case kUTriGrPlId: + case kDTriGrPlId: + case kLTriGrPlId: + case kRTriGrPlId: + case kRectGrPlId: + case kHLineGrPlId: + case kVLineGrPlId: + { + if( cmIsNotFlag(op->cfgFlags,kNoFillGrPlFl) ) + { + // set the fill color + cmGrDcSetColor( dcH, _cmGrPlotColor(op,op->fillColors) ); + + // draw the fill + switch(op->typeId) + { + case kEllipseGrPlId: + cmGrDcFillEllipse( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h); + break; + + case kDiamondGrPlId: + cmGrDcFillDiamond( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h); + break; + + case kUTriGrPlId: + case kDTriGrPlId: + case kLTriGrPlId: + case kRTriGrPlId: + cmGrDcFillTriangle( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h, _cmGrPlotObjTriShapeToFlags(op->typeId)); + break; + + case kStarGrPlId: + case kCrossGrPlId: + case kPlusGrPlId: + case kRectGrPlId: + case kHLineGrPlId: + case kVLineGrPlId: + cmGrDcFillRect( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h); + break; + + default: + { assert(0); } + } + } + + if( cmIsNotFlag(op->cfgFlags,kNoBorderGrPlFl) ) + { + // set the border color + cmGrDcSetColor( dcH, _cmGrPlotColor(op,op->drawColors) ); + + + // draw the border + switch(op->typeId) + { + case kEllipseGrPlId: + cmGrDcDrawEllipse( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h); + break; + + case kDiamondGrPlId: + cmGrDcDrawDiamond( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h); + break; + + case kUTriGrPlId: + case kDTriGrPlId: + case kLTriGrPlId: + case kRTriGrPlId: + cmGrDcDrawTriangle( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h, _cmGrPlotObjTriShapeToFlags(op->typeId)); + break; + + case kStarGrPlId: + cmGrDcDrawLine( dcH, cmGrPExtL(&pext), cmGrPExtT(&pext), cmGrPExtR(&pext), cmGrPExtB(&pext)); + cmGrDcDrawLine( dcH, cmGrPExtL(&pext), cmGrPExtB(&pext), cmGrPExtR(&pext), cmGrPExtT(&pext)); + cmGrDcDrawLine( dcH, cmGrPExtL(&pext) + cmGrPExtW(&pext)/2, cmGrPExtT(&pext), cmGrPExtL(&pext) + cmGrPExtW(&pext)/2, cmGrPExtB(&pext)); + cmGrDcDrawLine( dcH, cmGrPExtL(&pext), cmGrPExtT(&pext) + cmGrPExtH(&pext)/2, cmGrPExtR(&pext), cmGrPExtT(&pext) + cmGrPExtH(&pext)/2); + break; + + case kCrossGrPlId: + cmGrDcDrawLine( dcH, cmGrPExtL(&pext), cmGrPExtT(&pext), cmGrPExtR(&pext), cmGrPExtB(&pext)); + cmGrDcDrawLine( dcH, cmGrPExtR(&pext), cmGrPExtT(&pext), cmGrPExtL(&pext), cmGrPExtB(&pext)); + break; + + case kPlusGrPlId: + cmGrDcDrawLine( dcH, cmGrPExtL(&pext) + cmGrPExtW(&pext)/2, cmGrPExtT(&pext), cmGrPExtL(&pext) + cmGrPExtW(&pext)/2, cmGrPExtB(&pext)); + cmGrDcDrawLine( dcH, cmGrPExtL(&pext), cmGrPExtT(&pext) + cmGrPExtH(&pext)/2, cmGrPExtR(&pext), cmGrPExtT(&pext) + cmGrPExtH(&pext)/2); + break; + + + case kRectGrPlId: + case kHLineGrPlId: + case kVLineGrPlId: + cmGrDcDrawRect( dcH, pext.loc.x, pext.loc.y, pext.sz.w, pext.sz.h); + break; + + default: + { assert(0); } + + } + + } + + if( (op->label != NULL) && cmIsNotFlag(op->cfgFlags, kNoLabelGrPlFl) ) + { + unsigned cc = cmGrDcColor(dcH); + cmGrDcSetColor(dcH,op->labelColor); + cmGrDcDrawTextJustify( dcH, op->fontId, op->fontSize, op->fontStyle, op->label, &pext, op->labelFlags ); + cmGrDcSetColor(dcH,cc); + + + + /* + cmGrPSz_t sz; + cmGrPPt_t pt; + cmGrDcFontSetAndMeasure( dcH, op->fontId, op->fontSize, op->fontStyle, op->label, &sz ); + cmGrPExtCtr( &pext, &pt ); + cmGrDcDrawText( dcH, op->label, pt.x - sz.w/2, pt.y + sz.h/2 ); + */ + } + + } + break; + + + default: + { assert(0); } + } + + return true; +} + +int _cmGrPlotObjDistance( cmGrObjFuncArgs_t* args, int x, int y ) +{ + return 0; +} + +bool _cmGrPlotObjEvent( cmGrObjFuncArgs_t* args, unsigned flags, unsigned key, int px, int py ) +{ + cmGrPlotObj_t* op = args->cbArg; + bool fl = false; + cmGrPlotCbArg_t a; + + if( op->cbFunc != NULL ) + { + cmGrPlotObj_t* cb_op = op; + + // if this is a key up/dn event and 'op' is not the 'focused op' then callback + // callback on the 'focused op' instead of this 'op'. + if( (cmIsFlag(flags,kKeyDnGrFl) || cmIsFlag(flags,kKeyUpGrFl)) && op->p->fop != op ) + cb_op = op->p->fop; + + _cmGrPlotObjSetupCbArg(&a,cb_op,kPreEventCbSelGrPlId); + a.eventFlags = flags; + a.eventKey = key; + a.eventX = px; + a.eventY = py; + if( op->cbFunc(&a) == false ) + return true; + } + + switch( flags & kEvtMask ) + { + case kMsDownGrFl: + break; + + case kMsUpGrFl: + _cmGrPlotObjSetFocus( op ); + fl = true; + break; + + case kMsClickGrFl: + _cmGrPlotObjSetSelect(op, cmIsNotFlag(flags,kCtlKeyGrFl) ); + fl = true; + break; + + case kMsDragGrFl: + { + cmGrVExt_t vext; + cmGrVExt_t wext; + + if( cmIsFlag(op->cfgFlags,kNoDragGrPlFl | kNoDrawGrPlFl) ) + return false; + + // get the parent world extents + cmGrObjWorldExt( cmGrObjParent( args->objH ), &wext ); + + // calc the new position of the obj + cmGrV_t x = args->msVPt.x - args->msDnVOffs.w; + cmGrV_t y = args->msVPt.y - args->msDnVOffs.h; + + cmGrVExtSet(&vext,x,y,op->vext.sz.w,op->vext.sz.h); + + // the obj must be remain inside the parent wext + cmGrVExtContain(&wext,&vext); + + // calculate the obj's location as an offset from it's anchors + cmGrPlotObj_t* ap = op->xAnchor; + for(; ap!=NULL; ap=ap->xAnchor) + vext.loc.x -= ap->vext.loc.x; + + ap = op->yAnchor; + for(; ap!=NULL; ap=ap->yAnchor) + vext.loc.y -= ap->vext.loc.y; + + + if( !cmGrVExtIsEqual(&op->vext,&vext) ) + { + // move the object + op->vext.loc.x = vext.loc.x; + op->vext.loc.y = vext.loc.y; + fl = true; + } + } + break; + + case kKeyDnGrFl: + case kKeyUpGrFl: + break; + } + + // notify the app of the event + if( op->cbFunc != NULL ) + { + a.selId = kEventCbSelGrPlId; + op->cbFunc(&a); + } + + return fl; +} + + +bool _cmGrPlotObjIsInside( cmGrObjFuncArgs_t* args, int px, int py, cmGrV_t vx, cmGrV_t vy ) +{ + cmGrVExt_t vext; + cmGrPExt_t pext; + cmGrPlotObj_t* op = args->cbArg; + + // get the virtual extents of this object + _cmGrPlotObjVExt( args, &vext ); + + // convert the virtual ext's to phys ext's + cmGrVExt_VtoP(args->grH,args->objH,&vext,&pext); + + // expand the ext's according to the off's + cmGrPExtExpand(&pext,op->loffs,op->toffs,op->roffs,op->boffs); + + if( op->typeId == kLineGrPlId ) + if( cmVOR_PtToLineDistance(cmGrPExtL(&pext),cmGrPExtT(&pext),cmGrPExtR(&pext),cmGrPExtB(&pext),px,py) < 3 ) + return true; + + // check if the px,py is inside pext + return cmGrPExtIsXyInside(&pext,px,py); +} + +void _cmGrPlotFuncObjSetupDefault(cmGrObjFunc_t *f, void* arg ) +{ + f->createCbFunc = _cmGrPlotObjCreate; + f->createCbArg = arg; + f->destroyCbFunc = _cmGrPlotObjDestroy; + f->destroyCbArg = arg; + f->renderCbFunc = _cmGrPlotObjRender; + f->renderCbArg = arg; + f->distanceCbFunc = _cmGrPlotObjDistance; + f->distanceCbArg = arg; + f->eventCbFunc = _cmGrPlotObjEvent; + f->eventCbArg = arg; + f->vextCbFunc = _cmGrPlotObjVExt; + f->vextCbArg = arg; + f->isInsideCbFunc = _cmGrPlotObjIsInside; + f->isInsideCbArg = arg; +} + +//------------------------------------------------------------------------------------------------------------------ +// Plot Object Public Functions +//------------------------------------------------------------------------------------------------------------------ + +cmGrPlRC_t cmGrPlotObjCreate( + cmGrPlH_t hh, + cmGrH_t grH, + cmGrPlObjH_t* hp, + unsigned id, + cmGrPlObjH_t parentPlObjH, + cmGrPlObjH_t xAnchorPlObjH, + cmGrPlObjH_t yAnchorPlObjH, + cmGrPlObjTypeId_t typeId, + unsigned cfgFlags, + cmReal_t x, + cmReal_t y, + cmReal_t w, + cmReal_t h, + const cmChar_t* label, + const cmGrVExt_t* wext ) +{ + cmGrPlRC_t rc; + cmGrObjFunc_t funcs; + + if((rc = cmGrPlotObjDestroy(hp)) != kOkGrPlRC ) + return rc; + + cmGrPl_t* p = _cmGrPlHandleToPtr(hh); + cmGrPlotObj_t* op = cmMemAllocZ(cmGrPlotObj_t,1); + + _cmGrPlotObjLink(p,op); + + _cmGrPlotFuncObjSetupDefault(&funcs,op); + + // setup the object + op->grH = grH; + op->typeId = typeId; + op->cfgFlags = cfgFlags; + op->stateFlags = kEnabledGrPlFl; + op->label = label==NULL ?NULL : cmMemAllocStr(label); + op->labelFlags = kHorzCtrJsGrFl | kVertCtrJsGrFl; + op->labelAngle = 0; + op->labelColor = kBlackGrId; + op->grObjH = cmGrObjNullHandle; + op->parent = cmGrPlotObjIsValid(parentPlObjH) ? _cmGrPlObjHandleToPtr(parentPlObjH) : NULL; + op->xAnchor = cmGrPlotObjIsValid(xAnchorPlObjH) ? _cmGrPlObjHandleToPtr(xAnchorPlObjH) : NULL; + op->yAnchor = cmGrPlotObjIsValid(yAnchorPlObjH) ? _cmGrPlObjHandleToPtr(yAnchorPlObjH) : NULL; + op->p = p; + op->fontId = kHelveticaFfGrId; + op->fontSize = 12; + op->fontStyle = kNormalFsGrFl; + + if( cmIsFlag(op->cfgFlags,kSymbolGrPlFl) ) + { + int ww = w==0 ? kDefaultSymW : w; + int hh = h==0 ? kDefaultSymH : h; + + op->loffs = ww/2; + op->roffs = ww/2; + op->toffs = hh/2; + op->boffs = hh/2; + w = 0; + h = 0; + } + + cmGrVExtSet(&op->vext,x,y,w,h); + + + // set the default colors + op->drawColors[kFocusPlGrId] = 0xcd853f; + op->fillColors[kFocusPlGrId] = 0xdeb887; + + op->drawColors[kSelectPlGrId] = 0x483d8b; + op->fillColors[kSelectPlGrId] = 0x8470ff; + + op->drawColors[kEnablePlGrId] = 0x000000; + op->fillColors[kEnablePlGrId] = 0x009ff7; + + op->drawColors[kDisablePlGrId] = 0xbebebe; + op->fillColors[kDisablePlGrId] = 0xd3d3de; + + unsigned grObjCfgFlags = 0; + cmGrObjH_t parentGrH = op->parent == NULL ? cmGrObjNullHandle : op->parent->grObjH; + + + + // create the graphics system object - during this call a + // call is made to funcs.create(). + if( cmGrObjCreate(grH, &op->grObjH, parentGrH, &funcs, id, grObjCfgFlags, wext ) != kOkGrRC ) + { + rc = cmErrMsg(&p->err,kGrFailGrPlRC,"Graphic system object create failed for object (id=%i).",id); + goto errLabel; + } + + if( hp != NULL ) + hp->h = op; + + errLabel: + if( rc != kOkGrPlRC ) + _cmGrPlotObjDelete(op); + + return rc; +} + +cmGrPlRC_t cmGrPlotObjDestroy( cmGrPlObjH_t* hp ) +{ + cmGrPlRC_t rc = kOkGrPlRC; + + if( hp==NULL || cmGrPlotObjIsValid(*hp)==false ) + return kOkGrPlRC; + + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(*hp); + + if((rc = _cmGrPlotObjDelete(op)) != kOkGrPlRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmGrPlotObjIsValid( cmGrPlObjH_t h ) +{ return h.h != NULL; } + + +cmGrPlH_t cmGrPlotObjMgrHandle( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + cmGrPlH_t grPlH; + grPlH.h = op->p; + return grPlH; +} + +cmGrObjH_t cmGrPlotObjHandle( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->grObjH; +} + + +void cmGrPlotObjSetId( cmGrPlObjH_t oh, unsigned id ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + cmGrObjSetId( op->grObjH, id ); +} + +void cmGrPlotObjSetUserPtr( cmGrPlObjH_t oh, void* userPtr ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + op->userPtr = userPtr; +} +void* cmGrPlotObjUserPtr( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->userPtr; +} + + +unsigned cmGrPlotObjId( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return cmGrObjId(op->grObjH); +} + +void cmGrPlotObjSetLabel( cmGrPlObjH_t oh, const cmChar_t* label ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + + if( label == op->label || (label != NULL && op->label!=NULL && strcmp(label,op->label)==0 )) + return; + + cmMemPtrFree(&op->label); + + if( label != NULL ) + { + assert( op->label == NULL ); + op->label = cmMemAllocStr(label); + } +} + +const cmChar_t* cmGrPlotObjLabel( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->label; +} + +void cmGrPlotObjSetLabelAttr( cmGrPlObjH_t oh, unsigned flags, int angle, const cmGrColor_t color ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + op->labelFlags = flags; + op->labelAngle = angle; + op->labelColor = color; +} + +unsigned cmGrPlotObjLabelFlags( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->labelFlags; +} + +int cmGrPlotObjLabelAngle( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->labelAngle; +} + +const cmGrColor_t cmGrPlotObjLabelColor( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->labelColor; +} + +void cmGrPlotObjSetStateFlags( cmGrPlObjH_t oh, unsigned flags ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + + if( cmIsFlag(flags,kVisibleGrPlFl) != _cmGrPlotObjIsVisible(op) ) + { + if( _cmGrPlotObjCb(op, kStateChangeGrPlId, kVisibleGrPlFl ) == false ) + return; + + op->cfgFlags = cmTogFlag(op->cfgFlags,kNoDrawGrPlFl); + } + + if( cmIsFlag(flags,kEnabledGrPlFl) != _cmGrPlotObjIsEnabled(op) ) + { + if( _cmGrPlotObjCb(op, kStateChangeGrPlId, kEnabledGrPlFl ) == false ) + return; + + op->stateFlags = cmTogFlag(op->cfgFlags,kEnabledGrPlFl); + } + + bool fl; + if( cmIsFlag(flags,kSelectGrPlFl) != (fl=_cmGrPlotObjIsSelected(op)) ) + _cmGrPlotObjSetSelect(op, !fl ); + + if( cmIsFlag(flags,kFocusGrPlFl) != (fl=_cmGrPlotObjIsFocused(op)) ) + if( fl ) + _cmGrPlotObjSetFocus(op); + +} + +unsigned cmGrPlotObjStateFlags( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return + (_cmGrPlotObjIsEnabled(op) ? kEnabledGrPlFl : 0) + | (_cmGrPlotObjIsVisible(op) ? kVisibleGrPlFl : 0) + | (_cmGrPlotObjIsFocused(op) ? kFocusGrPlFl : 0) + | (_cmGrPlotObjIsSelected(op) ? kSelectGrPlFl : 0); + +} + +void cmGrPlotObjSetCfgFlags( cmGrPlObjH_t oh, unsigned flags ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + op->cfgFlags = flags; +} + +void cmGrPlotObjClrCfgFlags( cmGrPlObjH_t oh, unsigned flags ) +{ + unsigned curFlags = cmGrPlotObjCfgFlags(oh); + cmGrPlotObjSetCfgFlags(oh, cmClrFlag(curFlags,flags)); +} + +void cmGrPlotObjTogCfgFlags( cmGrPlObjH_t oh, unsigned flags ) +{ + unsigned curFlags = cmGrPlotObjCfgFlags(oh); + cmGrPlotObjSetCfgFlags(oh, cmTogFlag(curFlags,flags)); +} + + + +unsigned cmGrPlotObjCfgFlags( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->cfgFlags; +} + +cmGrPlRC_t cmGrPlotObjSetPhysExt( cmGrPlObjH_t oh, int loffs, int toffs, int roffs, int boffs ) +{ + cmGrPlRC_t rc = kOkGrPlRC; + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + + op->loffs = loffs; + op->toffs = toffs; + op->roffs = roffs; + op->boffs = boffs; + + return rc; +} + +void cmGrPlotObjPhysExt( cmGrPlObjH_t oh, int* loffs, int* toffs, int* roffs, int* boffs ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + + if( loffs != NULL ) + *loffs = op->loffs; + + if( toffs != NULL ) + *toffs = op->toffs; + + if( roffs != NULL ) + *roffs = op->roffs; + + if( boffs != NULL ) + *boffs = op->boffs; + +} + +void cmGrPlotObjVExt( cmGrPlObjH_t oh, cmGrVExt_t* vext ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + _cmGrPlotObjGetVExt(op, vext); +} + + +void cmGrPlotObjSetFontFamily( cmGrPlObjH_t oh, unsigned id ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + op->fontId = id; +} + +unsigned cmGrPlotObjFontFamily( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->fontId; +} + +void cmGrPlotObjSetFontStyle( cmGrPlObjH_t oh, unsigned style ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + op->fontStyle = style; +} + +unsigned cmGrPlotObjFontStyle( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->fontStyle; +} + +void cmGrPlotObjSetFontSize( cmGrPlObjH_t oh, unsigned size ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + op->fontSize = size; +} + +unsigned cmGrPlotObjFontSize( cmGrPlObjH_t oh ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + return op->fontSize; +} + +void cmGrPlotObjSetLineColor( cmGrPlObjH_t oh, cmGrPlStateId_t id, const cmGrColor_t c ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + assert( id < kMaxPlGrId ); + op->drawColors[ id ] = c; +} + +const cmGrColor_t cmGrPlotObjLineColor( cmGrPlObjH_t oh, cmGrPlStateId_t id ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + assert( id < kMaxPlGrId ); + return op->drawColors[id]; +} + +const cmGrColor_t cmGrPlotObjCurLineColor( cmGrPlObjH_t h ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(h); + return _cmGrPlotColor(op,op->drawColors); +} + +void cmGrPlotObjSetFillColor( cmGrPlObjH_t oh, cmGrPlStateId_t id, const cmGrColor_t c ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + assert( id < kMaxPlGrId ); + op->fillColors[ id ] = c; +} + +const cmGrColor_t cmGrPlotObjFillColor( cmGrPlObjH_t oh, cmGrPlStateId_t id ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(oh); + assert( id < kMaxPlGrId ); + return op->fillColors[id]; +} + +const cmGrColor_t cmGrPlotObjCurFillColor( cmGrPlObjH_t h ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(h); + return _cmGrPlotColor(op,op->fillColors); +} + +void cmGrPlotObjSetCb( cmGrPlObjH_t h, cmGrPlotCbFunc_t func, void* arg ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(h); + op->cbFunc = func; + op->cbArg = arg; +} + +cmGrPlotCbFunc_t cmGrPlotObjCbFunc( cmGrPlObjH_t h ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(h); + return op->cbFunc; +} + +void* cmGrPlotObjCbArg( cmGrPlObjH_t h ) +{ + cmGrPlotObj_t* op = _cmGrPlObjHandleToPtr(h); + return op->cbArg; +} + +void cmGrPlotObjDrawAbove( cmGrPlObjH_t bH, cmGrPlObjH_t aH ) +{ + cmGrPlotObj_t* bop = _cmGrPlObjHandleToPtr(bH); + cmGrPlotObj_t* aop = _cmGrPlObjHandleToPtr(aH); + cmGrObjDrawAbove(bop->grObjH,aop->grObjH); +} + +//------------------------------------------------------------------------------------------------------------------ +// Plot Object Manager Functions +//------------------------------------------------------------------------------------------------------------------ + +cmGrPlRC_t cmGrPlotCreate( cmCtx_t* ctx, cmGrPlH_t* hp ) +{ + cmGrPlRC_t rc; + if((rc = cmGrPlotDestroy(hp)) != kOkGrPlRC ) + return rc; + + cmGrPl_t* p = cmMemAllocZ(cmGrPl_t,1); + cmErrSetup(&p->err,&ctx->rpt,"cmGrPlot"); + p->ctx = ctx; + + hp->h = p; + + if( rc != kOkGrPlRC ) + _cmGrPlotDestroy(p); + + return rc; +} + +cmGrPlRC_t cmGrPlotDestroy( cmGrPlH_t* hp ) +{ + cmGrPlRC_t rc; + + if( hp==NULL || cmGrPlotIsValid(*hp) == false ) + return kOkGrPlRC; + + cmGrPl_t* p = _cmGrPlHandleToPtr(*hp); + + if((rc = _cmGrPlotDestroy(p)) != kOkGrPlRC ) + return rc; + + hp->h = NULL; + + return rc; +} + +bool cmGrPlotIsValid( cmGrPlH_t h ) +{ return h.h != NULL; } + +cmGrPlRC_t cmGrPlotClear( cmGrPlH_t h ) +{ + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + + return _cmGrPlotClear(p); +} + +cmErr_t* cmGrPlotErr( cmGrPlH_t h ) +{ + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + return &p->err; +} + +cmRpt_t* cmGrPlotRpt( cmGrPlH_t h ) +{ + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + return p->err.rpt; +} + +cmGrPlObjH_t cmGrPlotObjectIdToHandle( cmGrPlH_t h, unsigned id ) +{ + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + cmGrPlObjH_t oh = cmGrPlObjNullHandle; + cmGrPlotObj_t* op = p->list; + + for(; op!=NULL; op=op->next) + if( cmGrObjId(op->grObjH) == id ) + { + oh.h = op; + break; + } + + return oh; +} + +unsigned cmGrPlotObjectCount( cmGrPlH_t h ) +{ + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + cmGrPlotObj_t* op = p->list; + unsigned n = 0; + for(; op!=NULL; ++n ) + op=op->next; + return n; +} + +cmGrPlObjH_t cmGrPlotObjectIndexToHandle( cmGrPlH_t h, unsigned index ) +{ + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + cmGrPlotObj_t* op = p->list; + unsigned i = 0; + cmGrPlObjH_t oh = cmGrPlObjNullHandle; + + for(; inext; + + if( op != NULL ) + oh.h = op; + + return oh; +} + + +void cmGrPlotKeyEvent( cmGrPlH_t h, cmGrH_t grH, unsigned eventFlags, cmGrKeyCodeId_t keycode ) +{ + assert( cmIsFlag(eventFlags,kKeyDnGrFl | kKeyUpGrFl)); + + cmGrPl_t* p = _cmGrPlHandleToPtr(h); + if( p->fop != NULL && cmHandlesAreEqual(p->fop->grH,grH) ) + { + cmGrObjFuncArgs_t a; + memset(&a,0,sizeof(a)); + a.cbArg = p->fop; + a.ctx = p->ctx; + a.grH = grH; + a.objH = p->fop->grObjH; + + _cmGrPlotObjEvent(&a, eventFlags, keycode, 0, 0 ); + } +} diff --git a/cmGrPlot.h b/cmGrPlot.h new file mode 100644 index 0000000..7f965aa --- /dev/null +++ b/cmGrPlot.h @@ -0,0 +1,260 @@ +#ifndef cmGrTimeLine_h +#define cmGrTimeLine_h + +#ifdef __cplusplus +extern "C" { +#endif + + enum + { + kOkGrPlRC, + kObjNotFoundGrPlRC, + kGrFailGrPlRC, + kRsrcFailGrPlRC + }; + + typedef enum + { + kInvalidPlId, + kRectGrPlId, + kHLineGrPlId, + kVLineGrPlId, + kLineGrPlId, + kEllipseGrPlId, + kDiamondGrPlId, + kUTriGrPlId, + kDTriGrPlId, + kLTriGrPlId, + kRTriGrPlId, + kStarGrPlId, + kCrossGrPlId, + kPlusGrPlId, + } cmGrPlObjTypeId_t; + + // object cfg flags + enum + { + kSymbolGrPlFl = 0x0001, // This object is a symbol + kNoSelectGrPlFl = 0x0002, // The clicking with the mouse will not select this object + kNoDragGrPlFl = 0x0004, // Dragging with the mouse will not move this object + kNoFocusGrPlFl = 0x0008, // This object cannot receive focus. + kNoDrawGrPlFl = 0x0010, // Do not draw this object (is is invisible and disabled) + kNoFillGrPlFl = 0x0020, // Do not draw the fill area of this object + kNoBorderGrPlFl = 0x0040, // Do not draw the border of this object + kNoLabelGrPlFl = 0x0080, // Do not draw the label for this object + }; + + // object state flags + enum + { + kVisibleGrPlFl = 0x0001, + kEnabledGrPlFl = 0x0002, // Enabled obj's must be visible. + kSelectGrPlFl = 0x0004, // + kFocusGrPlFl = 0x0008 // Focused obj's are also selected. + }; + + typedef enum + { + kFocusPlGrId, + kSelectPlGrId, + kEnablePlGrId, + kDisablePlGrId, + + kMaxPlGrId + } cmGrPlStateId_t; + + enum // TODO: change these into cmGrPlotH_t user settable variables + { + kDefaultSymW = 9, + kDefaultSymH = 9 + }; + + typedef cmHandle_t cmGrPlH_t; + typedef cmHandle_t cmGrPlObjH_t; + typedef cmRC_t cmGrPlRC_t; + + // Plot object callback id's + typedef enum + { + kCreatedCbSelGrPlId, + kDestroyedCbSelGrPlId, + kPreEventCbSelGrPlId, + kEventCbSelGrPlId, + kStateChangeGrPlId, // See Note with cmGrPlotCbFunc_t below. + } cmGrPlCbSelId_t; + + + typedef struct + { + cmCtx_t* ctx; // Global program context. + void* cbArg; // User pointer set in cmGrPlotObjSetCb(). + cmGrPlCbSelId_t selId; // Callback selector id. See cmGrPlCbSeId_t. + cmGrPlObjH_t objH; // The plot object handle. + unsigned eventFlags; // Event flags from canvas callback (See cmGrEvent() flags) + cmGrKeyCodeId_t eventKey; // Event keys (See the cmGrEvent() keys) + int eventX; // Mouse X,Y location when event was generated (Same as cmGrEvent()) + int eventY; // + unsigned deltaFlags; // Event caused an object state change (See kXXXGrPlFl flags) + } cmGrPlotCbArg_t; + + + // Return 'false' from kPreEventCbGrPlSelId events to prevent default processing. + // Note: + // When this callback is made with the 'kStateChangeGrPlId' the state of + // the object has not yet been changed. This may be confusing because if + // the state of the object is queried inside the callback it will have the + // pre-change state - but this state will be automatically toggled when the + // callback returns 'true'. + typedef bool (*cmGrPlotCbFunc_t)( cmGrPlotCbArg_t* arg ); + + extern cmGrPlH_t cmGrPlNullHandle; + extern cmGrPlObjH_t cmGrPlObjNullHandle; + + // Notes: + // 1) Set kSymbolGrPlFl to create a symbol. + // 2) If kSymbolGrPlFl is set then w and h are taken as the physical size + // of the symbol. Set w and h to 0 to use the default symbols size + // kDefaultSymW, kDefaultSymH + + cmGrPlRC_t cmGrPlotObjCreate( + cmGrPlH_t plH, // Owner Plot Object Manager. See cmGrPlotCreate(). + cmGrH_t grH, // The canvas this object will be drawn on. + cmGrPlObjH_t* ohp, // Pointer to the new objects handle (optional) + unsigned id, // User defined identifier. + cmGrPlObjH_t parentObjH, // Containing parent object. + cmGrPlObjH_t xAnchorObjH, // x is taken as an offset from this obj's x coord (optional). + cmGrPlObjH_t yAnchorObjH, // y is taken as an offset from this obj's y coord (optional). + cmGrPlObjTypeId_t typeId, // See cmGrPlObjTypeId_t + unsigned cfgFlags, // + cmReal_t x, // Coord's within the parent's world coord system. + cmReal_t y, // + cmReal_t w, // + cmReal_t h, // + const cmChar_t* label, // Object text string (optional) + const cmGrVExt_t* wext ); // This objects internal world extents (optional) + + cmGrPlRC_t cmGrPlotObjDestroy( cmGrPlObjH_t* ohp ); + bool cmGrPlotObjIsValid( cmGrPlObjH_t oh ); + + cmGrPlH_t cmGrPlotObjMgrHandle( cmGrPlObjH_t oh ); + cmGrObjH_t cmGrPlotObjHandle( cmGrPlObjH_t oh ); + + void cmGrPlotObjSetId( cmGrPlObjH_t oh, unsigned id ); + unsigned cmGrPlotObjId( cmGrPlObjH_t oh ); + + void cmGrPlotObjSetUserPtr( cmGrPlObjH_t oh, void* userPtr ); + void* cmGrPlotObjUserPtr( cmGrPlObjH_t oh ); + + void cmGrPlotObjSetLabel( cmGrPlObjH_t oh, const cmChar_t* label ); + const cmChar_t* cmGrPlotObjLabel( cmGrPlObjH_t oh ); + // Set flags to kXXXJsGrFl values. See cmGrDrawTextJustify for their meaning. + // 'color' is optional + void cmGrPlotObjSetLabelAttr( cmGrPlObjH_t oh, unsigned flags, int angle, const cmGrColor_t color ); + unsigned cmGrPlotObjLabelFlags( cmGrPlObjH_t oh ); + int cmGrPlotObjLabelAngle( cmGrPlObjH_t oh ); + const cmGrColor_t cmGrPlotObjLabelColor( cmGrPlObjH_t oh ); + + + void cmGrPlotObjSetStateFlags( cmGrPlObjH_t oh, unsigned flags ); + unsigned cmGrPlotObjStateFlags( cmGrPlObjH_t oh ); + + void cmGrPlotObjSetCfgFlags( cmGrPlObjH_t oh, unsigned flags ); + void cmGrPlotObjClrCfgFlags( cmGrPlObjH_t oh, unsigned flags ); + void cmGrPlotObjTogCfgFlags( cmGrPlObjH_t oh, unsigned flags ); + unsigned cmGrPlotObjCfgFlags( cmGrPlObjH_t oh ); + + cmGrPlRC_t cmGrPlotObjSetPhysExt( cmGrPlObjH_t oh, int loffs, int toffs, int roffs, int boffs ); + void cmGrPlotObjPhysExt( cmGrPlObjH_t oh, int* loffs, int* toffs, int* roffs, int* boffs ); + + void cmGrPlotObjVExt( cmGrPlObjH_t oh, cmGrVExt_t* vext ); + + void cmGrPlotObjSetFontFamily( cmGrPlObjH_t h, unsigned id ); + unsigned cmGrPlotObjFontFamily( cmGrPlObjH_t h ); + void cmGrPlotObjSetFontSize( cmGrPlObjH_t h, unsigned size ); + unsigned cmGrPlotObjFontSize( cmGrPlObjH_t h ); + void cmGrPlotObjSetFontStyle( cmGrPlObjH_t h, unsigned flags ); + unsigned cmGrPlotObjFontStyle( cmGrPlObjH_t h ); + void cmGrPlotObjSetFont( cmGrPlObjH_t h, unsigned id, unsigned size, unsigned style ); + + void cmGrPlotObjSetLineColor( cmGrPlObjH_t h, cmGrPlStateId_t id, const cmGrColor_t c ); + const cmGrColor_t cmGrPlotObjLineColor( cmGrPlObjH_t h, cmGrPlStateId_t id ); + const cmGrColor_t cmGrPlotObjCurLineColor( cmGrPlObjH_t h ); + + void cmGrPlotObjSetFillColor( cmGrPlObjH_t h, cmGrPlStateId_t id, const cmGrColor_t c ); + const cmGrColor_t cmGrPlotObjFillColor( cmGrPlObjH_t h, cmGrPlStateId_t id ); + const cmGrColor_t cmGrPlotObjCurFillColor( cmGrPlObjH_t h ); + + void cmGrPlotObjSetCb( cmGrPlObjH_t h, cmGrPlotCbFunc_t func, void* arg ); + cmGrPlotCbFunc_t cmGrPlotObjCbFunc( cmGrPlObjH_t h ); + void* cmGrPlotObjCbArg( cmGrPlObjH_t h ); + + + // Draw aH above bH in the z-order. + void cmGrPlotObjDrawAbove( cmGrPlObjH_t bH, cmGrPlObjH_t aH ); + + //---------------------------------------------------------------------------- + // Plot Object Manager Functions + //---------------------------------------------------------------------------- + cmGrPlRC_t cmGrPlotCreate( cmCtx_t* ctx, cmGrPlH_t* hp ); + cmGrPlRC_t cmGrPlotDestroy( cmGrPlH_t* hp ); + bool cmGrPlotIsValid( cmGrPlH_t h ); + cmGrPlRC_t cmGrPlotClear( cmGrPlH_t h ); // destroy all objects + cmErr_t* cmGrPlotErr( cmGrPlH_t h ); + cmRpt_t* cmGrPlotRpt( cmGrPlH_t h ); + unsigned cmGrPlotObjectCount( cmGrPlH_t h ); + cmGrPlObjH_t cmGrPlotObjectIndexToHandle( cmGrPlH_t h, unsigned index ); + cmGrPlObjH_t cmGrPlotObjectIdToHandle( cmGrPlH_t h, unsigned id ); + + // Pass a keyboard event to the plot system. + void cmGrPlotKeyEvent( cmGrPlH_t h, cmGrH_t grH, unsigned eventFlags, cmGrKeyCodeId_t keycode ); + + + +#ifdef __cplusplus +} +#endif + +#endif + + +/* +Plot Object Attributes: + +Location: x,y +Size: w,h + +Shape: + rectangle: + ellipse: + line: + hline: + vline: + symbol: + +Parent: Defines the world coordinate system in which this object is drawn. +Children are always fully contained by their parent and may not be dragged +outside of their parent. + +Label: +Border Color: One per state (enabled,selected,focused) +Fill Color: One per state (enabled,selected,focused) +State: + Visible: Draw this object. + Enabled: Disabled objects cannot be selected,focused, or dragged. + Selected: Multiple objects may be selected. + Focused: Only one object may be focused. + +Physical Offsets: Physical offsets which expand the size of the object. +Font Family: +Font Style: +Font Size: +Cfg Flags: + No Drag: Do not allow dragging. + No Select: Do not allow selection. + No Focus: Do not allow focusing. + No Draw: Do not draw this object (automatically disabled the object) + No Fill: Do not draw fill color. + No Border: Do not draw border. + No Label: Do not draw label. + + */ diff --git a/cmGrPlotAudio.c b/cmGrPlotAudio.c new file mode 100644 index 0000000..6267295 --- /dev/null +++ b/cmGrPlotAudio.c @@ -0,0 +1,180 @@ +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmGr.h" +#include "cmGrDevCtx.h" +#include "cmGrPlot.h" + +#include "cmAudioFile.h" +#include "cmAudioFileMgr.h" +#include "cmGrPlotAudio.h" + +typedef struct +{ + cmGrPlObjH_t oH; + cmAfmFileH_t afH; + unsigned chIdx; + + cmGrRenderObjCb_t renderCbFunc; + void* renderCbArg; + + cmGrDestroyObjCb_t destroyCbFunc; + void* destroyCbArg; + + cmGrIsInsideObjCb_t isInsideCbFunc; + void* isInsideCbArg; + + void* mem; + cmGrPExt_t pext; + unsigned pixN; + cmSample_t* fMinV; + cmSample_t* fMaxV; + int* iMinV; + int* iMaxV; + +} cmGrPlObjAf_t; + +cmGrPlRC_t _cmGrPlObjAfCalcImage( cmGrPlObjAf_t* op, cmGrH_t grH ) +{ + cmGrPlRC_t rc = kOkGrPlRC; + cmGrObjH_t grObjH = cmGrPlotObjHandle(op->oH); + cmGrVExt_t vwExt,objExt,drExt; + + // get the intersection of the view and this audio object + cmGrViewExtents( grH, &vwExt ); + cmGrPlotObjVExt( op->oH, &objExt ); + cmGrVExtIntersect(&drExt,&vwExt,&objExt); + + // if the audio object is visible + if( cmGrVExtIsNotNullOrEmpty(&drExt) ) + { + // get the extents of the visible portion of the audio object + cmGrVExt_VtoP( grH, cmGrPlotObjHandle(op->oH), &drExt, &op->pext); + + // store the count of horizontal pixels + op->pixN = op->pext.sz.w; + + // allocate a cache to hold the image data + unsigned byteCnt = op->pixN * 2 * sizeof(int) + op->pixN * 2 * sizeof(cmSample_t); + op->mem = cmMemResize(char,op->mem,byteCnt); + op->fMinV = (cmSample_t*)op->mem; + op->fMaxV = op->fMinV + op->pixN; + op->iMinV = (int*)(op->fMaxV + op->pixN); + op->iMaxV = op->iMinV + op->pixN; + assert( op->iMaxV + op->pixN == op->mem + byteCnt ); + + // locate the offset into the file of the first sample to be displayed + unsigned si = 0; + if( drExt.loc.x > objExt.loc.x ) + si = drExt.loc.x - objExt.loc.x; + + + // get the floating point audio summary signal + if( cmAfmFileGetSummary( op->afH, op->chIdx, si, drExt.sz.w, op->fMinV, op->fMaxV, op->pixN ) != kOkAfmRC ) + { + const cmChar_t* afn = cmAudioFileName( cmAfmFileHandle(op->afH)); + rc = cmErrMsg( cmGrPlotErr( cmGrPlotObjMgrHandle(op->oH) ), kRsrcFailGrPlRC, "Audio file summary read failure on '%s'.",afn); + goto errLabel; + } + + unsigned i; + // convert the summary to pixels values + for(i=0; ipixN; ++i) + { + // Note the reversal of min and max during the conversion. + op->iMaxV[i] = cmGrY_VtoP( grH, grObjH, op->fMinV[i] ); + op->iMinV[i] = cmGrY_VtoP( grH, grObjH, op->fMaxV[i] ); + } + } + errLabel: + return rc; +} + + +bool _cmGrPlObjAfRender( cmGrObjFuncArgs_t* args, cmGrDcH_t dcH ) +{ + cmGrPlObjAf_t* op = (cmGrPlObjAf_t*)args->cbArg; + + if( _cmGrPlObjAfCalcImage(op, args->grH ) == kOkGrPlRC ) + { + int i; + cmGrPExt_t pext; + cmGrPhysExtents( args->grH, &pext); + + cmGrDcSetColor(dcH, cmGrPlotObjCurLineColor(op->oH)); + + // draw a horz line at y=0 + int y0 = cmGrY_VtoP( args->grH, cmGrPlotObjHandle(op->oH), 0.0 ); + cmGrDcDrawLine(dcH, cmGrPExtL(&op->pext), y0, cmGrPExtR(&op->pext) , y0 ); + + // draw a vertical line for each + for(i=0; ipixN; ++i) + cmGrDcDrawLine(dcH, op->pext.loc.x+i, op->iMinV[i], op->pext.loc.x+i, op->iMaxV[i] ); + + + // draw a rectangle around the entire audio clip + cmGrDcDrawRect(dcH, op->pext.loc.x, cmGrPExtT(&pext), op->pext.sz.w, cmGrPExtB(&pext) ); + + // draw the file label + cmGrDcDrawTextJustify( dcH, cmGrPlotObjFontFamily(op->oH), cmGrPlotObjFontSize(op->oH), cmGrPlotObjFontStyle(op->oH), cmGrPlotObjLabel(op->oH), &op->pext, kHorzCtrJsGrFl | kTopJsGrFl ); + + } + return true; +} + +bool _cmGrPlObjAfIsInside( cmGrObjFuncArgs_t* args, int px, int py, cmGrV_t vx, cmGrV_t vy ) +{ + cmGrPlObjAf_t* op = (cmGrPlObjAf_t*)args->cbArg; + + if( cmGrPExtIsXyInside( &op->pext, px, py ) ) + { + px -= op->pext.loc.x; + if( 0 <= px && px < op->pixN ) + return op->iMinV[px] <= py && py <= op->iMaxV[px]; + + } + + return false; +} + +void _cmGrPlObjAfDestroy( cmGrObjFuncArgs_t* args ) +{ + cmGrPlObjAf_t* op = (cmGrPlObjAf_t*)args->cbArg; + args->cbArg = op->destroyCbArg; + op->destroyCbFunc(args); + cmMemFree(op->mem); + cmMemFree(op); +} + +cmGrPlRC_t cmGrPlotAudioFileObjCreate( + cmGrPlObjH_t oH, + cmAfmFileH_t afH, + unsigned audioChIdx ) +{ + cmGrPlObjAf_t* op = cmMemAllocZ(cmGrPlObjAf_t,1); + op->oH = oH; + op->afH = afH; + op->chIdx = audioChIdx; + + cmGrObjH_t grObjH = cmGrPlotObjHandle(op->oH); + + op->renderCbFunc = cmGrObjRenderCbFunc(grObjH); + op->renderCbArg = cmGrObjRenderCbArg(grObjH); + cmGrObjSetRenderCb( grObjH, _cmGrPlObjAfRender, op ); + + op->destroyCbFunc = cmGrObjDestroyCbFunc(grObjH); + op->destroyCbArg = cmGrObjDestroyCbArg(grObjH); + cmGrObjSetDestroyCb( grObjH, _cmGrPlObjAfDestroy, op ); + + op->isInsideCbFunc = cmGrObjIsInsideCbFunc(grObjH); + op->isInsideCbArg = cmGrObjIsInsideCbArg(grObjH); + cmGrObjSetIsInsideCb( grObjH, _cmGrPlObjAfIsInside, op ); + + cmGrPlotObjSetUserPtr(oH,op); + + return kOkGrPlRC; +} diff --git a/cmGrPlotAudio.h b/cmGrPlotAudio.h new file mode 100644 index 0000000..0df091d --- /dev/null +++ b/cmGrPlotAudio.h @@ -0,0 +1,19 @@ +#ifndef cmGrPlotAudio_h +#define cmGrPlotAudio_h + + +#ifdef __cplusplus +extern "C" { +#endif + + + cmGrPlRC_t cmGrPlotAudioFileObjCreate( + cmGrPlObjH_t oH, + cmAfmFileH_t afH, + unsigned audioChIdx ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cmJson.c b/cmJson.c new file mode 100644 index 0000000..52952a5 --- /dev/null +++ b/cmJson.c @@ -0,0 +1,4077 @@ +#include "cmPrefix.h" +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmJson.h" +#include "cmMem.h" +#include "cmMallocDebug.h" +#include "cmLex.h" +#include "cmLinkedHeap.h" +#include "cmFile.h" + + +enum +{ + kLCurlyLexTId = kUserLexTId+1, + kRCurlyLexTId, + kLHardLexTId, + kRHardLexTId, + kColonLexTId, + kCommaLexTId, + kTrueLexTId, + kFalseLexTId, + kNullLexTId +}; + + +typedef struct +{ + unsigned id; + const char* text; +} cmJsToken_t; + +// serialization buffer header +typedef struct +{ + unsigned id; // always set to 'json' + unsigned byteCnt; // count of bytes following this field (total buf bytes = byteCnt + (2*sizeof(unsigned))) + unsigned nodeCnt; // count of nodes in this buffer + unsigned version; // buffer serialization version number +} cmJsSerialHdr_t; + +// serialization helper record +typedef struct +{ + cmJsSerialHdr_t hdr; + char* basePtr; + char* nextPtr; + char* endPtr; +} cmJsSerial_t; + +// deserialization helper record +typedef struct +{ + unsigned nodeCnt; + unsigned nodeIdx; + const char* nextPtr; + const char* endPtr; + +} cmJsDeserial_t; + + +typedef struct +{ + cmErr_t err; // + cmLexH lexH; // parsing lexer + cmLHeapH_t heapH; // linked heap stores all node memory + cmJsonNode_t* rootPtr; // root of internal node tree + cmJsonNode_t* basePtr; // base of parsing stack + cmJsonNode_t* lastPtr; // top of parsing stack + cmJsRC_t rc; // last error code + char* serialBufPtr; // serial buffer pointer + unsigned serialByteCnt; // count of bytes in serialBuf[] + bool reportErrPosnFl;// report the file posn of syntax errors +} cmJs_t; + +cmJsToken_t _cmJsTokenArray[] = +{ + { kLCurlyLexTId, "{" }, + { kRCurlyLexTId, "}" }, + { kLHardLexTId, "[" }, + { kRHardLexTId, "]" }, + { kColonLexTId, ":" }, + { kCommaLexTId, "," }, + { kTrueLexTId, "true"}, + { kFalseLexTId, "false"}, + { kNullLexTId, "null" }, + { kErrorLexTId,""} + +}; + +cmJsToken_t _cmJsNodeTypeLabel[] = +{ + { kObjectTId, "object" }, + { kPairTId, "pair" }, + { kArrayTId, "array" }, + { kStringTId, "string" }, + { kIntTId, "int" }, + { kRealTId, "real" }, + { kNullTId, "null" }, + { kTrueTId, "true" }, + { kFalseTId, "false" }, + { kInvalidTId,"invalid"} +}; + +cmJsonH_t cmJsonNullHandle = cmSTATIC_NULL_HANDLE; + +cmJsRC_t _cmJsonRemoveNode( cmJs_t* p, cmJsonNode_t* np, bool freeFl, bool balancePairsFl ); +void _cmJsonFreeNode( cmJs_t* p, cmJsonNode_t* np); + +const char* _cmJsonNodeTypeIdToLabel( unsigned nodeTypeId ) +{ + unsigned i; + for(i=0; _cmJsNodeTypeLabel[i].id != kInvalidTId; ++i) + if( _cmJsNodeTypeLabel[i].id == nodeTypeId ) + break; + return _cmJsNodeTypeLabel[i].text; +} + +unsigned _cmJsonNodeTypeLabelToId( const char* typeLabel ) +{ + unsigned i; + for(i=0; _cmJsNodeTypeLabel[i].id != kInvalidTId; ++i) + if( strcmp(_cmJsNodeTypeLabel[i].text,typeLabel) == 0 ) + break; + + return _cmJsNodeTypeLabel[i].id; + +} + +cmJs_t* _cmJsonHandleToPtr( cmJsonH_t h ) +{ + cmJs_t* p = (cmJs_t*)h.h; + assert(p != NULL); + p->rc = kOkJsRC; + return p; +} + + +cmJsRC_t _cmJsonError( cmJs_t* p, cmJsRC_t rc, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = cmErrVMsg(&p->err,rc,fmt,vl); + va_end(vl); + return rc; +} + +cmJsRC_t _cmJsonSyntaxError( cmJs_t* p, const char* fmt, ... ) +{ + int bn = 1024; + char buf[bn+1]; + buf[0]=0; + buf[bn]=0; + + va_list vl; + va_start(vl,fmt); + + if( p->reportErrPosnFl ) + snprintf(buf,bn,"Syntax error on line:%i column:%i. ",cmLexCurrentLineNumber(p->lexH),cmLexCurrentColumnNumber(p->lexH)); + + int n = strlen(buf); + vsnprintf(buf+n,bn-n,fmt,vl); + va_end(vl); + + return cmErrMsg(&p->err,kSyntaxErrJsRC,"%s",buf); +} + + +// Note that the stack is formed by a linked list +// which is chained together using the nodes parentPtr. +// This works beacause the parentPtr for object,array, +// and pair nodes (the only nodes on the stack) is not +// needed as long as the node is on the stack. Once the +// node is popped the parentPtr is then set from the +// new stack top. +cmJsRC_t _cmJsonPushNode( cmJs_t* p, cmJsonNode_t* np ) +{ + np->ownerPtr = p->lastPtr; + p->lastPtr = np; + + if( p->basePtr == NULL ) + p->basePtr = np; + + return kOkJsRC; +} + +cmJsRC_t _cmJsonPopNode( cmJs_t* p ) +{ + if( p->lastPtr == NULL ) + return _cmJsonSyntaxError(p,"A parser stack underlow signalled a syntax error."); + + cmJsonNode_t* t = p->lastPtr; + + // remove the top element + p->lastPtr = p->lastPtr->ownerPtr; + + // set the parent of the popped node + t->ownerPtr = p->lastPtr; + + if( p->lastPtr == NULL ) + p->basePtr = NULL; + + return kOkJsRC; +} + + + +cmJsRC_t cmJsonInitialize( cmJsonH_t* hp, cmCtx_t* ctx ) +{ + cmJsRC_t rc; + cmJs_t* p; + unsigned i; + + // finalize before initialize + if((rc = cmJsonFinalize(hp)) != kOkJsRC ) + return rc; + + // allocate the main object record + if((p = cmMemAllocZ( cmJs_t, 1 )) == NULL ) + return cmErrMsg(&ctx->err,kMemAllocErrJsRC,"Object memory allocation failed."); + + cmErrSetup(&p->err,&ctx->rpt,"JSON Parser"); + + // allocate the linked heap mgr + if( cmLHeapIsValid(p->heapH = cmLHeapCreate(1024,ctx)) == false ) + { + rc = _cmJsonError(p,kMemAllocErrJsRC,"Linked heap object allocation failed."); + goto errLabel; + } + + // allocate the lexer + if(cmLexIsValid(p->lexH = cmLexInit(NULL,0,0,&ctx->rpt)) == false ) + { + rc = _cmJsonError(p,kLexErrJsRC,"Lex allocation failed."); + goto errLabel; + } + + // register json specific tokens with the lexer + for(i=0; _cmJsTokenArray[i].id != kErrorLexTId; ++i) + { + cmRC_t lexRC; + if( (lexRC = cmLexRegisterToken(p->lexH, _cmJsTokenArray[i].id, _cmJsTokenArray[i].text )) != kOkLexRC ) + { + rc = _cmJsonError(p,kLexErrJsRC,"Lex token registration failed for:'%s'.\nLexer Error:%s",_cmJsTokenArray[i].text, cmLexRcToMsg(lexRC) ); + goto errLabel; + } + } + + hp->h = p; + + return kOkJsRC; + + errLabel: + + cmMemPtrFree(&p); + + if( cmLHeapIsValid(p->heapH) ) + cmLHeapDestroy(&p->heapH); + + if( cmLexIsValid(p->lexH) ) + cmLexFinal(&p->lexH); + + return rc; +} + +cmJsRC_t cmJsonInitializeFromFile( cmJsonH_t* hp, const char* fn, cmCtx_t* ctx ) +{ + cmJsRC_t jsRC; + + if((jsRC = cmJsonInitialize(hp,ctx)) != kOkJsRC ) + return jsRC; + + if((jsRC = cmJsonParseFile(*hp,fn,NULL)) != kOkJsRC ) + cmJsonFinalize(hp); + + return jsRC; +} + +cmJsRC_t cmJsonInitializeFromBuf( cmJsonH_t* hp, cmCtx_t* ctx, const char* buf, unsigned bufByteCnt ) +{ + cmJsRC_t jsRC; + + if((jsRC = cmJsonInitialize(hp,ctx)) != kOkJsRC ) + return jsRC; + + if((jsRC = cmJsonParse(*hp,buf,bufByteCnt,NULL)) != kOkJsRC ) + cmJsonFinalize(hp); + + return jsRC; +} + +cmJsRC_t cmJsonFinalize( cmJsonH_t* hp ) +{ + cmRC_t lexRC; + + if( hp == NULL || hp->h == NULL ) + return kOkJsRC; + + cmJs_t* p = _cmJsonHandleToPtr(*hp); + + // free the internal heap object + cmLHeapDestroy( &p->heapH ); + + // free the lexer + if( cmLexIsValid(p->lexH) ) + if((lexRC = cmLexFinal(&p->lexH)) != kOkLexRC ) + return _cmJsonError(p,kLexErrJsRC,"Lexer finalization failed.\nLexer Error:%s",cmLexRcToMsg(lexRC)); + + cmMemPtrFree(&p->serialBufPtr); + + // free the handle + cmMemPtrFree(&hp->h); + + return kOkJsRC; +} + + + +bool cmJsonIsValid( cmJsonH_t h ) +{ return h.h != NULL; } + +cmJsRC_t _cmJsonLinkInChild( cmJs_t* p, cmJsonNode_t* parentPtr, cmJsonNode_t* np ) +{ + cmJsRC_t rc = kOkJsRC; + + np->ownerPtr = parentPtr; + + switch( parentPtr->typeId ) + { + case kObjectTId: + case kArrayTId: + case kPairTId: + { + // if the parent is an 'object' then the child must be a 'pair' + if( parentPtr->typeId == kObjectTId && np->typeId != kPairTId ) + rc = _cmJsonSyntaxError(p,"Expect only 'pair' nodes as children of 'objects'."); + + // if the parent is a 'pair' then is may have a max of two children + if( parentPtr->typeId == kPairTId && cmJsonChildCount(parentPtr) >= 2 ) + rc = _cmJsonSyntaxError(p,"'pair' nodes may only have 2 children."); + + + // insert the new node into the parent child list + + // if the new node is the first child + if( parentPtr->u.childPtr == NULL ) + parentPtr->u.childPtr = np; + else + { + // if the new node is the second or greater child + cmJsonNode_t* lp = parentPtr->u.childPtr; + while( lp->siblingPtr != NULL ) + lp = lp->siblingPtr; + + lp->siblingPtr = np; + } + } + break; + + + + default: + rc = _cmJsonSyntaxError(p,"'%s' nodes cannot be parent nodes.",_cmJsonNodeTypeIdToLabel(parentPtr->typeId)); + break; + } + + return rc; +} + +// This function creates nodes it also: +// 1. inserts array elments into their parents child list +// 2. inserts pairs into their parents member list +// 3. assigns values to pairs +cmJsRC_t _cmJsonCreateNode( cmJs_t* p, cmJsonNode_t* parentPtr, unsigned newNodeTypeId, cmJsonNode_t** npp ) +{ + cmJsRC_t rc = kOkJsRC; + + cmJsonNode_t* np; + + assert( npp != NULL ); + + *npp = NULL; + + // allocate the new node + if((np = cmLHeapAllocZ( p->heapH, sizeof(cmJsonNode_t) )) == NULL ) + return _cmJsonError(p,kMemAllocErrJsRC,"Error allocating node memory."); + + // set the new node type + np->typeId = newNodeTypeId; + np->ownerPtr = parentPtr; + + if( parentPtr == NULL ) + { + if( newNodeTypeId != kObjectTId && newNodeTypeId != kArrayTId ) + rc = _cmJsonSyntaxError(p,"'%s' nodes must have a parent node.",_cmJsonNodeTypeIdToLabel(newNodeTypeId)); + } + else + { + // if the parent is an 'object', 'array', or 'pair' then the + // new node must be a child - insert it into the parent child list + if((rc = _cmJsonLinkInChild( p, parentPtr, np )) != kOkJsRC ) + return rc; + + switch( newNodeTypeId ) + { + case kObjectTId: + case kArrayTId: + break; + + case kPairTId: + if( parentPtr == NULL || parentPtr->typeId != kObjectTId ) + rc = _cmJsonSyntaxError(p,"'pair' nodes must be the child of an object 'node'."); + break; + + default: + if( parentPtr == NULL ) + rc = _cmJsonSyntaxError(p,"'%s' nodes must have parent nodes.",_cmJsonNodeTypeIdToLabel(newNodeTypeId)); + } + + } + + // assign the return value + *npp = np; + + if( p->rootPtr == NULL) + { + if( np->typeId != kObjectTId && np->typeId != kArrayTId ) + rc = _cmJsonSyntaxError(p,"The root object must be an 'object' or 'array'."); + + p->rootPtr = np; + } + return rc; +} + + +cmJsRC_t _cmJsonCreateNumber( cmJs_t* p, cmJsonNode_t* parentPtr, unsigned nodeTId, cmJsonNode_t** npp ) +{ + // numbers may only occurr as children of a 'pair' or element of an 'array' + if( (parentPtr==NULL) || (parentPtr->typeId != kPairTId && parentPtr->typeId != kArrayTId) ) + return _cmJsonSyntaxError(p, "The parent of scalar:%*s is not a 'pair' or 'array'.", cmLexTokenCharCount(p->lexH), cmLexTokenText(p->lexH) ); + + return _cmJsonCreateNode(p,parentPtr,nodeTId,npp); + +} + +cmJsRC_t _cmJsonCreateReal(cmJs_t* p, cmJsonNode_t* parentPtr, double v, cmJsonNode_t** newNodePtrPtr ) +{ + cmJsRC_t rc; + cmJsonNode_t* np = NULL; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = NULL; + + if((rc= _cmJsonCreateNumber(p,parentPtr,kRealTId,&np)) == kOkJsRC ) + { + np->u.realVal = v; + + if( newNodePtrPtr != NULL) + *newNodePtrPtr = np; + } + return rc; +} + +cmJsRC_t _cmJsonCreateInt(cmJs_t* p, cmJsonNode_t* parentPtr, int v, cmJsonNode_t** newNodePtrPtr ) +{ + cmJsRC_t rc; + cmJsonNode_t* np = NULL; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = NULL; + + if((rc= _cmJsonCreateNumber(p,parentPtr,kIntTId,&np)) == kOkJsRC ) + { + np->u.intVal = v; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = np; + } + + return rc; +} + +cmJsRC_t _cmJsonCreateBool(cmJs_t* p, cmJsonNode_t* parentPtr, bool fl, cmJsonNode_t** newNodePtrPtr ) +{ + cmJsRC_t rc; + cmJsonNode_t* np = NULL; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = NULL; + + + if((rc= _cmJsonCreateNumber(p,parentPtr,fl?kTrueTId:kFalseTId,&np)) == kOkJsRC ) + { + np->u.boolVal = fl; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = np; + } + + return rc; +} + +cmJsRC_t _cmJsonCreateNull(cmJs_t* p, cmJsonNode_t* parentPtr, cmJsonNode_t** newNodePtrPtr ) +{ + cmJsRC_t rc; + cmJsonNode_t* np = NULL; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = NULL; + + if((rc = _cmJsonCreateNumber(p,parentPtr,kNullTId,&np)) == kOkJsRC ) + { + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = np; + } + return rc; +} + + + +cmJsRC_t _cmJsonEscapeInt( cmJs_t* p, const char* sp, unsigned i, unsigned n, unsigned hexDigitCnt, char* rp ) +{ + char buf[hexDigitCnt+1]; + + memset(buf,0,hexDigitCnt+1); + + // fill buf[] with the next two characters + unsigned j; + for(j=0; i 0xff ) + return _cmJsonError(p,kInvalidHexEscapeJsRC,"Hex escape value is out of range (0x00-0xff)."); + + *rp = (char)val; + + return kOkJsRC; +} + +cmJsRC_t _cmJsonEscapeString( cmJs_t* p, char* dp, const char* sp, unsigned n ) +{ + cmJsRC_t rc; + unsigned hexDigCnt = 2; // count of digits in \u notation + unsigned i,j; + for(i=0,j=0; itypeId == kStringTId ); + + cmJsRC_t rc = kOkJsRC; + + // deallocate the current string + if( np->u.stringVal != NULL ) + { + cmLHeapFree(p->heapH,np->u.stringVal); + np->u.stringVal = NULL; + } + + if( cp != NULL && cn>0 ) + { + + // allocate the node data memory to hold the string + if((np->u.stringVal = cmLHeapAllocZ(p->heapH,cn+1)) == NULL ) + return _cmJsonError(p,kMemAllocErrJsRC,"Unable to allocate stirng memory."); + + // copy the string into the node data memory + if((rc = _cmJsonEscapeString(p, np->u.stringVal, cp, cn)) != kOkJsRC ) + return rc; + + + np->u.stringVal[cn]=0; + } + return rc; +} + +cmJsRC_t _cmJsonCreateString(cmJs_t* p, cmJsonNode_t* parentPtr, const char* cp, unsigned cn, cmJsonNode_t** newNodePtrPtr ) +{ + cmJsRC_t rc; + cmJsonNode_t* np = NULL; + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = NULL; + + // strings may only occurr as children of a 'pair' or element of an 'array' + if( (parentPtr->typeId != kPairTId && parentPtr->typeId != kArrayTId ) ) + return _cmJsonSyntaxError(p, "The parent of string:%*s is not a 'pair', 'array', or 'object'.", cn,cp); + + // create the new node + if((rc = _cmJsonCreateNode(p,parentPtr,kStringTId,&np)) != kOkJsRC ) + return rc; + + if((rc = _cmJsonSetString(p,np,cp,cn)) != kOkJsRC ) + return rc; + + /* + // allocate the node data memory to hold the string + if((np->u.stringVal = cmLHeapAllocZ(p->heapH,cn+1)) == NULL ) + return _cmJsonError(p,kMemAllocErrJsRC,"Unable to allocate stirng memory."); + + if((rc = _cmJsonEscapeString(p, np->u.stringVal, cp, cn)) != kOkJsRC ) + return rc; + + // copy the string into the node data memory + //strncpy(np->u.stringVal,cp,cn); + np->u.stringVal[cn]=0; + */ + + if( newNodePtrPtr != NULL ) + *newNodePtrPtr = np; + + + return rc; +} + +cmJsRC_t _cmJsonCreatePair( cmJs_t* p, cmJsonNode_t* parentPtr, const char* label, cmJsonNode_t** newNodePtrPtr ) +{ + cmJsRC_t rc; + + if((rc = _cmJsonCreateNode(p,parentPtr,kPairTId,newNodePtrPtr)) != kOkJsRC ) + return rc; + + return _cmJsonCreateString( p, *newNodePtrPtr, label, strlen(label), NULL); +} + + +// if fn == NULL then the buffer must contain the text to parse +cmJsRC_t _cmJsonParse(cmJsonH_t h, const char* buf, unsigned bufCharCnt, const cmChar_t* fn, cmJsonNode_t* altRootPtr ) +{ + unsigned lexTId = kErrorLexTId; + cmJs_t* p = _cmJsonHandleToPtr(h); + cmJsonNode_t* cnp = altRootPtr == p->rootPtr ? altRootPtr : p->rootPtr; + cmJsonNode_t* nnp = NULL; + cmJsRC_t rc; + + p->reportErrPosnFl = true; + + // assign the text buffer and reset the lexer + if( fn == NULL ) + rc = cmLexSetTextBuffer( p->lexH, buf, bufCharCnt ); + else + rc = cmLexSetFile( p->lexH, fn ); + + if( rc != kOkLexRC ) + return _cmJsonError( p, kLexErrJsRC, "Error setting lexer buffer."); + + // get the next token + while( (lexTId = cmLexGetNextToken( p->lexH )) != kErrorLexTId && (lexTId != kEofLexTId ) && (rc==kOkJsRC) ) + { + cnp = p->lastPtr; + + // if cnp is a pair and it's value has been assigned + if( cnp != NULL && cnp->typeId == kPairTId && cmJsonChildCount(cnp)==2 ) + { + if((rc = _cmJsonPopNode(p)) != kOkJsRC ) + break; + + cnp = p->lastPtr; + } + + switch( lexTId ) + { + case kRealLexTId: // real number + rc = _cmJsonCreateReal(p, cnp, cmLexTokenDouble( p->lexH ), NULL); + break; + + case kHexLexTId: // hexidecimal integer + // allow hex integers to be equivalent to decimal integers + + case kIntLexTId: // decimal integer + rc = _cmJsonCreateInt(p, cnp, cmLexTokenInt( p->lexH ), NULL ); + break; + + case kTrueLexTId: // true + rc = _cmJsonCreateBool(p, cnp, true, NULL ); + break; + + case kFalseLexTId: // false + rc = _cmJsonCreateBool(p, cnp, false, NULL ); + break; + + case kNullLexTId: // null + rc = _cmJsonCreateNull(p, cnp, NULL ); + break; + + case kIdentLexTId: // identifier + // allow identifiers to be equivalent to strings. + + case kQStrLexTId: // quoted string + if( cnp == NULL ) + rc = _cmJsonSyntaxError(p,"Encountered a 'string' with no parent."); + else + if( cnp->typeId == kObjectTId ) + { + if((rc = _cmJsonCreateNode(p,cnp,kPairTId,&nnp)) == kOkJsRC ) + { + _cmJsonPushNode(p,nnp); + cnp = nnp; + } + + //if((rc = _cmJsonCreateNewParent(p,cnp,kPairTId)) == kOkJsRC ) + // cnp = p->lastPtr; + } + + if( rc == kOkJsRC ) + rc = _cmJsonCreateString(p, cnp, cmLexTokenText(p->lexH), cmLexTokenCharCount(p->lexH), NULL); + break; + + case kColonLexTId: + if( cnp->typeId != kPairTId ) + rc = _cmJsonSyntaxError(p,"A colon was found outside of a 'pair' element."); + break; + + case kLCurlyLexTId: // { + //rc = _cmJsonCreateNewParent(p, cnp, kObjectTId ); + if((rc = _cmJsonCreateNode(p,cnp,kObjectTId,&nnp)) == kOkJsRC ) + _cmJsonPushNode(p,nnp); + break; + + case kRCurlyLexTId: // } + if( cnp == NULL || cnp->typeId != kObjectTId ) + rc = _cmJsonSyntaxError(p,"A '}' was found without an accompanying opening bracket."); + else + rc = _cmJsonPopNode(p); + break; + + case kLHardLexTId: // [ + //rc = _cmJsonCreateNewParent(p, cnp, kArrayTId); + if((rc = _cmJsonCreateNode(p,cnp,kArrayTId,&nnp)) == kOkJsRC ) + _cmJsonPushNode(p,nnp); + break; + + case kRHardLexTId: // ] + if( cnp == NULL || cnp->typeId != kArrayTId ) + rc = _cmJsonSyntaxError(p,"A ']' was found without an accompanying opening bracket."); + else + rc = _cmJsonPopNode(p); + break; + + case kCommaLexTId: // , + if( (cnp==NULL) || (cnp->typeId != kArrayTId && cnp->typeId != kObjectTId) ) + rc = _cmJsonSyntaxError(p,"Commas may only occur in 'array' and 'object' nodes."); + + break; + + + case kSpaceLexTId: // white space + case kBlockCmtLexTId: // block comment + case kLineCmtLexTId: // line comment + assert(0); + break; + + default: + break; + } + } + + if( lexTId == kErrorLexTId ) + rc = _cmJsonSyntaxError( p, "The lexer failed: %s.", cmLexRcToMsg(cmLexErrorRC(p->lexH))); + + p->reportErrPosnFl = false; + + return rc; +} + +cmJsRC_t cmJsonParse( cmJsonH_t h, const char* buf, unsigned bufCharCnt, cmJsonNode_t* altRootPtr ) +{ return _cmJsonParse(h,buf,bufCharCnt,NULL,altRootPtr); } + +cmJsRC_t cmJsonParseFile( cmJsonH_t h, const char* fn, cmJsonNode_t* altRootPtr ) +{ return _cmJsonParse(h,NULL,0,fn,altRootPtr); } + +/* +cmJsRC_t cmJsonParseFile( cmJsonH_t h, const char* fn ) +{ + cmJsRC_t rc = kOkJsRC; + FILE* fp = NULL; + cmJs_t* p = _cmJsonHandleToPtr(h); + unsigned n = 0; + char* textBuf = NULL; + + assert( fn != NULL && p != NULL ); + + // open the file + if((fp = fopen(fn,"rb")) == NULL ) + return _cmJsonError(p,kFileOpenErrJsRC,"Unable to open the file:'%s'.",fn); + + // seek to the end + if( fseek(fp,0,SEEK_END) != 0 ) + { + rc= _cmJsonError(p,kFileSeekErrJsRC,"Unable to seek to the end of '%s'.",fn); + goto errLabel; + } + + // get the length of the file + if( (n=ftell(fp)) == 0 ) + { + rc = _cmJsonError(p,kFileOpenErrJsRC,"The file '%s' appears to be empty.",fn); + goto errLabel; + } + + // rewind the file + if( fseek(fp,0,SEEK_SET) != 0 ) + { + rc = _cmJsonError(p,kFileSeekErrJsRC,"Unable to seek to the beginning of '%s'.",fn); + goto errLabel; + } + + // allocate the text buffer + if((textBuf = cmMemAllocZ( char, n+1)) == NULL ) + { + rc = _cmJsonError(p,kMemAllocErrJsRC,"Unable to allocate the text file buffer for:'%s'.",fn); + goto errLabel; + } + + // read the file into the text buffer + if( fread(textBuf,n,1,fp) != 1 ) + { + rc = _cmJsonError(p,kFileReadErrJsRC,"File read failed on:'%s'.",fn); + goto errLabel; + } + + rc = cmJsonParse(h,textBuf,n,NULL); + + errLabel: + + // close the file + if( fclose(fp) != 0 ) + { + rc = _cmJsonError(p,kFileCloseErrJsRC,"File close failed on:'%s'.",fn); + goto errLabel; + } + + // free the buffer + if( textBuf != NULL ) + cmMemFree(textBuf); + + return rc; +} +*/ + +cmJsonNode_t* cmJsonRoot( cmJsonH_t h ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return p->rootPtr; +} + + +cmJsRC_t cmJsonClearTree( cmJsonH_t h ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + + p->rootPtr = NULL; + p->basePtr = NULL; + p->lastPtr = NULL; + + cmLHeapClear(p->heapH,true); + + return kOkJsRC; +} + +bool cmJsonIsObject( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kObjectTId); } +bool cmJsonIsArray( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kArrayTId); } +bool cmJsonIsPair( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kPairTId); } +bool cmJsonIsString( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kStringTId); } +bool cmJsonIsInt( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kIntTId); } +bool cmJsonIsReal( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kRealTId); } +bool cmJsonIsBool( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kTrueTId | kFalseTId); } + + + +unsigned cmJsonChildCount( const cmJsonNode_t* np ) +{ + if( np == NULL ) + return 0; + + unsigned n = 0; + switch( np->typeId ) + { + case kObjectTId: + case kArrayTId: + case kPairTId: + { + const cmJsonNode_t* lp = np->u.childPtr; + + while( lp != NULL ) + { + ++n; + lp = lp->siblingPtr; + } + + } + break; + + default: + break; + } + return n; +} + +cmJsonNode_t* cmJsonArrayElement( cmJsonNode_t* np, unsigned index ) +{ + unsigned i; + + assert( index < cmJsonChildCount(np)); + + np = np->u.childPtr; + + for(i=0; isiblingPtr; + + return np; +} + +const cmJsonNode_t* cmJsonArrayElementC( const cmJsonNode_t* np, unsigned index ) +{ return cmJsonArrayElement( (cmJsonNode_t*)np, index ); } + +const char* cmJsonPairLabel( const cmJsonNode_t* pairPtr ) +{ + assert( pairPtr->typeId == kPairTId ); + + if( pairPtr->typeId != kPairTId ) + return NULL; + + return pairPtr->u.childPtr->u.stringVal; +} + +unsigned cmJsonPairTypeId( const cmJsonNode_t* pairPtr ) +{ + assert( pairPtr->typeId == kPairTId ); + return pairPtr->u.childPtr->siblingPtr->typeId; +} + +cmJsonNode_t* cmJsonPairValue( cmJsonNode_t* pairPtr ) +{ + assert( pairPtr->typeId == kPairTId ); + + if( pairPtr->typeId != kPairTId ) + return NULL; + + return pairPtr->u.childPtr->siblingPtr; + +} + +cmJsonNode_t* cmJsonFindValue( cmJsonH_t h, const char* label, const cmJsonNode_t* np, unsigned keyTypeMask ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + + if( np == NULL ) + np = p->rootPtr; + + if( np == NULL ) + return NULL; + + // we are only interested in pairs + if( np->typeId == kPairTId ) + { + // pairs must have exactly two nodes - the first must be a string + assert(np->u.childPtr != NULL && np->u.childPtr->typeId == kStringTId && np->u.childPtr->siblingPtr != NULL ); + + if( strcmp(cmJsonPairLabel(np),label) == 0 ) + { + if( (keyTypeMask==kInvalidTId) || (keyTypeMask & np->u.childPtr->siblingPtr->typeId) ) + return np->u.childPtr->siblingPtr; + } + } + + // if the node is an object,array, or pair ... + if( np->typeId==kObjectTId || np->typeId==kArrayTId || np->typeId==kPairTId ) + { + // ... then recurse on its children + cmJsonNode_t* cnp = np->u.childPtr; + while(cnp != NULL) + { + cmJsonNode_t* rp; + + if((rp = cmJsonFindValue(h,label,cnp,keyTypeMask)) != NULL ) + return rp; + + cnp = cnp->siblingPtr; + } + } + + return NULL; +} + + +cmJsRC_t _cmJsonFindPathValue( cmJs_t* p, const char* pathPrefix, const char* path, const cmJsonNode_t* np, const cmJsonNode_t** rpp ) +{ + cmJsRC_t rc = kOkJsRC; + const cmJsonNode_t* rp = np; + + if( np == NULL ) + rp = p->rootPtr; + + if( np == NULL ) + return kOkJsRC; + + assert( cmJsonIsObject(rp)); + assert( rpp != NULL ); + + *rpp = NULL; + + // create a copy of the path + unsigned i,j; + unsigned sn = (pathPrefix==NULL ? 0 : strlen(pathPrefix)) + strlen(path) + 1; // add one for the possible extra seperator + char ss[ 1024 ]; + char* sp = NULL; + char* sm = NULL; + char* sb = ss; + + // don't put more than 1k on the stack + if( sn + 1 > 1024 ) + { + sm = cmMemAllocZ(char,sn+1); + sb = sm; + } + + sp = sb; + + sp[0] = 0; + if(pathPrefix != NULL ) + { + strcpy(sp,pathPrefix); + + // if pathPrefix does not end in a '/' then insert one + if( sp[ strlen(sp)-1 ] != '/' ) + strcat(sp,"/"); + } + + // the '/' has already been inserted - skip any leading '/' character in path + strcat(sp,path[0]=='/' ? path+1 : path ); + + // terminate each path label with a '/0'. + sp = ss; + for(i=0,j=0; sp < ss + sn; ++sp, ++i ) + if( *sp == '/' ) + { + *sp = 0; + ++j; + } + + if( i > 0 ) + { + + sp = sb; + + while( sp < sb + sn ) + { + + // labeled values are always associated with pairs and + // pairs only exist as the children of objects. + if( cmJsonIsObject(rp) == false ) + { + rc = cmErrMsg(&p->err,kInvalidNodeTypeJsRC,"A non-object node was encountered on a object path."); + break; + } + + // get the count of pairs in this object + unsigned cn = cmJsonChildCount( rp ); + cmJsonNode_t* cp = rp->u.childPtr; + unsigned k; + + for(k=0; ksiblingPtr; + } + + // if the search failed + if( k == cn || cp == NULL ) + { + rc = cmErrMsg(&p->err,kNodeNotFoundJsRC,"The path label '%s' could not be found.",cmStringNullGuard(sp)); + break; + } + + // take the value of the located pair to continue the search + rp = cmJsonPairValue(cp); + + // advance to the next label + sp += strlen(sp) + 1; + + } + + + } + + cmMemPtrFree(&sm); + + *rpp = rp; + + return rc; +} + +cmJsRC_t _cmJsonPathToValueError( cmJs_t* p, cmJsRC_t rc, const char* pathPrefix, const char* path, const char* typeLabel ) +{ + if( pathPrefix != NULL ) + cmErrMsg(&p->err,rc,"The JSON value at '%s/%s' could not be converted to a '%s'.",cmStringNullGuard(pathPrefix),cmStringNullGuard(path),cmStringNullGuard(typeLabel)); + else + cmErrMsg(&p->err,rc,"The JSON value at '%s' could not be converted to a '%s'.",cmStringNullGuard(path),cmStringNullGuard(typeLabel)); + + return rc; +} + +cmJsRC_t cmJsonPathToValueNode( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, const cmJsonNode_t** nodePtrPtr ) +{ + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if( objectNodePtr == NULL ) + objectNodePtr = p->rootPtr; + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,nodePtrPtr)) != kOkJsRC ) + return rc; + + return rc; +} + +cmJsRC_t cmJsonPathToBool( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, bool* retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonBoolValue(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"bool"); + + return rc; +} + +cmJsRC_t cmJsonPathToInt( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, int* retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonIntValue(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"bool"); + + return rc; +} + +cmJsRC_t cmJsonPathToUInt( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, unsigned* retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonUIntValue(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"unsigned integer"); + + return rc; +} + +cmJsRC_t cmJsonPathToReal( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, double* retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonRealValue(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"real"); + + return rc; +} + +cmJsRC_t cmJsonPathToString( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, const char** retValPtr ) +{ + const cmJsonNode_t* rp = NULL; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonStringValue(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"string"); + + return rc; +} + +cmJsRC_t cmJsonPathToPair( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, cmJsonNode_t** retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonPairNode(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"pair"); + + return rc; +} + +cmJsRC_t cmJsonPathToArray( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, cmJsonNode_t** retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonArrayNode(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"array"); + + return rc; +} + +cmJsRC_t cmJsonPathToObject( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, cmJsonNode_t** retValPtr ) +{ + const cmJsonNode_t* rp; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC ) + return rc; + + if((rc = cmJsonObjectNode(rp,retValPtr)) != kOkJsRC ) + return _cmJsonPathToValueError(p,rc,pathPrefix,path,"object"); + + return rc; +} + + +const cmJsonNode_t* cmJsonFindPathValueC( cmJsonH_t h, const char* path, const cmJsonNode_t* np, unsigned typeIdMask ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + const cmJsonNode_t* rp = NULL; + cmJsRC_t rc = kOkJsRC; + + if((rc = _cmJsonFindPathValue(p,NULL,path,np,&rp)) != kOkJsRC ) + { + // validate the return type + if( rp != NULL ) + if( (typeIdMask!=kInvalidTId) && (typeIdMask & rp->typeId)==0 ) + { + cmErrMsg(&p->err,kInvalidNodeTypeJsRC,"The value at the end of the path '%s' did not match the requested type.",cmStringNullGuard(path)); + rp = NULL; + } + } + + return rp; +} + +cmJsonNode_t* cmJsonFindPathValue( cmJsonH_t h, const char* path, const cmJsonNode_t* np, unsigned typeIdMask ) +{ return (cmJsonNode_t*)cmJsonFindPathValueC(h,path,np,typeIdMask ); } + +cmJsRC_t _cmJsonFindMemberValue( const cmJsonNode_t* np, const char* label, unsigned keyTypeId, cmJsonNode_t** npp ) +{ + *npp = NULL; + + // the src node must be an object + if( np->typeId != kObjectTId ) + return kNodeNotFoundJsRC; + + // for each member pair + const cmJsonNode_t* cnp = np->u.childPtr; + while( cnp != NULL ) + { + assert( (cnp->typeId & kMaskTId) == kPairTId ); + + // if the labels match ... + if( strcmp( label, cmJsonPairLabel(cnp)) == 0 ) + { + // ... and the type flags match ... + if( (keyTypeId==kInvalidTId || cmIsFlag(cnp->u.childPtr->siblingPtr->typeId,keyTypeId) ) ) + { + *npp = cnp->u.childPtr->siblingPtr; + return kOkJsRC; // ... then the key was found. + } + + // ... label match but wrong type ... this is considered an error + return kNodeCannotCvtJsRC; + } + + cnp = cnp->siblingPtr; + } + + return kNodeNotFoundJsRC; +} + +cmJsRC_t cmJsonUIntValue( const cmJsonNode_t* vp, unsigned* retPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( vp == NULL ) + return kNodeCannotCvtJsRC; + + switch(vp->typeId) + { + case kIntTId: *retPtr = vp->u.intVal; break; + case kRealTId: *retPtr = (unsigned)vp->u.realVal; break; + case kTrueTId: *retPtr = 1; break; + case kFalseTId: *retPtr = 0; break; + default: + rc = kNodeCannotCvtJsRC; + } + return rc; +} + +cmJsRC_t cmJsonIntValue( const cmJsonNode_t* vp, int* retPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( vp == NULL ) + return kNodeCannotCvtJsRC; + + switch(vp->typeId) + { + case kIntTId: *retPtr = vp->u.intVal; break; + case kRealTId: *retPtr = (int)vp->u.realVal; break; + case kTrueTId: *retPtr = 1; break; + case kFalseTId: *retPtr = 0; break; + default: + rc = kNodeCannotCvtJsRC; + } + return rc; +} + +cmJsRC_t cmJsonRealValue( const cmJsonNode_t* vp, double* retPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( vp == NULL ) + return kNodeCannotCvtJsRC; + + switch(vp->typeId) + { + case kIntTId: *retPtr = vp->u.intVal; break; + case kRealTId: *retPtr = vp->u.realVal; break; + case kTrueTId: *retPtr = 1; break; + case kFalseTId: *retPtr = 0; break; + default: + rc = kNodeCannotCvtJsRC; + + } + return rc; + +} + +cmJsRC_t cmJsonBoolValue( const cmJsonNode_t* vp, bool* retPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( vp == NULL ) + return kNodeCannotCvtJsRC; + + switch(vp->typeId) + { + case kIntTId: *retPtr = vp->u.intVal != 0; break; + case kRealTId: *retPtr = (int)vp->u.realVal != 0; break; + case kTrueTId: *retPtr = 1; break; + case kFalseTId: *retPtr = 0; break; + default: + rc = kNodeCannotCvtJsRC; + + } + return rc; + +} + +cmJsRC_t cmJsonStringValue( const cmJsonNode_t* vp, const char **retPtrPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( vp == NULL && vp->typeId != kStringTId ) + return kNodeCannotCvtJsRC; + + *retPtrPtr = vp->u.stringVal; + + return rc; +} + +cmJsRC_t cmJsonPairNode( const cmJsonNode_t* vp, cmJsonNode_t **retPtrPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( !cmJsonIsPair(vp) ) + return kNodeCannotCvtJsRC; + + *retPtrPtr = (cmJsonNode_t*)vp; + + return rc; +} + +cmJsRC_t cmJsonArrayNode( const cmJsonNode_t* vp, cmJsonNode_t **retPtrPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( !cmJsonIsArray(vp) ) + return kNodeCannotCvtJsRC; + + *retPtrPtr = (cmJsonNode_t*)vp; + + return rc; +} + +cmJsRC_t cmJsonObjectNode( const cmJsonNode_t* vp, cmJsonNode_t **retPtrPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( !cmJsonIsObject(vp) ) + return kNodeCannotCvtJsRC; + + *retPtrPtr = (cmJsonNode_t*)vp; + + return rc; +} + +cmJsRC_t cmJsonUIntMember( const cmJsonNode_t* np, const char* label, unsigned* retPtr ) +{ + cmJsonNode_t* vp; + cmJsRC_t rc; + + if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC ) + return rc; + + return cmJsonUIntValue(vp,retPtr); +} + +cmJsRC_t cmJsonIntMember( const cmJsonNode_t* np, const char* label, int* retPtr ) +{ + cmJsonNode_t* vp; + cmJsRC_t rc; + + if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC ) + return rc; + + return cmJsonIntValue(vp,retPtr); +} + +cmJsRC_t cmJsonRealMember( const cmJsonNode_t* np, const char* label, double* retPtr ) +{ + cmJsonNode_t* vp; + cmJsRC_t rc; + + if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC ) + return rc; + + return cmJsonRealValue(vp,retPtr); +} + +cmJsRC_t cmJsonBoolMember( const cmJsonNode_t* np, const char* label, bool* retPtr ) +{ + cmJsonNode_t* vp; + cmJsRC_t rc; + + if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC ) + return rc; + + return cmJsonBoolValue(vp,retPtr); + +} + +cmJsRC_t cmJsonStringMember( const cmJsonNode_t* np, const char* label, const char** retPtrPtr ) +{ + cmJsonNode_t* vp; + cmJsRC_t rc; + + *retPtrPtr = NULL; + + if((rc = _cmJsonFindMemberValue(np,label,kStringTId,&vp)) != kOkJsRC ) + return rc; + + return cmJsonStringValue(vp,retPtrPtr); +} + +cmJsRC_t cmJsonNodeMember( const cmJsonNode_t* np, const char* label, cmJsonNode_t** retPtrPtr ) +{ + cmJsonNode_t* vp; + cmJsRC_t rc; + + *retPtrPtr = NULL; + + if((rc = _cmJsonFindMemberValue(np,label,kArrayTId|kObjectTId,&vp)) != kOkJsRC ) + return rc; + + *retPtrPtr = vp; + + return rc; +} + +cmJsonNode_t* cmJsonNodeMemberValue( const cmJsonNode_t* np, const char* label ) +{ + assert( cmJsonIsObject(np) ); + + unsigned n = cmJsonChildCount(np); + unsigned j = 0; + for(; jtypeId & kMaskTId) != (typeId&kMaskTId) ) + rc = kNodeCannotCvtJsRC; + } + + } + break; + + case kIntTId: + { + int* ip = va_arg(vl, int* ); + assert(ip != NULL); + rc = cmJsonIntMember(objectNodePtr, label, ip); + } + break; + + case kRealTId: + { + double* dp = va_arg(vl, double*); + assert(dp != NULL); + rc = cmJsonRealMember(objectNodePtr, label, dp); + } + break; + + case kStringTId: + { + const char** cpp = va_arg(vl, const char**); + assert(cpp != NULL); + rc = cmJsonStringMember(objectNodePtr, label, cpp); + } + break; + + case kTrueTId: + case kFalseTId: + { + bool* bp = va_arg(vl, bool* ); + rc = cmJsonBoolMember(objectNodePtr, label, bp); + } + break; + + default: + // missing terminating NULL on the var args list??? + assert(0); + break; + } + + if( (rc == kNodeNotFoundJsRC) && cmIsFlag(typeId,kOptArgJsFl) ) + rc = kOkJsRC; + + if( rc != kOkJsRC && errLabelPtrPtr != NULL ) + *errLabelPtrPtr = label; + + } + + return rc; +} + +cmJsRC_t cmJsonMemberValues( const cmJsonNode_t* objectNodePtr, const char** errLabelPtrPtr, ...) +{ + va_list vl; + va_start(vl,errLabelPtrPtr); + cmJsRC_t rc = cmJsonVMemberValues(objectNodePtr,errLabelPtrPtr,vl); + va_end(vl); + return rc; +} + +cmJsRC_t cmJsonVPathValues( cmJsonH_t h, const char* pathPrefix, const cmJsonNode_t* objNodePtr, const char** errLabelPtrPtr, va_list vl ) +{ + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if( errLabelPtrPtr != NULL ) + *errLabelPtrPtr = NULL; + + const char* path; + while( ((path = va_arg(vl,const char*)) != NULL) && (rc==kOkJsRC) ) + { + unsigned typeId; + const cmJsonNode_t* vnp; + + typeId = va_arg(vl,unsigned); + + // find the requested pair value + if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objNodePtr,&vnp)) != kOkJsRC ) + break; + + switch( typeId & kMaskTId ) + { + case kObjectTId: + case kPairTId: + case kArrayTId: + { + const cmJsonNode_t** nodePtrPtr = va_arg(vl, const cmJsonNode_t**); + + if( (vnp->typeId & kMaskTId) != (typeId & kMaskTId) ) + rc = kNodeCannotCvtJsRC; + else + *nodePtrPtr = vnp; + + } + break; + + case kIntTId: + { + int* ip = va_arg(vl, int* ); + assert(ip != NULL); + rc = cmJsonIntValue(vnp, ip); + } + break; + + case kRealTId: + { + double* dp = va_arg(vl, double*); + assert(dp != NULL); + rc = cmJsonRealValue(vnp, dp); + } + break; + + case kStringTId: + { + const char** cpp = va_arg(vl, const char**); + assert(cpp != NULL); + rc = cmJsonStringValue(vnp, cpp); + } + break; + + case kTrueTId: + case kFalseTId: + { + bool* bp = va_arg(vl, bool* ); + rc = cmJsonBoolValue(vnp, bp); + } + break; + + default: + // missing terminating NULL on the var args list??? + assert(0); + break; + + } + + if( (rc == kNodeNotFoundJsRC) && cmIsFlag(typeId,kOptArgJsFl) ) + rc = kOkJsRC; + + if( rc != kOkJsRC && errLabelPtrPtr != NULL ) + *errLabelPtrPtr = path; + + } + + return rc; +} + +cmJsRC_t cmJsonPathValues( cmJsonH_t h, const char* pathPrefix, const cmJsonNode_t* objectNodePtr, const char** errLabelPtrPtr, ... ) +{ + va_list vl; + va_start(vl,errLabelPtrPtr); + cmJsRC_t rc = cmJsonVPathValues(h,pathPrefix,objectNodePtr,errLabelPtrPtr,vl); + va_end(vl); + return rc; +} + + +cmJsonNode_t* _cmJsonCreateNode2( cmJsonH_t h, unsigned newNodeTypeId, cmJsonNode_t* parentPtr ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + cmJsonNode_t* np = NULL; + + if((p->rc = _cmJsonCreateNode(p,parentPtr,newNodeTypeId,&np)) != kOkJsRC ) + return NULL; + + return np; +} + + +cmJsRC_t cmJsonCreate( cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned typeId, const char* sv, int iv, double dv, cmJsonNode_t** rpp ) +{ + cmJsonNode_t* rp = NULL; + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if( rpp != NULL ) + *rpp = NULL; + + switch( typeId ) + { + case kObjectTId: + case kArrayTId: + if((rp = _cmJsonCreateNode2(h,typeId,parentPtr)) == NULL) + rc = p->rc; + else + { + if( rpp != NULL ) + *rpp = rp; + } + break; + + case kPairTId: rc = _cmJsonCreatePair(p,parentPtr,sv,rpp); break; + case kIntTId: rc = _cmJsonCreateInt(p,parentPtr,iv,rpp); break; + case kRealTId: rc = _cmJsonCreateReal(p,parentPtr,dv,rpp); break; + case kTrueTId: rc = _cmJsonCreateBool(p,parentPtr,true,rpp); break; + case kFalseTId: rc = _cmJsonCreateBool(p,parentPtr,false,rpp); break; + case kNullTId: rc = _cmJsonCreateNull(p,parentPtr,rpp); break; + case kStringTId: rc = _cmJsonCreateString(p,parentPtr,sv,strlen(sv),rpp); break; + default: + assert(0); + break; + } + + return rc; +} + + +cmJsonNode_t* cmJsonCreateObject( cmJsonH_t h, cmJsonNode_t* parentPtr ) +{ return _cmJsonCreateNode2(h,kObjectTId,parentPtr); } + + +cmJsonNode_t* cmJsonCreateArray( cmJsonH_t h, cmJsonNode_t* parentPtr ) +{ return _cmJsonCreateNode2(h,kArrayTId,parentPtr); } + + +cmJsonNode_t* cmJsonCreatePair( cmJsonH_t h, cmJsonNode_t* parentPtr, const char* label ) +{ + cmJsonNode_t* np; + + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((_cmJsonCreatePair(p,parentPtr,label,&np)) != kOkJsRC ) + return NULL; + + return np; +} + +cmJsRC_t cmJsonCreateString( cmJsonH_t h, cmJsonNode_t* parentPtr, const char* stringValue ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonCreateString( p, parentPtr, stringValue, strlen(stringValue),NULL);; +} + +cmJsRC_t cmJsonCreateStringN(cmJsonH_t h, cmJsonNode_t* parentPtr, const char* stringValue, unsigned stringCharCnt ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonCreateString( p, parentPtr, stringValue, stringCharCnt,NULL);; +} + +cmJsRC_t cmJsonCreateInt( cmJsonH_t h, cmJsonNode_t* parentPtr, int value ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonCreateInt( p, parentPtr, value, NULL ); +} + +cmJsRC_t cmJsonCreateReal( cmJsonH_t h, cmJsonNode_t* parentPtr, double value ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonCreateReal( p, parentPtr, value, NULL ); +} + +cmJsRC_t cmJsonCreateBool( cmJsonH_t h, cmJsonNode_t* parentPtr, bool value ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonCreateBool( p, parentPtr, value,NULL ); +} + + cmJsRC_t cmJsonCreateNull( cmJsonH_t h, cmJsonNode_t* parentPtr ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonCreateNull( p, parentPtr, NULL ); +} + +cmJsRC_t cmJsonCreateStringArray( cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned n, const char** value ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + cmJsRC_t rc = kOkJsRC; + cmJsonNode_t* np; + unsigned i; + + if((np = cmJsonCreateArray(h,parentPtr)) == NULL ) + return _cmJsonError(p,cmErrLastRC(&p->err),"Unable to create 'bool' array."); + + for(i=0; ierr),"Unable to create 'int' array."); + + for(i=0; ierr),"Unable to create 'real' array."); + + for(i=0; ierr),"Unable to create 'bool' array."); + + for(i=0; itypeId != kIntTId ) + return _cmJsonError(_cmJsonHandleToPtr(h),kInvalidNodeTypeJsRC,"Cannot assign type 'int' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId)); + + np->u.intVal = ival; + + return kOkJsRC; +} + +cmJsRC_t cmJsonSetReal( cmJsonH_t h, cmJsonNode_t * np, double rval ) +{ + if( np->typeId != kRealTId ) + return _cmJsonError(_cmJsonHandleToPtr(h),kInvalidNodeTypeJsRC,"Cannot assign type 'real' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId)); + + np->u.realVal = rval; + + return kOkJsRC; +} + +cmJsRC_t cmJsonSetBool( cmJsonH_t h, cmJsonNode_t * np, bool bval ) +{ + if( np->typeId == kTrueTId || np->typeId==kFalseTId ) + return _cmJsonError(_cmJsonHandleToPtr(h),kInvalidNodeTypeJsRC,"Cannot assign type 'bool' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId)); + + np->u.boolVal = bval; + + return kOkJsRC; +} + +cmJsRC_t cmJsonSetString( cmJsonH_t h, cmJsonNode_t* np, const char* sval ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + + if( np->typeId != kStringTId ) + return _cmJsonError(p,kInvalidNodeTypeJsRC,"Cannot assign type 'string' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId)); + + unsigned sn = strlen(sval); + + if( np->u.stringVal != NULL && strlen(np->u.stringVal) <= sn ) + strcpy(np->u.stringVal,sval); + else + return _cmJsonSetString(p,np,sval,sn); + + return kOkJsRC; +} + +cmJsonNode_t* cmJsonInsertPair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned typeId, const char* sv, int iv, double dv ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + + cmJsRC_t rc; + cmJsonNode_t* pairNodePtr; + + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = _cmJsonCreatePair(p,objectNodePtr,label,&pairNodePtr)) != kOkJsRC ) + return NULL; + + assert( pairNodePtr != NULL ); + + if((rc = cmJsonCreate(h,pairNodePtr,typeId,sv,iv,dv,NULL)) != kOkJsRC ) + return NULL; + + return pairNodePtr; +} + + +cmJsonNode_t* cmJsonInsertPairObject( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return NULL; + + return cmJsonCreateObject(h,pairNodePtr); +} + +cmJsonNode_t* cmJsonInsertPairArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return NULL; + + return cmJsonCreateArray(h,pairNodePtr); +} + +cmJsonNode_t* cmJsonInsertPairPair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, const char* pairLabel ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return NULL; + + return cmJsonCreatePair(h,pairNodePtr,pairLabel); +} + + + +cmJsRC_t cmJsonInsertPairInt( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, int intVal ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateInt(h,pairNodePtr,intVal); +} +cmJsRC_t cmJsonInsertPairReal( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, double realVal ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateReal(h,pairNodePtr,realVal); +} + +cmJsRC_t cmJsonInsertPairString( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, const char* stringVal ) +{ return cmJsonInsertPairStringN(h,objectNodePtr,label,stringVal,strlen(stringVal)); } + +cmJsRC_t cmJsonInsertPairStringN( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, const char* stringVal, unsigned stringCharCnt ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateStringN(h,pairNodePtr,stringVal,stringCharCnt); +} + + +cmJsRC_t cmJsonInsertPairBool( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, bool boolVal ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateBool(h,pairNodePtr,boolVal); +} + +cmJsRC_t cmJsonInsertPairNull( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateNull(h,pairNodePtr); +} + +cmJsRC_t cmJsonInsertPairIntArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const int* values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateIntArray(h,pairNodePtr,n,values); +} + +cmJsRC_t cmJsonInsertPairRealArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const double* values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateRealArray(h,pairNodePtr,n,values); +} + +cmJsRC_t cmJsonInsertPairStringArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const char** values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateStringArray(h,pairNodePtr,n,values); +} + +cmJsRC_t cmJsonInsertPairBoolArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const bool* values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL ) + return cmJsonErrorCode(h); + + return cmJsonCreateBoolArray(h,pairNodePtr,n,values); + +} + +cmJsonNode_t* cmJsonInsertPairIntArray2( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const int* values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL ) + if( cmJsonCreateIntArray(h,pairNodePtr,n,values) != kOkJsRC ) + return NULL; + + return pairNodePtr; +} + +cmJsonNode_t* cmJsonInsertPairRealArray2( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const double* values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL ) + if( cmJsonCreateRealArray(h,pairNodePtr,n,values) != kOkJsRC ) + return NULL; + return pairNodePtr; +} + +cmJsonNode_t* cmJsonInsertPairStringArray2( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const char** values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL ) + if( cmJsonCreateStringArray(h,pairNodePtr,n,values) != kOkJsRC ) + return NULL; + + return pairNodePtr; +} + +cmJsonNode_t* cmJsonInsertPairBoolArray2( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const bool* values ) +{ + assert( objectNodePtr->typeId == kObjectTId ); + cmJsonNode_t* pairNodePtr; + if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL ) + if( cmJsonCreateBoolArray(h,pairNodePtr,n,values) != kOkJsRC ) + return NULL; + return pairNodePtr; +} + + +cmJsRC_t _cmJsonInsertOrReplacePair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, unsigned newTypeId, const char* sv, int iv, double dv, cmJsonNode_t* nv, bool insertFl, cmJsonNode_t** retNodePtrPtr ) +{ + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + cmJsonNode_t* valNodePtr = NULL; + cmJsonNode_t* pairNodePtr = NULL; + + assert( objectNodePtr->typeId == kObjectTId ); + + if( retNodePtrPtr != NULL ) + *retNodePtrPtr = NULL; + + // if a matching pair was not found .... + if(( valNodePtr = cmJsonFindValue(h,label,objectNodePtr,matchTypeMask)) == NULL ) + { + // ... and insertion is not allowed then return error + if( insertFl == false ) + return kNodeNotFoundJsRC; + + // ... otherwise insert a new pair and return a pointer to it + pairNodePtr = cmJsonInsertPair(h,objectNodePtr,label,newTypeId,sv,iv,dv); + + goto errLabel; + + } + + // ... otherwise a match was found to at least the pair label. + // If matchTypeMask was set to kInvalidTId then the type id + // of the found pair may not be the same as newTypeId. To handle + // this case we proceed by first deallocating all resources held + // by the found node and then proceeding by either creating + // deleting the found node and creating a replacement node + // (object,array,pair) or forcing the found node to a new + // type (int,real,true,false,string,null). + + assert( valNodePtr != NULL); + + pairNodePtr = valNodePtr->ownerPtr; + + // release any resources held by the found node + switch( valNodePtr->typeId ) + { + case kObjectTId: + case kArrayTId: + case kPairTId: + + // remove the pair value node and replace it with a 'null' node. + if((rc =_cmJsonRemoveNode( p, valNodePtr, true, true )) != kOkJsRC ) + goto errLabel; + break; + + case kStringTId: + // deallocate string memory + _cmJsonSetString( p, valNodePtr, NULL, 0 ); + break; + + } + + + // relace the found node with the new node + switch( newTypeId ) + { + case kObjectTId: + case kArrayTId: + case kPairTId: + { + cmJsonNode_t* newValueNodePtr = NULL; + + // remove the current pair value .... + if((rc =_cmJsonRemoveNode( p, valNodePtr, true, false )) != kOkJsRC ) + goto errLabel; + + + // new pair nodes should have the pair label in 'sv' + assert( newTypeId!=kPairTId || (newTypeId==kPairTId && sv != NULL )); + + // if no new value was given or the new value is a pair then ... + if( nv == NULL || newTypeId == kPairTId ) + { + // ... create a new blank array,obj or pair + if((rc = cmJsonCreate( h, pairNodePtr, newTypeId, sv, 0, 0, &newValueNodePtr )) != kOkJsRC ) + goto errLabel; + } + + // if the new value is a pair and no value node was given then set the + // new pairs value node to NULL + if( nv == NULL && newTypeId == kPairTId ) + { + if((rc = _cmJsonCreateNull(p,newValueNodePtr,NULL)) != kOkJsRC ) + goto errLabel; + } + + // if a new value node was given + if( nv != NULL ) + { + // the new child node should not already be linked to a parent + assert( nv->ownerPtr == NULL ); + + // if the new value is an obj or array then the new + // value node type id should be the same + assert( newTypeId==kPairTId || newTypeId==nv->typeId ); + + // + cmJsonNode_t* pp = newTypeId==kPairTId ? newValueNodePtr : pairNodePtr; + + assert( pp->typeId == kPairTId ); + + // link in the child to the pair + if((rc = _cmJsonLinkInChild(p,pp,nv)) != kOkJsRC ) + goto errLabel; + + } + } + break; + + // All cases below follow the same pattern: + // 1. Set the type id to the replacement value type id + // 2. Assign the value to the node. + // This sequence is safe because all resources were freed + // by the earlier switch. + + case kStringTId: + valNodePtr->typeId = kStringTId; + _cmJsonSetString( p, valNodePtr, sv, strlen(sv) ); + break; + + case kIntTId: + valNodePtr->typeId =kIntTId; + valNodePtr->u.intVal = iv; + break; + + case kRealTId: + valNodePtr->typeId = kRealTId; + valNodePtr->u.realVal = dv; + break; + + case kTrueTId: + valNodePtr->typeId = kTrueTId; + valNodePtr->u.boolVal = true; + break; + + case kFalseTId: + valNodePtr->typeId = kFalseTId; + valNodePtr->u.boolVal = false; + break; + + case kNullTId: + valNodePtr->typeId = kNullTId; + break; + + default: + { assert(0); } + + } + + errLabel: + + if( rc == kOkJsRC ) + if( retNodePtrPtr != NULL ) + *retNodePtrPtr = pairNodePtr; + + return rc; +} + +cmJsonNode_t* cmJsonInsertOrReplacePair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, unsigned typeId, const char* sv, int iv, double dv, cmJsonNode_t* nv ) +{ + cmJsonNode_t* retNodePtr = NULL; + + _cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,typeId,sv,iv,dv,nv,true,&retNodePtr ); + + return retNodePtr; +} + +cmJsonNode_t* cmJsonInsertOrReplacePairObject( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newObjNodePtr ) +{ + cmJsonNode_t* pairPtr; + if((pairPtr = cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kObjectTId,NULL,0,0,newObjNodePtr)) == NULL ) + return NULL; + + return pairPtr->u.childPtr->siblingPtr; +} + +cmJsonNode_t* cmJsonInsertOrReplacePairArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newArrayNodePtr ) +{ + cmJsonNode_t* pairPtr; + if((pairPtr = cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kArrayTId,NULL,0,0,newArrayNodePtr)) == NULL ) + return NULL; + + return pairPtr->u.childPtr->siblingPtr; +} + +cmJsonNode_t* cmJsonInsertOrReplacePairPair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* newPairLabel, cmJsonNode_t* newPairValNodePtr ) +{ + cmJsonNode_t* pairPtr; + if((pairPtr = cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kPairTId,newPairLabel,0,0,newPairValNodePtr)) == NULL ) + return NULL; + + return pairPtr->u.childPtr->siblingPtr; +} + +cmJsRC_t cmJsonInsertOrReplacePairInt( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, int intVal ) +{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kIntTId,NULL,intVal,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonInsertOrReplacePairReal( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, double realVal ) +{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kRealTId,NULL,0,realVal,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonInsertOrReplacePairString( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* stringVal ) +{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kStringTId,stringVal,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonInsertOrReplacePairBool( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, bool boolVal ) +{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,boolVal ? kTrueTId : kFalseTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonInsertOrReplacePairNull( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask ) +{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kNullTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsonNode_t* cmJsonReplacePair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, unsigned newTypeId, const char* sv, int iv, double dv, cmJsonNode_t* nv ) +{ + cmJsonNode_t* retNodePtr = NULL; + _cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,newTypeId,sv,iv,dv,nv,false,&retNodePtr ); + return retNodePtr; +} + +cmJsonNode_t* cmJsonReplacePairObject( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newPairNodePtr ) +{ + cmJsonNode_t* pairPtr; + if((pairPtr = cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kObjectTId,NULL,0,0,newPairNodePtr)) == NULL ) + return NULL; + return pairPtr->u.childPtr->siblingPtr; +} + + +cmJsonNode_t* cmJsonReplacePairArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newArrayNodePtr ) +{ + cmJsonNode_t* pairPtr; + if((pairPtr = cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kArrayTId,NULL,0,0,newArrayNodePtr)) == NULL ) + return NULL; + return pairPtr->u.childPtr->siblingPtr; +} + +cmJsonNode_t* cmJsonReplacePairPair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* newPairLabel, cmJsonNode_t* newPairValNodePtr ) +{ + cmJsonNode_t* pairPtr; + if((pairPtr = cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kPairTId,newPairLabel,0,0,newPairValNodePtr)) == NULL ) + return NULL; + return pairPtr->u.childPtr->siblingPtr; +} + + +cmJsRC_t cmJsonReplacePairInt( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, int intVal ) +{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kIntTId,NULL,intVal,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonReplacePairReal( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, double realVal ) +{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kRealTId,NULL,0,realVal,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonReplacePairString( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* stringVal ) +{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kStringTId,stringVal,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonReplacePairBool( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, bool boolVal ) +{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,boolVal ? kTrueTId : kFalseTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonReplacePairNull( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask ) +{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kNullTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; } + +cmJsRC_t cmJsonVInsertPairs( cmJsonH_t h, cmJsonNode_t* objNodePtr, va_list vl ) +{ + cmJsRC_t rc = kOkJsRC; + + assert( objNodePtr->typeId == kObjectTId ); + + const char* label; + + while( ((label = va_arg(vl,const char*)) != NULL) && (rc == kOkJsRC) ) + { + unsigned sel = va_arg(vl,unsigned); + switch( sel ) + { + case kObjectTId: + if( cmJsonInsertPairObject(h,objNodePtr,label) == NULL ) + rc = cmJsonErrorCode(h); + break; + + case kArrayTId: + if( cmJsonInsertPairArray(h,objNodePtr,label) == NULL ) + rc = cmJsonErrorCode(h); + break; + + case kPairTId: + if( cmJsonInsertPairPair(h,objNodePtr,label, va_arg(vl,const char*)) == NULL ) + rc = cmJsonErrorCode(h); + break; + + + case kIntTId: + rc = cmJsonInsertPairInt(h,objNodePtr,label, va_arg(vl,int) ); + break; + + case kRealTId: + rc = cmJsonInsertPairReal(h,objNodePtr,label, va_arg(vl,double) ); + break; + + case kStringTId: + rc = cmJsonInsertPairString(h,objNodePtr,label, va_arg(vl,const char *) ); + break; + + case kTrueTId: + case kFalseTId: + case kBoolTId: + rc = cmJsonInsertPairBool(h,objNodePtr,label, va_arg(vl,int) ); + break; + + case kNullTId: + rc = cmJsonInsertPairNull(h,objNodePtr,label ); + break; + + default: + // missing terminating NULL on the var args list??? + assert(0); + break; + } + } + + return rc; +} + +cmJsRC_t cmJsonInsertPairs( cmJsonH_t h, cmJsonNode_t* objectNodePtr, ... ) +{ + va_list vl; + va_start(vl,objectNodePtr); + cmJsRC_t rc = cmJsonVInsertPairs(h,objectNodePtr,vl); + va_end(vl); + return rc; +} + +cmJsonNode_t* cmJsonVCreateFilledObject( cmJsonH_t h, cmJsonNode_t* parentPtr, va_list vl ) +{ + cmJsonNode_t* np; + + if((np = cmJsonCreateObject(h,parentPtr)) == NULL) + return NULL; + + if( cmJsonVInsertPairs(h,np,vl) != kOkJsRC ) + { + cmJsonRemoveNode(h,np,true); + return NULL; + } + + return np; +} + +cmJsonNode_t* cmJsonCreateFilledObject( cmJsonH_t h, cmJsonNode_t* parentPtr, ... ) +{ + va_list vl; + va_start(vl,parentPtr); + cmJsonNode_t* np = cmJsonVCreateFilledObject(h,parentPtr,vl); + va_end(vl); + return np; +} + +void _cmJsonFreeNode( cmJs_t* p, cmJsonNode_t* np ) +{ + switch( np->typeId ) + { + case kObjectTId: + case kPairTId: + case kArrayTId: + { + cmJsonNode_t* cnp = np->u.childPtr; + while( cnp != NULL ) + { + cmJsonNode_t* nnp = cnp->siblingPtr; + _cmJsonFreeNode(p,cnp); + cnp = nnp; + } + + } + break; + + case kStringTId: + if( np->u.stringVal != NULL ) + cmLHeapFree(p->heapH,np->u.stringVal); + break; + + + } + + cmLHeapFree(p->heapH,np); + +} + +cmJsRC_t _cmJsonRemoveNode( cmJs_t* p, cmJsonNode_t* np, bool freeFl, bool balancePairsFl ) +{ + if(np == NULL ) + return kOkJsRC; + + cmJsonNode_t* parentPtr = np->ownerPtr; + + // if np is the root ... + if( np == p->rootPtr ) + { + // ... we only need to set the root to null to remove the node. + p->rootPtr = NULL; + } + else + { + if( parentPtr != NULL ) + { + // get the parents first child + cmJsonNode_t* cnp = parentPtr->u.childPtr; + + // if np is the first child then make the second child the first child + if( cnp == np ) + { + if( parentPtr->typeId == kPairTId ) + return _cmJsonError( p, kCannotRemoveLabelJsRC, "Cannot remove pair label nodes because this would invalidate the tree structure."); + + parentPtr->u.childPtr = cnp->siblingPtr; + } + else + { + // otherwise unlink it from the child chain + while( cnp != NULL ) + { + if( cnp->siblingPtr == np ) + { + + cnp->siblingPtr = np->siblingPtr; + + // if the parent is a pair then the removed node is a + // 'pair value' which must be replaced with a null node in order + // to maintain a valid tree. + if( (parentPtr->typeId == kPairTId) && balancePairsFl) + _cmJsonCreateNull( p, parentPtr, NULL ); + + break; + } + + cnp = cnp->siblingPtr; + } + + assert( cnp != NULL ); + } + } + } + + // if the memory assoc'd with the removed node should be released + if( freeFl ) + { + _cmJsonFreeNode(p,np); + /* + if( np->typeId == kStringTId ) + { + cmLHeapFree(p->heapH,np->u.stringVal); + np->u.stringVal = NULL; + } + + cmLHeapFree(p->heapH,np); + */ + } + + return kOkJsRC; +} + + +cmJsRC_t cmJsonRemoveNode( cmJsonH_t h, cmJsonNode_t* np, bool freeFl ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + + return _cmJsonRemoveNode( p, np, freeFl, true ); +} + +cmJsRC_t _cmJsonValidateErr( cmJs_t* p, const char* text ) +{ + if( p != NULL ) + return _cmJsonSyntaxError(p,text); + + return kValidateFailJsRC; +} + +// 'p' is optional. If 'p' is set to NULL the function will return kValidFailJsRC if the +// tree rooted at 'np' is invalid and will not print an error message. +cmJsRC_t _cmJsonValidateNode( cmJs_t* p, const cmJsonNode_t* np, const cmJsonNode_t* parentPtr ) +{ + cmJsRC_t rc = kOkJsRC; + + if( parentPtr != np->ownerPtr ) + return _cmJsonValidateErr(p,"A child->parent link does not agree with a parent->child link."); + + if( parentPtr == NULL ) + { + if( np->typeId != kArrayTId && np->typeId != kObjectTId ) + return _cmJsonValidateErr(p,"Only 'array' and 'object' nodes may be the root element."); + } + else + { + + if( parentPtr->typeId != kArrayTId && parentPtr->typeId != kObjectTId && parentPtr->typeId != kPairTId ) + return _cmJsonValidateErr(p,"Parent nodes must be either 'object','array' or 'pair' nodes."); + } + + + switch( np->typeId ) + { + case kPairTId: + if( cmJsonChildCount(np) != 2 ) + return _cmJsonValidateErr(p,"'pair' nodes must have exactly two children."); + + if( np->u.childPtr->typeId != kStringTId ) + return _cmJsonValidateErr(p,"The first child of 'pair' nodes must be a 'string' node."); + + // fall through + + + case kObjectTId: + case kArrayTId: + { + // validate each child node + cmJsonNode_t* cnp = np->u.childPtr; + while(cnp != NULL) + { + if( cnp->ownerPtr != np ) + return _cmJsonValidateErr(p,"A parent->child pointer was not validated with a child->parent pointer."); + + if( np->typeId == kObjectTId && cnp->typeId != kPairTId ) + return _cmJsonValidateErr(p,"All 'object' child nodes must be 'pair' nodes."); + + if((rc = _cmJsonValidateNode(p,cnp,np)) != kOkJsRC ) + return rc; + + cnp = cnp->siblingPtr; + } + } + break; + + + case kStringTId: + case kIntTId: + case kRealTId: + case kNullTId: + case kTrueTId: + case kFalseTId: + default: + break; + + } + + return rc; +} + +cmJsRC_t cmJsonValidateTree( cmJsonH_t h ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + + return _cmJsonValidateNode(p,p->rootPtr,NULL); +} + +cmJsRC_t cmJsonValidate( const cmJsonNode_t* np ) +{ return _cmJsonValidateNode(NULL,np,np->ownerPtr); } + + +cmJsonNode_t* _cmJsonDuplicateNode( cmJs_t* p, const cmJsonNode_t* np, cmJsonNode_t* parentPtr ) +{ + cmJsonNode_t* newParentPtr = NULL; + cmJsonNode_t* newNodePtr = NULL; + cmJsRC_t rc = kOkJsRC; + + switch( np->typeId ) + { + case kObjectTId: + case kArrayTId: + rc = _cmJsonCreateNode(p,parentPtr,np->typeId,&newParentPtr); + break; + + case kPairTId: + rc = _cmJsonCreatePair(p,parentPtr,np->u.childPtr->u.stringVal,&newParentPtr); + break; + + case kIntTId: + rc = _cmJsonCreateInt(p,parentPtr,np->u.intVal,&newNodePtr); + break; + + case kRealTId: + rc = _cmJsonCreateReal(p,parentPtr,np->u.realVal,&newNodePtr); + break; + + case kStringTId: + rc = _cmJsonCreateString(p,parentPtr,np->u.stringVal,strlen(np->u.stringVal),&newNodePtr); + break; + + case kTrueTId: + case kFalseTId: + rc = _cmJsonCreateBool(p,parentPtr,np->u.boolVal,&newNodePtr); + break; + + case kNullTId: + rc = _cmJsonCreateNull(p,parentPtr,&newNodePtr); + } + + if( rc != kOkJsRC ) + return NULL; + + if( newParentPtr != NULL ) + { + newNodePtr = newParentPtr; + + cmJsonNode_t* cnp = np->u.childPtr; + + if(np->typeId == kPairTId) + cnp = np->u.childPtr->siblingPtr; + + while( cnp != NULL ) + { + if( _cmJsonDuplicateNode(p,cnp,newParentPtr) != kOkJsRC ) + return NULL; + + cnp = cnp->siblingPtr; + } + } + + return newNodePtr; + +} + +cmJsonNode_t* cmJsonDuplicateNode( cmJsonH_t h, const cmJsonNode_t* np, cmJsonNode_t* parentPtr ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + + assert( _cmJsonValidateNode(p,np,NULL) == kOkJsRC ); + + return _cmJsonDuplicateNode(p,np,parentPtr); +} + + +cmJsRC_t cmJsonMergeObjectNodes( cmJsonH_t h, cmJsonNode_t* dstObjNodePtr, const cmJsonNode_t* srcObjNodePtr ) +{ + assert( dstObjNodePtr!=NULL && dstObjNodePtr->typeId == kObjectTId ); + assert( srcObjNodePtr!=NULL && srcObjNodePtr->typeId == kObjectTId ); + + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + cmJsonNode_t* cnp = NULL; + const cmJsonNode_t* snp = srcObjNodePtr->u.childPtr; + + while( snp!=NULL && rc==kOkJsRC ) + { + cmJsonNode_t* dnp; + + assert( snp->typeId == kPairTId ); + + // if the src pair was not found in the dst object ... + if((rc = _cmJsonFindMemberValue(dstObjNodePtr, cmJsonPairLabel(snp), snp->u.childPtr->siblingPtr->typeId, &dnp )) != kOkJsRC ) + { + cmJsonNode_t* newPairNodePtr; + + // the only acceptable error is kNodeNotFoundJsRC + // (in particular we reject kNodeCannotCvtJsRC to avoid duplicating + // pairs with the same name but different types) + if( rc != kNodeNotFoundJsRC ) + goto errLabel; + + // create the new pair and attach it to the dst obj node + if((rc = _cmJsonCreatePair(p,dstObjNodePtr,cmJsonPairLabel(snp),&newPairNodePtr)) != kOkJsRC ) + goto errLabel; + + // duplicate the src pair value node and attcmh it to the new dst node + if( _cmJsonDuplicateNode(p,snp->u.childPtr->siblingPtr,newPairNodePtr) == NULL ) + rc = p->rc; + + // set kTempJsFl on the new node to use possible cleanup on error later + newPairNodePtr->typeId = cmSetFlag(newPairNodePtr->typeId,kTempJsFl); + + } + + + snp = snp->siblingPtr; + + } + + errLabel: + + // for each dst obj pair + cnp = dstObjNodePtr->u.childPtr; + + while( cnp != NULL) + { + // if this child is a new node + if( cmIsFlag(cnp->typeId,kTempJsFl) ) + { + // clear the temp fl + cnp->typeId = cmClrFlag(cnp->typeId,kTempJsFl); + + // if there was an error remove all new pairs + if( rc != kOkJsRC ) + cmJsonRemoveNode(h,cnp,true); + } + + cnp = cnp->siblingPtr; + } + + return rc; +} + + +void _cmJsonSerialCopy( cmJsSerial_t* rp, const void* sp, unsigned sn ) +{ + assert( rp->nextPtr + sn <= rp->endPtr ); + + memcpy(rp->nextPtr,sp,sn); + + rp->nextPtr += sn; +} + +cmJsRC_t _cmJsonSerializeNode( const cmJsonNode_t* np, cmJsSerial_t* rp ) +{ + cmJsRC_t rc = kOkJsRC; + + // on the first pass rp->basePtr will be NULL so collect size information + // on the second pass rp->basePtr will be set so copy out data + + // write the type id of this node + if( rp->basePtr != NULL ) + _cmJsonSerialCopy(rp,&np->typeId,sizeof(np->typeId)); + else + rp->hdr.byteCnt += sizeof(np->typeId); + + rp->hdr.nodeCnt++; + + switch( np->typeId ) + { + + // write the child count + case kObjectTId: + case kArrayTId: + if( rp->basePtr == NULL ) + rp->hdr.byteCnt += sizeof(unsigned); + else + { + + unsigned n = cmJsonChildCount( np ); + + _cmJsonSerialCopy(rp,&n,sizeof(unsigned)); + } + // fall through + + case kPairTId: + { + cmJsonNode_t* cnp = np->u.childPtr; + while(cnp != NULL ) + { + _cmJsonSerializeNode(cnp,rp); + cnp = cnp->siblingPtr; + } + } + break; + + case kStringTId: + // write the string contents + if( rp->basePtr == NULL ) + rp->hdr.byteCnt += strlen(np->u.stringVal) + 1; + else + _cmJsonSerialCopy( rp, np->u.stringVal, strlen(np->u.stringVal)+1 ); + + break; + + case kIntTId: + // write the int value + if( rp->basePtr == NULL ) + rp->hdr.byteCnt += sizeof(np->u.intVal); + else + _cmJsonSerialCopy(rp,&np->u.intVal,sizeof(np->u.intVal)); + break; + + case kRealTId: + // write the real value + if( rp->basePtr == NULL ) + rp->hdr.byteCnt += sizeof(np->u.realVal); + else + _cmJsonSerialCopy(rp, &np->u.realVal, sizeof(np->u.realVal)); + break; + } + + return rc; + +} + +unsigned cmJsonSerialByteCount( const cmJsonNode_t* np ) +{ + cmJsRC_t rc; + cmJsSerial_t vr; + memset(&vr,0,sizeof(vr)); + + // make a first pass to determine the size of the buffer + if((rc = _cmJsonSerializeNode(np,&vr)) != kOkJsRC ) + return rc; + + // increment the buffer size to include the buffer header + return (4*sizeof(unsigned)) + vr.hdr.byteCnt; +} + +cmJsRC_t cmJsonSerialize( const cmJsonNode_t* np, void* buf, unsigned bufByteCnt ) +{ + cmJsRC_t rc = kOkJsRC; + cmJsSerial_t vr; + memset(&vr,0,sizeof(vr)); + + // setup the serial buffer + vr.hdr.id = 'json'; + vr.hdr.byteCnt = bufByteCnt - (2*sizeof(unsigned)); + vr.hdr.version = 0; + vr.basePtr = buf; + vr.nextPtr = vr.basePtr; + vr.endPtr = vr.basePtr + bufByteCnt; + + // write the header recd + _cmJsonSerialCopy(&vr, &vr.hdr.id, sizeof(unsigned)); + _cmJsonSerialCopy(&vr, &vr.hdr.byteCnt, sizeof(unsigned)); + _cmJsonSerialCopy(&vr, &vr.hdr.nodeCnt, sizeof(unsigned)); + _cmJsonSerialCopy(&vr, &vr.hdr.version, sizeof(unsigned)); + + // copy the node data into the serial buffer + if((rc = _cmJsonSerializeNode(np,&vr)) != kOkJsRC ) + return rc; + + vr.basePtr = buf; + vr.nextPtr = vr.basePtr; + _cmJsonSerialCopy(&vr, &vr.hdr.id, sizeof(unsigned)); + _cmJsonSerialCopy(&vr, &vr.hdr.byteCnt, sizeof(unsigned)); + _cmJsonSerialCopy(&vr, &vr.hdr.nodeCnt, sizeof(unsigned)); + + return rc; + +} + +cmJsRC_t cmJsonSerializeTree( cmJsonH_t h, const cmJsonNode_t* np, void** bufPtrPtr, unsigned* bufByteCntPtr) +{ + cmJsRC_t rc; + cmJsSerial_t vr; + memset(&vr,0,sizeof(vr)); + + cmJs_t* p = _cmJsonHandleToPtr(h); + + assert( bufPtrPtr != NULL && bufByteCntPtr != NULL ); + + *bufPtrPtr = NULL; + *bufByteCntPtr = 0; + + if( np == NULL ) + np = p->rootPtr; + + // validate the tree + if((rc = _cmJsonValidateNode(p,np,np->ownerPtr)) != kOkJsRC ) + return rc; + + // increment the buffer size to include the buffer header + p->serialByteCnt = cmJsonSerialByteCount(np); + + // allocate the serial buffer memory + p->serialBufPtr = cmMemResize( char, p->serialBufPtr, p->serialByteCnt ); + + // serialize the tree + if((rc = cmJsonSerialize(np, p->serialBufPtr, p->serialByteCnt )) != kOkJsRC ) + return rc; + + *bufPtrPtr = p->serialBufPtr; + *bufByteCntPtr = p->serialByteCnt; + + return rc; +} + +const void* _cmJsonDeserialAdv( cmJsDeserial_t* rp, unsigned n ) +{ + const void* vp = rp->nextPtr; + rp->nextPtr += n; + assert(rp->nextPtr <= rp->endPtr); + assert(rp->nodeIdx <= rp->nodeCnt); + return vp; +} + +int _cmJsonDeserialInt( cmJsDeserial_t* rp ) +{ return *(const int*)_cmJsonDeserialAdv(rp,sizeof(int)); } + +unsigned _cmJsonDeserialUint( cmJsDeserial_t* rp ) +{ return *(const unsigned*)_cmJsonDeserialAdv(rp,sizeof(unsigned)); } + +double _cmJsonDeserialReal( cmJsDeserial_t* rp ) +{ return *(const double*)_cmJsonDeserialAdv(rp,sizeof(double)); } + + +cmJsRC_t _cmJsonDeserializeNode( cmJs_t* p, cmJsonNode_t* parentPtr, cmJsDeserial_t* rp ) +{ + cmJsRC_t rc = kOkJsRC; + unsigned typeId = _cmJsonDeserialUint(rp); + unsigned childCnt = 0; + cmJsonNode_t* newParentPtr = NULL; + + rp->nodeIdx++; + + switch( typeId ) + { + case kPairTId: + rc = _cmJsonCreateNode(p,parentPtr,typeId,&newParentPtr); + childCnt = 2; + break; + + case kObjectTId: + case kArrayTId: + rc = _cmJsonCreateNode(p,parentPtr,typeId,&newParentPtr); + childCnt = _cmJsonDeserialUint(rp); + break; + + case kStringTId: + { + unsigned sn = strlen(rp->nextPtr); + rc = _cmJsonCreateString( p, parentPtr, rp->nextPtr, sn,NULL); + _cmJsonDeserialAdv(rp,sn+1); + } + break; + + case kIntTId: + { + int v = _cmJsonDeserialInt(rp); + rc = _cmJsonCreateInt( p, parentPtr, v, NULL ); + } + break; + + case kRealTId: + { + double v = _cmJsonDeserialReal(rp); + rc = _cmJsonCreateReal( p, parentPtr, v, NULL ); + } + break; + + case kNullTId: + rc = _cmJsonCreateNull( p, parentPtr, NULL ); + break; + + case kTrueTId: + rc = _cmJsonCreateBool( p, parentPtr, true, NULL ); + break; + + case kFalseTId: + rc = _cmJsonCreateBool( p, parentPtr, false, NULL ); + break; + + default: + assert(0); + break; + } + + if( rc != kOkJsRC ) + return rc; + + // if the current node is a parent + if( childCnt > 0 ) + { + unsigned i; + assert( newParentPtr != NULL ); + + for(i=0; irootPtr : altRootPtr; + const char* buf = (const char*)vbuf; + + memset(&r,0,sizeof(r)); + + r.nextPtr = buf; + r.endPtr = buf + (4*sizeof(unsigned)); // the buf must at least contain a header + + // read the buffer header + unsigned hdrId = _cmJsonDeserialUint(&r); + unsigned byteCnt = _cmJsonDeserialUint(&r); + r.nodeCnt = _cmJsonDeserialUint(&r); + /*unsigned version =*/ _cmJsonDeserialUint(&r); + + if( hdrId != 'json' ) + return _cmJsonError(p,kSerialErrJsRC,"The buffer does not have the correct header."); + + if( byteCnt < (4*sizeof(unsigned)) ) + return _cmJsonError(p,kSerialErrJsRC,"The buffer is too small to be contain any information."); + + // change the buffer end pointer to the correct size based + // on the the byte count stored in the buffer + r.endPtr = buf + byteCnt + (2*sizeof(unsigned)); + + return _cmJsonDeserializeNode(p, rootPtr, &r ); + +} + +cmJsRC_t cmJsonDeserialize( cmJsonH_t h, const void* bufPtr, cmJsonNode_t* altRootPtr ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return _cmJsonDeserialize(p,bufPtr,altRootPtr); +} + +cmJsRC_t cmJsonLeafToString( const cmJsonNode_t* np, cmChar_t* buf, unsigned bufCharCnt ) +{ + const char* cp = NULL; + unsigned n = 0; + + assert( buf!=NULL && bufCharCnt > 0 ); + + switch(np->typeId & kMaskTId ) + { + case kStringTId: + cp = np->u.stringVal==NULL ? "" : np->u.stringVal; + break; + + case kNullTId: + cp = "null"; + break; + + case kTrueTId: + cp = "true"; + break; + + case kFalseTId: + cp = "false"; + break; + + case kIntTId: + n = snprintf(buf,bufCharCnt,"%i",np->u.intVal)+1; + break; + + case kRealTId: + n = snprintf(buf,bufCharCnt,"%f",np->u.realVal)+1; + break; + + + default: + assert(0); + return kInvalidNodeTypeJsRC; + + } + + if( cp != NULL ) + { + n = strlen(cp)+1; + + if( bufCharCnt < n ) + n = bufCharCnt; + + strncpy(buf,cp,n); + + /* + n = strlen(np->u.stringVal)+1; + + if( bufCharCnt < n ) + n = bufCharCnt; + + strncpy(buf,np->u.stringVal,n); + */ + } + + buf[bufCharCnt-1]=0; + + assert( n>0 && n < bufCharCnt ); + + return n == bufCharCnt ? kBufTooSmallJsRC : kOkJsRC ; +} + + +cmJsRC_t _cmJsonRptCsvTokErr( cmJs_t* p, unsigned lineNo, cmLexH lexH, const char* iFn ) +{ + unsigned n = cmMin(31,cmLexTokenCharCount(lexH)); + char b[n+1]; + strncpy(b,cmLexTokenText(lexH),n); + b[n]=0; + return _cmJsonError( p, kCsvErrJsRC, "Unexpected token '%s' during CSV parse on line %i of '%s'.",b,cmLexCurrentLineNumber(lexH),cmStringNullGuard(iFn)); + +} + +cmJsRC_t _cmJsonRptCsvTypeErr( cmJs_t* p, unsigned lineNo, cmLexH lexH, const char* iFn, const char* actualTypeStr, unsigned expTypeId ) +{ + unsigned n = cmMin(31,cmLexTokenCharCount(lexH)); + char b[n+1]; + strncpy(b,cmLexTokenText(lexH),n); + b[n]=0; + return _cmJsonError( p, kCsvErrJsRC, "Unexpected token '%s' during CSV parse on line %i of '%s'.\nExpected type:%s actual type:%s",b,cmLexCurrentLineNumber(lexH),cmStringNullGuard(iFn),_cmJsonNodeTypeIdToLabel(expTypeId),actualTypeStr); +} + + + +cmJsRC_t cmJsonFromCSV( cmJsonH_t h, const char* iFn, cmJsonNode_t* parentNodePtr, cmJsonNode_t** arrayNodePtrPtr ) +{ + enum + { + kCommaTokId = kUserLexTId+1, + kIntTokId, + kRealTokId, + kTrueTokId, + kFalseTokId, + kStringTokId, + kBoolTokId + }; + + typedef struct field_str + { + char* fieldLabel; + unsigned typeId; + struct field_str* linkPtr; + } field_t; + + cmJsRC_t rc = kOkJsRC; + cmJs_t* p = _cmJsonHandleToPtr(h); + cmLexH lexH = cmLexInit( NULL, 0, 0, p->err.rpt ); + field_t* fieldList = NULL; + cmJsonNode_t* arrayNodePtr = NULL; + cmJsonNode_t* objNodePtr = NULL; + unsigned lineNo = 0; + field_t* fieldPtr = NULL; + field_t* lp = NULL; + unsigned tokId; + unsigned fieldIdx = 0; + + // validate the init state of the lexer + if( cmLexIsValid(lexH) == false ) + { + rc = _cmJsonError( p, kLexErrJsRC, "Lexer initialization failed on CSV parse of '%s'.",cmStringNullGuard(iFn)); + goto errLabel; + } + + // register CSV specific tokens + cmLexRegisterToken( lexH, kCommaTokId, ","); + cmLexRegisterToken( lexH, kIntTokId, "int"); + cmLexRegisterToken( lexH, kRealTokId, "real"); + cmLexRegisterToken( lexH, kTrueTokId, "true"); + cmLexRegisterToken( lexH, kFalseTokId, "false"); + cmLexRegisterToken( lexH, kStringTokId, "string"); + cmLexRegisterToken( lexH, kBoolTokId, "bool"); + + // lex the file + if( cmLexSetFile( lexH, iFn ) != kOkLexRC ) + { + rc = _cmJsonError( p, kLexErrJsRC, "Lex failed on CSV parse of '%s'.",cmStringNullGuard(iFn)); + goto errLabel; + } + + // create the parent array + if((arrayNodePtr = cmJsonCreateArray( h, parentNodePtr )) == NULL ) + { + rc = _cmJsonError( p, kCsvErrJsRC, "CSV array create failed during parse of '%s'.",cmStringNullGuard(iFn)); + goto errLabel; + } + + // iterate through the lexer file + while(((tokId = cmLexGetNextToken(lexH)) != kErrorLexTId) && (tokId != kEofLexTId) ) + { + unsigned fieldTypeTokId = kInvalidTId; + + switch( cmLexCurrentLineNumber(lexH) ) + { + // line 1 contains the field type labels (e.g. int,real,string,true,false,bool) + case 1: + switch(tokId) + { + case kCommaTokId: + break; + + case kIntTokId: + fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kIntTId : fieldTypeTokId; + + case kRealTokId: + fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kRealTId : fieldTypeTokId; + + case kTrueTokId: + fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kTrueTId : fieldTypeTokId; + + case kFalseTokId: + fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kFalseTId : fieldTypeTokId; + + case kBoolTokId: + fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kFalseTId : fieldTypeTokId; + + case kStringTokId: + fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kStringTId : fieldTypeTokId; + + // create and intitialize a new field + field_t* rp = cmMemAllocZ( field_t, 1 ); + + rp->fieldLabel = NULL; + rp->typeId = fieldTypeTokId; + rp->linkPtr = NULL; + + // and add it to the end of the field list + if( fieldList == NULL ) + fieldList = rp; + else + fieldPtr->linkPtr = rp; + + // fieldPtr points to the end of the list + fieldPtr = rp; + + break; + + default: + rc = _cmJsonRptCsvTokErr( p, 1, lexH, iFn ); + goto errLabel; + + } + break; + + // line 2 contains the field labels + case 2: + if( fieldIdx == 0 ) + fieldPtr = fieldList; + ++fieldIdx; + + switch(tokId) + { + case kCommaTokId: + break; + + // all line 2 tokens must be identifiers or q-strings + case kIdentLexTId: + case kQStrLexTId: + if( fieldPtr == NULL ) + { + rc = _cmJsonError( p, kCsvErrJsRC, "More fields on line 2 than type specifiers on line 1 of '%s'.",cmStringNullGuard(iFn)); + goto errLabel; + } + else + { + // set the field name in the field list + unsigned n = cmLexTokenCharCount(lexH); + fieldPtr->fieldLabel = cmMemAllocZ( char, n+1 ); + strncpy(fieldPtr->fieldLabel,cmLexTokenText(lexH),n); + fieldPtr->fieldLabel[n] = 0; + fieldPtr = fieldPtr->linkPtr; + } + break; + + default: + rc = _cmJsonRptCsvTokErr( p, 2, lexH, iFn ); + goto errLabel; + + } + break; + + // lines 3 to end of file contain data + default: + { + int ival = 0; + cmReal_t rval = 0; + + // if we are starting a new line in the CSV file + if( lineNo != cmLexCurrentLineNumber(lexH) ) + { + // verify that field ptr is pointing to the end of the field list + if( fieldPtr != NULL ) + { + rc = _cmJsonError( p, kCsvErrJsRC, "Missing columns were detected on line %i in CSV file '%s'.", lineNo, cmStringNullGuard(iFn)); + goto errLabel; + } + + fieldPtr = fieldList; + lineNo = cmLexCurrentLineNumber(lexH); + + // create the object to hold the fields on this line + if((objNodePtr = cmJsonCreateObject( h, arrayNodePtr )) == NULL ) + { + rc = _cmJsonError( p, kCsvErrJsRC, "Object node create failed on line %i in CSV file '%s'.",lineNo,cmStringNullGuard(iFn)); + goto errLabel; + } + } + + if( tokId == kCommaTokId ) + continue; + + if( fieldPtr == NULL ) + { + rc = _cmJsonError( p, kCsvErrJsRC, "More columns than fields on line %i in CSV file '%s'.", lineNo,cmStringNullGuard(iFn)); + goto errLabel; + } + + // given the tokens type convert the token string into a value + switch(tokId) + { + + case kRealLexTId: +#ifdef CM_FLOAT_REAL + rval = cmLexTokenFloat(lexH); +#else + rval = cmLexTokenDouble(lexH); +#endif + ival = (int)rval; + + if( fieldPtr->typeId == kStringTId ) + { + rc = _cmJsonRptCsvTypeErr(p, lineNo, lexH, iFn, "numeric", fieldPtr->typeId ); + goto errLabel; + + } + break; + + case kIntLexTId: + case kHexLexTId: + ival = cmLexTokenInt(lexH); + rval = ival; + if( fieldPtr->typeId == kStringTId ) + { + rc = _cmJsonRptCsvTypeErr(p, lineNo, lexH, iFn, "numeric", fieldPtr->typeId ); + goto errLabel; + + } + break; + + case kTrueTokId: + ival = 1; + rval = 1.0; + break; + + case kFalseTokId: + ival = 0; + rval = 0.0; + break; + + case kIdentLexTId: + case kQStrLexTId: + if( fieldPtr->typeId != kStringTId ) + { + rc = _cmJsonRptCsvTypeErr(p, lineNo, lexH, iFn, "string", fieldPtr->typeId ); + goto errLabel; + } + break; + + default: + rc = _cmJsonRptCsvTokErr( p, lineNo, lexH, iFn ); + goto errLabel; + } + + + // create the pair object from the current field label and value + switch(fieldPtr->typeId) + { + case kIntTId: + rc = cmJsonInsertPairInt( h, objNodePtr, fieldPtr->fieldLabel, ival ); + break; + + case kRealTId: + rc = cmJsonInsertPairReal( h, objNodePtr, fieldPtr->fieldLabel, rval ); + break; + + case kTrueTId: + case kFalseTId: + rc = cmJsonInsertPairBool( h, objNodePtr, fieldPtr->fieldLabel, ival ); + break; + + case kStringTId: + rc = cmJsonInsertPairStringN( h, objNodePtr, fieldPtr->fieldLabel, cmLexTokenText(lexH), cmLexTokenCharCount(lexH) ); + break; + + default: + { + assert(0); + goto errLabel; + } + } + + if( rc != kOkJsRC ) + goto errLabel; + + + fieldPtr = fieldPtr->linkPtr; + } + break; + + } + } + + errLabel: + if( cmLexFinal(&lexH) != kOkLexRC ) + { + rc = _cmJsonError( p, kLexErrJsRC, "Lexer finalize failed on CSV parse of '%s'.",cmStringNullGuard(iFn)); + goto errLabel; + } + + lp = fieldList; + while( lp!=NULL ) + { + field_t* pp = lp->linkPtr; + cmMemPtrFree(&lp->fieldLabel); + cmMemPtrFree(&lp); + lp = pp; + } + + if( rc != kOkJsRC ) + _cmJsonRemoveNode( p, arrayNodePtr, true, true ); + + if( rc == kOkJsRC && arrayNodePtrPtr != NULL ) + *arrayNodePtrPtr = arrayNodePtr; + + return rc; +} + +cmJsRC_t cmJsonToCSV( cmJsonH_t h, const char* oFn, const cmJsonNode_t* arrayNodePtr ) +{ + assert( arrayNodePtr->typeId == kArrayTId ); + + typedef struct r_str + { + const char* fieldLabel; + unsigned typeId; + struct r_str* linkPtr; + } field_t; + + cmJs_t* p = _cmJsonHandleToPtr(h); + cmJsRC_t rc = kOkJsRC; + unsigned arrayCnt = cmJsonChildCount(arrayNodePtr); + field_t* fieldList = NULL; + FILE* fp = NULL; + field_t* lp = NULL; + unsigned i,j; + + // for each object in the array + for(i=0; itypeId == kObjectTId ); + + // for each pair in the object + for(j=0; jlinkPtr ) + if( strcmp(lp->fieldLabel,pairLabel) == 0) + { + unsigned typeId = cmJsonPairTypeId(pairNodePtr); + + switch( typeId ) + { + case kIntTId: + case kRealTId: + case kTrueTId: + case kFalseTId: + case kStringTId: + break; + default: + rc = _cmJsonError( p, kInvalidNodeTypeJsRC, "Field '%s' has type '%s' which cannot be written by cmJsonToCSV().",cmStringNullGuard(pairLabel),_cmJsonNodeTypeIdToLabel(typeId) ); + goto errLabel; + + } + + if( typeId != lp->typeId ) + { + rc = _cmJsonError( p, kInvalidNodeTypeJsRC, "All nodes for a field label '%s' do not have the same type. '%s' != '%s'",cmStringNullGuard(pairLabel), _cmJsonNodeTypeIdToLabel(typeId), _cmJsonNodeTypeIdToLabel(lp->typeId) ); + goto errLabel; + } + + break; + } + + // if this field was not found then insert it + if( lp == NULL ) + { + field_t* rp = (field_t*)cmLHeapAlloc(p->heapH,sizeof(field_t)); + rp->fieldLabel = pairLabel; + rp->linkPtr = fieldList; + rp->typeId = cmJsonPairTypeId(pairNodePtr); + fieldList = rp; + } + } + } + + // create the output file + if((fp = fopen(oFn,"wt")) == NULL ) + { + rc = _cmJsonError( p, kFileCreateErrJsRC, "CSV file '%s' create failed.", oFn ); + goto errLabel; + } + + // write the field type + lp = fieldList; + for(; lp!=NULL; lp=lp->linkPtr) + { + fprintf(fp,"%s", _cmJsonNodeTypeIdToLabel(lp->typeId)); + + if( lp->linkPtr != NULL ) + fprintf(fp,","); + } + fprintf(fp,"\n"); + + // write the field label + lp = fieldList; + for(; lp!=NULL; lp=lp->linkPtr) + { + fprintf(fp,"\"%s\"", lp->fieldLabel); + + if( lp->linkPtr != NULL ) + fprintf(fp,","); + } + fprintf(fp,"\n"); + + + // for each object in the array + for(i=0; ilinkPtr,++j) + { + cmJsonNode_t* valNodePtr; + + // ... locate the pair given the field label + if((valNodePtr = cmJsonFindValue(h,lp->fieldLabel, objNodePtr, lp->typeId )) == NULL) + { + // no pair was found for the field label - output a NULL value + switch( lp->typeId ) + { + case kIntTId: + fprintf(fp,"%i",0); + break; + + case kRealTId: + fprintf(fp,"%f",0.0); + break; + + case kTrueTId: + case kFalseTId: + fprintf(fp,"%i",0); + break; + + case kStringTId: + fprintf(fp,"\"\""); + break; + + default: + assert(0); + break; + } + + //rc = _cmJsonError( p, kNodeNotFoundJsRC,"No field with label '%s' was found.", lp->fieldLabel); + //goto errLabel; + } + else + { + switch( valNodePtr->typeId ) + { + case kIntTId: + fprintf(fp,"%i", valNodePtr->u.intVal); + break; + + case kRealTId: + fprintf(fp,"%e", valNodePtr->u.realVal); + break; + + case kTrueTId: + case kFalseTId: + fprintf(fp,"%i", valNodePtr->u.boolVal); + break; + + case kStringTId: + fprintf(fp,"\"%s\"", valNodePtr->u.stringVal); + break; + + default: + assert(0); + break; + } + } + if( j < fieldCnt-1 ) + fprintf(fp,","); + + } + + fprintf(fp,"\n"); + + } + + errLabel: + + if( fp != NULL ) + fclose(fp); + + lp = fieldList; + while( lp!=NULL ) + { + field_t* pp = lp->linkPtr; + cmLHeapFree(p->heapH,lp); + lp = pp; + } + + return rc; +} + + +void _cmJsonPrintIndent( cmRpt_t* rpt, unsigned indent ) +{ + if( indent ) + { + char spaces[indent+1]; + spaces[indent]=0; + memset(spaces,' ',indent); + cmRptPrint(rpt,spaces); + } +} + + +void _cmJsonPrintNode( const cmJsonNode_t* np, cmRpt_t* rpt, unsigned indent ) +{ + unsigned childCnt = 0; + char eoObjStr[] = "}"; + char eoArrStr[] = "]"; + char eoPairStr[] = "\n"; + char commaStr[] = ",\n"; + char colonStr[] = ": "; + const char* eleStr = NULL; + const char* lastEleStr = NULL; + const char* eoStr = NULL; + unsigned localIndent = 0; + + + switch(np->typeId) + { + case kObjectTId: + cmRptPrint(rpt,"\n"); + _cmJsonPrintIndent(rpt,indent); + cmRptPrint(rpt,"{\n"); + childCnt = cmJsonChildCount(np); + eoStr = eoObjStr; + localIndent = 2; + break; + + case kArrayTId: + cmRptPrint(rpt,"\n"); + _cmJsonPrintIndent(rpt,indent); + cmRptPrint(rpt,"[\n"); + childCnt = cmJsonChildCount(np); + eoStr = eoArrStr; + localIndent = 2; + eleStr = commaStr; + lastEleStr = "\n"; + break; + + case kPairTId: + childCnt = cmJsonChildCount(np); + eleStr = colonStr; + eoStr = eoPairStr; + break; + + case kStringTId: + { + const char* fmt0 = "\"%s\" "; + const char* fmt1 = "%s"; + const char* fmt = fmt0; + + // if this string is the label part of a pair + if( np->u.stringVal != NULL && np->ownerPtr != NULL && cmJsonIsPair(np->ownerPtr) && np->ownerPtr->u.childPtr == np ) + { + // and the label has no white space + char* cp = np->u.stringVal; + while( *cp!=0 && isspace(*cp)==false ) + ++cp; + + // then print without quotes + if( *cp == 0 ) + fmt = fmt1; + } + cmRptPrintf(rpt,fmt,np->u.stringVal==NULL ? "" : np->u.stringVal); + } + break; + + case kIntTId: + cmRptPrintf(rpt,"%i ",np->u.intVal); + break; + + case kRealTId: + cmRptPrintf(rpt,"%f ",np->u.realVal); + break; + + case kNullTId: + cmRptPrint(rpt,"null "); + break; + + case kTrueTId: + cmRptPrint(rpt,"true "); + break; + + case kFalseTId: + cmRptPrint(rpt,"false "); + break; + } + + if( childCnt ) + { + indent += localIndent; + + unsigned i; + cmJsonNode_t* cnp = np->u.childPtr; + + for(i=0; itypeId != kPairTId ) + _cmJsonPrintIndent(rpt,indent); + + _cmJsonPrintNode(cnp,rpt,indent); + + cnp = cnp->siblingPtr; + + if( i < childCnt-1 && eleStr != NULL ) + cmRptPrint(rpt,eleStr); + + if( i == childCnt-1 && lastEleStr != NULL ) + cmRptPrint(rpt,lastEleStr); + } + + indent -= localIndent; + } + + if( eoStr != NULL ) + { + _cmJsonPrintIndent(rpt,indent); + cmRptPrint(rpt,eoStr); + } + +} + + +void cmJsonPrintTree( const cmJsonNode_t* np, cmRpt_t* rpt ) +{ + _cmJsonPrintNode(np,rpt,0); +} + +void _cmJsPrintFile(void* cmRptUserPtr, const cmChar_t* text) +{ + cmFileH_t* hp = (cmFileH_t*)cmRptUserPtr; + cmFilePrint(*hp,text); +} + +cmJsRC_t cmJsonWrite( cmJsonH_t h, const cmJsonNode_t* np, const cmChar_t* fn ) +{ + cmRpt_t rpt; + cmFileH_t fh = cmFileNullHandle; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if( np == NULL ) + np = cmJsonRoot(h); + + // create the output file + if( cmFileOpen(&fh,fn,kWriteFileFl,p->err.rpt) != kOkFileRC ) + return _cmJsonError( p, kFileCreateErrJsRC, "Output file '%s' create failed.", fn ); + + // setup a reporter to write to the file + cmRptSetup(&rpt,_cmJsPrintFile,_cmJsPrintFile,&fh); + + // print the tree to the file + cmJsonPrintTree(np,&rpt); + + // close the file + if( cmFileClose(&fh) != kOkFileRC ) + return _cmJsonError( p, kFileCloseErrJsRC, "Output file '%s' close failed.", fn ); + + return kOkJsRC; +} + +cmJsRC_t cmJsonReport( cmJsonH_t h ) +{ + cmJsRC_t rc; + cmJs_t* p = _cmJsonHandleToPtr(h); + + if((rc = cmJsonValidateTree(h)) != kOkJsRC ) + return rc; + + if(p->rootPtr != NULL ) + _cmJsonPrintNode(p->rootPtr,p->err.rpt,0); + + return rc; +} + +cmJsRC_t cmJsonErrorCode( cmJsonH_t h ) +{ + cmJs_t* p = _cmJsonHandleToPtr(h); + return p->rc; +} + + void cmJsonClearErrorCode( cmJsonH_t h ) + { + cmJs_t* p = _cmJsonHandleToPtr(h); + p->rc = kOkJsRC; + } + +void _cmJsonTestVPrint( void* rptDataPtr, const char* fmt, va_list vl ) +{ + vfprintf(stdout,fmt,vl); +} + +void _cmJsonTestPrint( void* userPtr, const cmChar_t* text ) +{ + fputs(text,stdout); +} + + +//{ { label:cmJsonEx } +//( +// cmJsonTest() demonstrates some JSON tree operations. +//) +//[ +cmJsRC_t cmJsonTest( const char* fn, cmCtx_t* ctx ) +{ + cmJsRC_t rc = kOkJsRC; + cmJsRC_t rc1 = kOkJsRC; + cmJsonH_t h = cmJsonNullHandle; + cmJsonH_t h1 = cmJsonNullHandle; + void* sbp = NULL; + unsigned sbn = 0; + cmJsonNode_t* np = NULL; + cmRpt_t* rpt = &ctx->rpt; + + // initialize an empty JSON tree + if((rc = cmJsonInitialize(&h,ctx)) != kOkJsRC ) + goto errLabel; + + // load the tree from a file + if((rc = cmJsonParseFile(h,fn,NULL)) != kOkJsRC ) + goto errLabel; + + // print the tree + cmJsonReport(h); + + // find an array member named 'mem14' + if((np = cmJsonFindValue(h,"mem14",NULL,kArrayTId)) == NULL ) + cmRptPrint(rpt,"'mem14' not found.\n"); + else + { + cmRptPrint(rpt,"'mem14' found.\n"); + cmJsonPrintTree(np,rpt); + } + + // remove the array node from the tree + cmJsonRemoveNode(h,np, true); + cmRptPrint(rpt,"mem14 removed.\n"); + + // print the tree with the array node removed + cmJsonPrintTree( cmJsonRoot(h), rpt ); + + // serialize the tree into a dynamically allocated + // buffer sbp[sbn]. + if((rc = cmJsonSerializeTree(h,NULL,&sbp,&sbn)) != kOkJsRC ) + goto errLabel; + else + cmRptPrint(rpt,"***Serialize Ok.****\n"); + + // initialize an empty JSON tree + if((rc = cmJsonInitialize(&h1,ctx)) != kOkJsRC ) + goto errLabel; + + // deserialize sbp[sbn] into the empty tree + if((rc = cmJsonDeserialize(h1,sbp,NULL)) != kOkJsRC ) + goto errLabel; + else + { + cmJsonPrintTree( cmJsonRoot(h1),rpt); + cmRptPrint(rpt,"***Deserialize Ok.****\n"); + } + + // find an member node named 'mem5' + if((np = cmJsonFindValue(h,"mem5",NULL,0)) == NULL ) + cmRptPrint(rpt,"mem5 not found."); + + // merge two sub-trees + if( cmJsonMergeObjectNodes( h, np->u.childPtr, + np->u.childPtr->siblingPtr) != kOkJsRC ) + { + cmRptPrint(rpt,"merge failed."); + } + else + { + cmJsonReport(h); + } + + errLabel: + + // release the JSON trees + rc = cmJsonFinalize(&h); + rc1 = cmJsonFinalize(&h1); + + return rc == kOkJsRC ? rc1 : rc; +} +//] +//} diff --git a/cmJson.h b/cmJson.h new file mode 100644 index 0000000..f187adc --- /dev/null +++ b/cmJson.h @@ -0,0 +1,504 @@ +#ifndef cmJson_h +#define cmJson_h + + +#ifdef __cplusplus +extern "C" { +#endif + //{ + //( + // + // Limitations: + // + // 1. Accpets two digit hex sequences with + // the \\u escape command. JSON specifies 4 digits. + // + // 2. The scientific notation for real numbers is limited to + // exponent prefixes: e,E,e-,E-. The prefixes e+ and E+ are + // not recognized by cmLex. + // + // Extensions: + // + // 1. Will accept C style identifiers where JSON demands + // quoted strings. + // + // 2. Will accept C style hex notation (0xddd) for integer values. + // + //) + + //( + + // JSON data type flags + enum + { + kInvalidTId = 0x0000, + kObjectTId = 0x0001, // children are pairs + kPairTId = 0x0002, // children are string : value pairs + kArrayTId = 0x0004, // children may be of any type + kStringTId = 0x0008, // terminal + kNullTId = 0x0040, // terminal + kIntTId = 0x0010, // terminal + kRealTId = 0x0020, // terminal + kTrueTId = 0x0080, // terminal + kFalseTId = 0x0100, // terminal + + kMaskTId = 0x01ff, + + kOptArgJsFl = 0x0800, // only used by cmJsonVMemberValues() + kTempJsFl = 0x1000, // used internally + + kNumericTId = kIntTId | kRealTId | kTrueTId | kFalseTId, + kBoolTId = kTrueTId | kFalseTId + + }; + + enum + { + kOkJsRC, + kMemAllocErrJsRC, + kLexErrJsRC, + kSyntaxErrJsRC, + kFileOpenErrJsRC, + kFileCreateErrJsRC, + kFileReadErrJsRC, + kFileSeekErrJsRC, + kFileCloseErrJsRC, + kInvalidHexEscapeJsRC, + kSerialErrJsRC, + kNodeNotFoundJsRC, + kNodeCannotCvtJsRC, + kCannotRemoveLabelJsRC, + kInvalidNodeTypeJsRC, + kValidateFailJsRC, + kCsvErrJsRC, + kBufTooSmallJsRC + }; + + typedef unsigned cmJsRC_t; + + typedef cmHandle_t cmJsonH_t; + + // JSON tree node + typedef struct cmJsonNode_str + { + + unsigned typeId; // id of this node + struct cmJsonNode_str* siblingPtr; // next ele in array or member list + struct cmJsonNode_str* ownerPtr; // parent node ptr + + union + { + // childPtr usage: + // object: first pair + // array: first element + // pair: string + struct cmJsonNode_str* childPtr; + int intVal; // valid if typeId == kIntTId + double realVal; // valid if typeId == kRealTId + char* stringVal; // valid if typeId == kStringTId + bool boolVal; // valid if typeId == kTrueTId || kFalseTId + } u; + + } cmJsonNode_t; + + extern cmJsonH_t cmJsonNullHandle; + + // Initialize a json parser/tree object + cmJsRC_t cmJsonInitialize( cmJsonH_t* hp, cmCtx_t* ctx ); + + // Equivalent to cmJsonInitialize() followed by cmJsonParseFile() + cmJsRC_t cmJsonInitializeFromFile( cmJsonH_t* hp, const char* fn, cmCtx_t* ctx ); + + // Equivalent to cmJsonInitialize() followed by cmJsonParse(h,buf,cnt,NULL). + cmJsRC_t cmJsonInitializeFromBuf( cmJsonH_t* hp, cmCtx_t* ctx, const char* buf, unsigned bufByteCnt ); + + // Release all the resources held by the tree. + cmJsRC_t cmJsonFinalize( cmJsonH_t* hp ); + + // Returns true if 'h' is a valid cmJsonH_t handle. + bool cmJsonIsValid( cmJsonH_t h ); + + // Build the internal tree by parsing a text buffer. + // altRootPtr is an optional alternate root ptr which can be used + // append to an existing tree. Set to altRootPtr to + // NULL to append the tree to the internal root. + // If altRootPtr is given it must point ot either an array or + // object node. + cmJsRC_t cmJsonParse( cmJsonH_t h, const char* buf, unsigned bufCharCnt, cmJsonNode_t* altRootPtr ); + + // Fills a text buffer from a file and calls cmJsonParse(). + cmJsRC_t cmJsonParseFile( cmJsonH_t h, const char* fn, cmJsonNode_t* altRootPtr ); + + // Return the root node of the internal tree. + cmJsonNode_t* cmJsonRoot( cmJsonH_t h ); + + // Return the tree to the post initialize state by clearing the internal tree. + cmJsRC_t cmJsonClearTree( cmJsonH_t h ); + + // Node type predicates. + bool cmJsonIsObject( const cmJsonNode_t* np ); + bool cmJsonIsArray( const cmJsonNode_t* np ); + bool cmJsonIsPair( const cmJsonNode_t* np ); + bool cmJsonIsString( const cmJsonNode_t* np ); + bool cmJsonIsInt( const cmJsonNode_t* np ); + bool cmJsonIsReal( const cmJsonNode_t* np ); + bool cmJsonIsBool( const cmJsonNode_t* np ); + + + // Return the count of child nodes of 'np'. + // Note that only object,array, and pair nodes have children. + unsigned cmJsonChildCount( const cmJsonNode_t* np ); + + // Return the node at 'index' from an element array. + // 'np must point to an array element. + cmJsonNode_t* cmJsonArrayElement( cmJsonNode_t* np, unsigned index ); + const cmJsonNode_t* cmJsonArrayElementC( const cmJsonNode_t* np, unsigned index ); + + // Return the child value node of a pair with a label node equal to 'label'. + // Set 'root' to NULL to begin the search at the internal tree root node. + // Set 'typeIdMask' with all type flags to match. + // If 'typeIdMask' is equal to kInvalidTId then all types will match. + cmJsonNode_t* cmJsonFindValue( cmJsonH_t h, const char* label, const cmJsonNode_t* root, unsigned typeIdMask ); + + // Return the value node of a pair at the end of an object path. + // 'path' is a '/' seperated list of object names where the final + // object specifies the pair label for the value to return. + const cmJsonNode_t* cmJsonFindPathValueC( cmJsonH_t h, const char* path, const cmJsonNode_t* root, unsigned typeIdMask ); + cmJsonNode_t* cmJsonFindPathValue( cmJsonH_t h, const char* path, const cmJsonNode_t* root, unsigned typeIdMask ); + + // Return the node value. If 'np' does not point to the same type as + // specified in '*retPtr' then the value is converted if possible. + // If the value cannot be converted function returns a 'kNodeCannotCvtJsRC' + // error + cmJsRC_t cmJsonUIntValue( const cmJsonNode_t* np, unsigned* retPtr ); + cmJsRC_t cmJsonIntValue( const cmJsonNode_t* np, int* retPtr ); + cmJsRC_t cmJsonRealValue( const cmJsonNode_t* np, double* retPtr ); + cmJsRC_t cmJsonBoolValue( const cmJsonNode_t* np, bool* retPtr ); + cmJsRC_t cmJsonStringValue( const cmJsonNode_t* np, const char ** retPtrPtr ); + cmJsRC_t cmJsonPairNode( const cmJsonNode_t* vp, cmJsonNode_t ** retPtrPtr ); + cmJsRC_t cmJsonArrayNode( const cmJsonNode_t* vp, cmJsonNode_t ** retPtrPtr ); + cmJsRC_t cmJsonObjectNode( const cmJsonNode_t* vp, cmJsonNode_t ** retPtrPtr ); + + + // Return the label from a pair object. + const char* cmJsonPairLabel( const cmJsonNode_t* pairPtr ); + unsigned cmJsonPairTypeId( const cmJsonNode_t* pairPtr ); + cmJsonNode_t* cmJsonPairValue( cmJsonNode_t* pairPtr ); + + // Return values associated with the member values in the object + // pointed to by object objectNodePtr. + cmJsRC_t cmJsonUIntMember( const cmJsonNode_t* objectNodePtr, const char* label, unsigned* retPtr ); + cmJsRC_t cmJsonIntMember( const cmJsonNode_t* objectNodePtr, const char* label, int* retPtr ); + cmJsRC_t cmJsonRealMember( const cmJsonNode_t* objectNodePtr, const char* label, double* retPtr ); + cmJsRC_t cmJsonBoolMember( const cmJsonNode_t* objectNodePtr, const char* label, bool* retPtr ); + cmJsRC_t cmJsonStringMember( const cmJsonNode_t* objectNodePtr, const char* label, const char** retPtrPtr ); + + // Returns array or object nodes. + cmJsRC_t cmJsonNodeMember( const cmJsonNode_t* objectNodePtr, const char* label, cmJsonNode_t** nodePtrPtr ); + + // Returns the value of the member pair named by 'label' or NULL if the + // named pair does not exist. + cmJsonNode_t* cmJsonNodeMemberValue( const cmJsonNode_t* np, const char* label ); + + // Return values for specified pairs from an object node. + // + // The var args syntax is: