From b108da1911a3b2ca4c11ceb0e428b9935c03b0eb Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 29 Oct 2012 20:52:39 -0700 Subject: [PATCH] Initial commit --- Makefile.am | 75 + app/cmOnset.c | 377 +++ app/cmOnset.h | 55 + app/cmPickup.c | 876 +++++ app/cmPickup.h | 96 + app/cmScore.c | 635 ++++ app/cmScore.h | 82 + app/cmTimeLine.c | 1542 +++++++++ app/cmTimeLine.h | 217 ++ cmApBuf.c | 910 +++++ cmApBuf.h | 229 ++ cmAudDsp.c | 1224 +++++++ cmAudDsp.h | 55 + cmAudDspIF.c | 291 ++ cmAudDspIF.h | 167 + cmAudDspLocal.c | 147 + cmAudDspLocal.h | 38 + cmAudLabelFile.c | 316 ++ cmAudLabelFile.h | 50 + cmAudioAggDev.c | 942 ++++++ cmAudioAggDev.h | 104 + cmAudioBuf.c | 285 ++ cmAudioBuf.h | 63 + cmAudioFile.c | 1696 ++++++++++ cmAudioFile.h | 305 ++ cmAudioFileDev.c | 561 ++++ cmAudioFileDev.h | 74 + cmAudioFileMgr.c | 483 +++ cmAudioFileMgr.h | 67 + cmAudioNrtDev.c | 355 ++ cmAudioNrtDev.h | 65 + cmAudioPort.c | 799 +++++ cmAudioPort.h | 148 + cmAudioPortFile.c | 280 ++ cmAudioPortFile.h | 47 + cmAudioSys.c | 1415 ++++++++ cmAudioSys.h | 298 ++ cmComplexTypes.h | 87 + cmCsv.c | 1146 +++++++ cmCsv.h | 149 + cmCtx.c | 22 + cmCtx.h | 53 + cmDocMain.h | 54 + cmErr.c | 137 + cmErr.h | 84 + cmFeatFile.c | 2455 ++++++++++++++ cmFeatFile.h | 279 ++ cmFile.c | 555 +++ cmFile.h | 203 ++ cmFileSys.c | 1285 +++++++ cmFileSys.h | 231 ++ cmFloatTypes.h | 81 + cmFrameFile.c | 2210 ++++++++++++ cmFrameFile.h | 360 ++ cmGlobal.c | 2 + cmGlobal.h | 154 + cmGnuPlot.c | 1121 +++++++ cmGnuPlot.h | 102 + cmGr.c | 2670 +++++++++++++++ cmGr.h | 871 +++++ cmGrDevCtx.c | 530 +++ cmGrDevCtx.h | 196 ++ cmGrPage.c | 1471 ++++++++ cmGrPage.h | 164 + cmGrPlot.c | 1223 +++++++ cmGrPlot.h | 260 ++ cmGrPlotAudio.c | 180 + cmGrPlotAudio.h | 19 + cmJson.c | 4077 ++++++++++++++++++++++ cmJson.h | 504 +++ cmKeyboard.c | 235 ++ cmKeyboard.h | 37 + cmLex.c | 910 +++++ cmLex.h | 139 + cmLib.c | 309 ++ cmLib.h | 63 + cmLinkedHeap.c | 358 ++ cmLinkedHeap.h | 90 + cmMain.c | 33 + cmMallocDebug.c | 136 + cmMallocDebug.h | 168 + cmMath.c | 369 ++ cmMath.h | 67 + cmMem.c | 699 ++++ cmMem.h | 231 ++ cmMidi.c | 227 ++ cmMidi.h | 140 + cmMidiFile.c | 1036 ++++++ cmMidiFile.h | 163 + cmMidiFilePlay.c | 567 ++++ cmMidiFilePlay.h | 59 + cmMidiPort.c | 458 +++ cmMidiPort.h | 89 + cmMsgProtocol.c | 122 + cmMsgProtocol.h | 233 ++ cmOp.c | 83 + cmOp.h | 30 + cmPgmOpts.c | 1149 +++++++ cmPgmOpts.h | 191 ++ cmPrefix.h | 16 + cmPrefs.c | 1495 +++++++++ cmPrefs.h | 157 + cmProc.c | 5242 +++++++++++++++++++++++++++++ cmProc.h | 753 +++++ cmProc2.c | 4233 +++++++++++++++++++++++ cmProc2.h | 876 +++++ cmProc3.c | 3627 ++++++++++++++++++++ cmProc3.h | 700 ++++ cmProcObj.c | 221 ++ cmProcObj.h | 172 + cmProcTest.c | 1557 +++++++++ cmProcTest.h | 17 + cmRbm.c | 698 ++++ cmRbm.h | 45 + cmRpt.c | 89 + cmRpt.h | 61 + cmSerialize.c | 1725 ++++++++++ cmSerialize.h | 311 ++ cmStack.c | 385 +++ cmStack.h | 63 + cmSymTbl.c | 390 +++ cmSymTbl.h | 68 + cmTagFile.c | 294 ++ cmTagFile.h | 63 + cmText.c | 763 +++++ cmText.h | 233 ++ cmThread.c | 1985 +++++++++++ cmThread.h | 278 ++ cmTime.c | 23 + cmTime.h | 46 + cmUdpNet.c | 730 ++++ cmUdpNet.h | 131 + cmUdpPort.c | 547 +++ cmUdpPort.h | 122 + cmVirtNet.c | 572 ++++ cmVirtNet.h | 73 + dsp/cmDspBuiltIn.c | 5004 +++++++++++++++++++++++++++ dsp/cmDspBuiltIn.h | 18 + dsp/cmDspClass.c | 1366 ++++++++ dsp/cmDspClass.h | 410 +++ dsp/cmDspCtx.h | 36 + dsp/cmDspFx.c | 6140 ++++++++++++++++++++++++++++++++++ dsp/cmDspFx.h | 50 + dsp/cmDspKr.c | 269 ++ dsp/cmDspKr.h | 16 + dsp/cmDspMod.c | 20 + dsp/cmDspNet.c | 894 +++++ dsp/cmDspNet.h | 121 + dsp/cmDspPgm.c | 2360 +++++++++++++ dsp/cmDspPgm.h | 29 + dsp/cmDspPgmPP.c | 577 ++++ dsp/cmDspPgmPP.h | 17 + dsp/cmDspPgmPPMain.c | 1768 ++++++++++ dsp/cmDspPgmPPMain.h | 56 + dsp/cmDspPreset.c | 979 ++++++ dsp/cmDspPreset.h | 81 + dsp/cmDspSys.c | 2229 ++++++++++++ dsp/cmDspSys.h | 276 ++ dsp/cmDspTl.c | 148 + dsp/cmDspTl.h | 16 + dsp/cmDspUi.c | 395 +++ dsp/cmDspUi.h | 19 + dsp/cmDspValue.c | 1511 +++++++++ dsp/cmDspValue.h | 336 ++ dsp/notes.txt | 10 + linux/cmAudioPortAlsa.c | 1635 +++++++++ linux/cmAudioPortAlsa.h | 47 + linux/cmFileSysLinux.c | 56 + linux/cmFileSysLinux.h | 33 + linux/cmMidiAlsa.c | 131 + osx/clock_gettime_stub.c | 144 + osx/clock_gettime_stub.h | 14 + osx/cmAudioPortOsx.c | 800 +++++ osx/cmAudioPortOsx.h | 30 + osx/cmFileSysOsx.c | 132 + osx/cmFileSysOsx.h | 16 + osx/cmMidiOsx.c | 821 +++++ vop/cmProcTemplate.c | 22 + vop/cmProcTemplate.h | 8 + vop/cmProcTemplateCode.h | 241 ++ vop/cmProcTemplateHdr.h | 79 + vop/cmProcTemplateMain.h | 75 + vop/cmProcTemplateUndef.h | 56 + vop/cmVectOps.c | 265 ++ vop/cmVectOps.h | 112 + vop/cmVectOpsDoc.h | 27 + vop/cmVectOpsRICode.h | 1208 +++++++ vop/cmVectOpsRIHdr.h | 199 ++ vop/cmVectOpsTemplateCode.h | 3204 ++++++++++++++++++ vop/cmVectOpsTemplateHdr.h | 601 ++++ vop/cmVectOpsTemplateMain.h | 109 + vop/cmVectOpsTemplateUndef.h | 38 + 192 files changed, 110710 insertions(+) create mode 100644 Makefile.am create mode 100644 app/cmOnset.c create mode 100644 app/cmOnset.h create mode 100644 app/cmPickup.c create mode 100644 app/cmPickup.h create mode 100644 app/cmScore.c create mode 100644 app/cmScore.h create mode 100644 app/cmTimeLine.c create mode 100644 app/cmTimeLine.h create mode 100644 cmApBuf.c create mode 100644 cmApBuf.h create mode 100644 cmAudDsp.c create mode 100644 cmAudDsp.h create mode 100644 cmAudDspIF.c create mode 100644 cmAudDspIF.h create mode 100644 cmAudDspLocal.c create mode 100644 cmAudDspLocal.h create mode 100644 cmAudLabelFile.c create mode 100644 cmAudLabelFile.h create mode 100644 cmAudioAggDev.c create mode 100644 cmAudioAggDev.h create mode 100644 cmAudioBuf.c create mode 100644 cmAudioBuf.h create mode 100644 cmAudioFile.c create mode 100644 cmAudioFile.h create mode 100644 cmAudioFileDev.c create mode 100644 cmAudioFileDev.h create mode 100644 cmAudioFileMgr.c create mode 100644 cmAudioFileMgr.h create mode 100644 cmAudioNrtDev.c create mode 100644 cmAudioNrtDev.h create mode 100644 cmAudioPort.c create mode 100644 cmAudioPort.h create mode 100644 cmAudioPortFile.c create mode 100644 cmAudioPortFile.h create mode 100644 cmAudioSys.c create mode 100644 cmAudioSys.h create mode 100644 cmComplexTypes.h create mode 100644 cmCsv.c create mode 100644 cmCsv.h create mode 100644 cmCtx.c create mode 100644 cmCtx.h create mode 100644 cmDocMain.h create mode 100644 cmErr.c create mode 100644 cmErr.h create mode 100644 cmFeatFile.c create mode 100644 cmFeatFile.h create mode 100644 cmFile.c create mode 100644 cmFile.h create mode 100644 cmFileSys.c create mode 100644 cmFileSys.h create mode 100644 cmFloatTypes.h create mode 100644 cmFrameFile.c create mode 100644 cmFrameFile.h create mode 100644 cmGlobal.c create mode 100644 cmGlobal.h create mode 100644 cmGnuPlot.c create mode 100644 cmGnuPlot.h create mode 100644 cmGr.c create mode 100644 cmGr.h create mode 100644 cmGrDevCtx.c create mode 100644 cmGrDevCtx.h create mode 100644 cmGrPage.c create mode 100644 cmGrPage.h create mode 100644 cmGrPlot.c create mode 100644 cmGrPlot.h create mode 100644 cmGrPlotAudio.c create mode 100644 cmGrPlotAudio.h create mode 100644 cmJson.c create mode 100644 cmJson.h create mode 100644 cmKeyboard.c create mode 100644 cmKeyboard.h create mode 100644 cmLex.c create mode 100644 cmLex.h create mode 100644 cmLib.c create mode 100644 cmLib.h create mode 100644 cmLinkedHeap.c create mode 100644 cmLinkedHeap.h create mode 100644 cmMain.c create mode 100644 cmMallocDebug.c create mode 100644 cmMallocDebug.h create mode 100644 cmMath.c create mode 100644 cmMath.h create mode 100644 cmMem.c create mode 100644 cmMem.h create mode 100644 cmMidi.c create mode 100644 cmMidi.h create mode 100644 cmMidiFile.c create mode 100644 cmMidiFile.h create mode 100644 cmMidiFilePlay.c create mode 100644 cmMidiFilePlay.h create mode 100644 cmMidiPort.c create mode 100644 cmMidiPort.h create mode 100644 cmMsgProtocol.c create mode 100644 cmMsgProtocol.h create mode 100644 cmOp.c create mode 100644 cmOp.h create mode 100644 cmPgmOpts.c create mode 100644 cmPgmOpts.h create mode 100644 cmPrefix.h create mode 100644 cmPrefs.c create mode 100644 cmPrefs.h create mode 100644 cmProc.c create mode 100644 cmProc.h create mode 100644 cmProc2.c create mode 100644 cmProc2.h create mode 100644 cmProc3.c create mode 100644 cmProc3.h create mode 100644 cmProcObj.c create mode 100644 cmProcObj.h create mode 100644 cmProcTest.c create mode 100644 cmProcTest.h create mode 100644 cmRbm.c create mode 100644 cmRbm.h create mode 100644 cmRpt.c create mode 100644 cmRpt.h create mode 100644 cmSerialize.c create mode 100644 cmSerialize.h create mode 100644 cmStack.c create mode 100644 cmStack.h create mode 100644 cmSymTbl.c create mode 100644 cmSymTbl.h create mode 100644 cmTagFile.c create mode 100644 cmTagFile.h create mode 100644 cmText.c create mode 100644 cmText.h create mode 100644 cmThread.c create mode 100644 cmThread.h create mode 100644 cmTime.c create mode 100644 cmTime.h create mode 100644 cmUdpNet.c create mode 100644 cmUdpNet.h create mode 100644 cmUdpPort.c create mode 100644 cmUdpPort.h create mode 100644 cmVirtNet.c create mode 100644 cmVirtNet.h create mode 100644 dsp/cmDspBuiltIn.c create mode 100644 dsp/cmDspBuiltIn.h create mode 100644 dsp/cmDspClass.c create mode 100644 dsp/cmDspClass.h create mode 100644 dsp/cmDspCtx.h create mode 100644 dsp/cmDspFx.c create mode 100644 dsp/cmDspFx.h create mode 100644 dsp/cmDspKr.c create mode 100644 dsp/cmDspKr.h create mode 100644 dsp/cmDspMod.c create mode 100644 dsp/cmDspNet.c create mode 100644 dsp/cmDspNet.h create mode 100644 dsp/cmDspPgm.c create mode 100644 dsp/cmDspPgm.h create mode 100644 dsp/cmDspPgmPP.c create mode 100644 dsp/cmDspPgmPP.h create mode 100644 dsp/cmDspPgmPPMain.c create mode 100644 dsp/cmDspPgmPPMain.h create mode 100644 dsp/cmDspPreset.c create mode 100644 dsp/cmDspPreset.h create mode 100644 dsp/cmDspSys.c create mode 100644 dsp/cmDspSys.h create mode 100644 dsp/cmDspTl.c create mode 100644 dsp/cmDspTl.h create mode 100644 dsp/cmDspUi.c create mode 100644 dsp/cmDspUi.h create mode 100644 dsp/cmDspValue.c create mode 100644 dsp/cmDspValue.h create mode 100644 dsp/notes.txt create mode 100644 linux/cmAudioPortAlsa.c create mode 100644 linux/cmAudioPortAlsa.h create mode 100644 linux/cmFileSysLinux.c create mode 100644 linux/cmFileSysLinux.h create mode 100644 linux/cmMidiAlsa.c create mode 100644 osx/clock_gettime_stub.c create mode 100644 osx/clock_gettime_stub.h create mode 100644 osx/cmAudioPortOsx.c create mode 100644 osx/cmAudioPortOsx.h create mode 100644 osx/cmFileSysOsx.c create mode 100644 osx/cmFileSysOsx.h create mode 100644 osx/cmMidiOsx.c create mode 100644 vop/cmProcTemplate.c create mode 100644 vop/cmProcTemplate.h create mode 100644 vop/cmProcTemplateCode.h create mode 100644 vop/cmProcTemplateHdr.h create mode 100644 vop/cmProcTemplateMain.h create mode 100644 vop/cmProcTemplateUndef.h create mode 100644 vop/cmVectOps.c create mode 100644 vop/cmVectOps.h create mode 100644 vop/cmVectOpsDoc.h create mode 100644 vop/cmVectOpsRICode.h create mode 100644 vop/cmVectOpsRIHdr.h create mode 100644 vop/cmVectOpsTemplateCode.h create mode 100644 vop/cmVectOpsTemplateHdr.h create mode 100644 vop/cmVectOpsTemplateMain.h create mode 100644 vop/cmVectOpsTemplateUndef.h 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: