diff --git a/app/cmSdb.c b/app/cmSdb.c index 6bf4346..617f96c 100644 --- a/app/cmSdb.c +++ b/app/cmSdb.c @@ -1,4 +1,5 @@ #include "cmGlobal.h" +#include "cmFloatTypes.h" #include "cmRpt.h" #include "cmErr.h" #include "cmCtx.h" @@ -9,6 +10,12 @@ #include "cmCsv.h" #include "cmSdb.h" #include "cmText.h" +#include "cmMath.h" +#include "cmTime.h" +#include "cmMidi.h" +#include "cmVectOpsTemplateMain.h" +#include "cmAudioFile.h" +#include "cmFileSys.h" typedef enum { @@ -31,6 +38,25 @@ typedef enum struct cmSdb_str; +typedef struct cmSdbSeqBlk_str +{ + cmSdbSeqEvent_t* eV; + unsigned cnt; + struct cmSdbSeqBlk_str* link; +} cmSdbSeqBlk_t; + +typedef struct cmSdbSeq_str +{ + struct cmSdb_str* p; + cmSdbSeqBlk_t* blocks; + cmSdbSeqBlk_t* ebp; + unsigned cnt; // total count of events in all blocks + unsigned chCnt; // max(chIdx)+1 of all events + double minDurSec; // min dur of all events + double maxDurSec; // max dur of all events + struct cmSdbSeq_str* link; +} cmSdbSeq_t; + typedef struct cmSdbRspBlk_str { unsigned* indexV; // indexV[ cmSdb_t.blkIdxAllocCnt ] @@ -49,17 +75,21 @@ typedef struct cmSdbRsp_str typedef struct cmSdb_str { - cmCtx_t ctx; - cmLHeapH_t lhH; - cmCsvH_t csvH; - cmSdbEvent_t* eV; - unsigned eN; - unsigned blkIdxAllocCnt; - struct cmSdbRsp_str* responses; + cmCtx_t ctx; + cmLHeapH_t lhH; + cmCsvH_t csvH; + cmSdbEvent_t* eV; + unsigned eN; + cmChar_t* audioDir; + unsigned blkIdxAllocCnt; + unsigned blkEvtAllocCnt; + cmSdbRsp_t* responses; + cmSdbSeq_t* seqs; } cmSdb_t; cmSdbH_t cmSdbNullHandle = cmSTATIC_NULL_HANDLE; cmSdbResponseH_t cmSdbResponseNullHandle = cmSTATIC_NULL_HANDLE; +cmSdbSeqH_t cmSdbSeqNullHandle = cmSTATIC_NULL_HANDLE; cmSdb_t* _cmSdbHandleToPtr( cmSdbH_t h ) { @@ -68,130 +98,8 @@ cmSdb_t* _cmSdbHandleToPtr( cmSdbH_t h ) return p; } -cmSdbRsp_t* _cmSdbRspHandleToPtr( cmSdbResponseH_t h ) -{ - cmSdbRsp_t* p = (cmSdbRsp_t*)h.h; - assert( p != NULL ); - return p; -} - -void _cmSdbRspBlkFree( cmSdb_t* p, cmSdbRspBlk_t* bp ) -{ - cmLhFree(p->lhH, bp->indexV); - cmLhFree(p->lhH, bp); -} - - -cmSdbRspBlk_t* _cmSdbRspBlkUnlink( cmSdbRsp_t* rp, cmSdbRspBlk_t* bp ) -{ - cmSdbRspBlk_t* dp = rp->blocks; - cmSdbRspBlk_t* pp = NULL; - for(; dp!=NULL; dp=dp->link) - { - if( dp == bp ) - { - if( pp == NULL ) - rp->blocks = dp->link; - else - pp->link = dp->link; - - return bp; - } - - pp = dp; - } - - assert(0); - return NULL; -} - -void _cmSdbRspInsertIndex( cmSdb_t* p, cmSdbRsp_t* rp, unsigned evtIndex ) -{ - - if( rp->ebp == NULL || rp->ebp->cnt == p->blkIdxAllocCnt ) - { - cmSdbRspBlk_t* bp = cmLhAllocZ(p->lhH,cmSdbRspBlk_t,1); - bp->indexV = cmLhAllocZ(p->lhH,unsigned,p->blkIdxAllocCnt); - - if( rp->ebp != NULL ) - rp->ebp->link = bp; - - if( rp->blocks == NULL ) - rp->blocks = bp; - - rp->ebp = bp; - - } - - assert( rp->ebp!=NULL && rp->ebp->cnt < p->blkIdxAllocCnt ); - - rp->ebp->indexV[ rp->ebp->cnt++ ] = evtIndex; - rp->cnt += 1; -} - -void _cmSdbRspRelease( cmSdbRsp_t* rp ) -{ - while( rp->blocks != NULL ) - { - cmSdbRspBlk_t* np = rp->blocks->link; - cmSdbRspBlk_t* bp; - - if((bp = _cmSdbRspBlkUnlink(rp,rp->blocks)) != NULL ) - _cmSdbRspBlkFree(rp->p,bp); - - rp->blocks = np; - } - - cmLhFree(rp->p->lhH,rp); -} - -cmSdbRsp_t* _cmSdbRspUnlink( cmSdbRsp_t* rp ) -{ - cmSdb_t* p = rp->p; - cmSdbRsp_t* dp = p->responses; - cmSdbRsp_t* pp = NULL; - - for(; dp!=NULL; dp=dp->link) - { - if( dp == rp ) - { - if( pp == NULL ) - p->responses = dp->link; - else - pp->link = dp->link; - - return rp; - } - - pp = dp; - } - - assert( 0 ); - - return NULL; -} - - -void _cmSdbRspFree( cmSdbRsp_t* rp ) -{ - _cmSdbRspUnlink(rp); - _cmSdbRspRelease(rp); -} - -cmSdbRsp_t* _cmSdbRspAlloc( cmSdb_t* p, cmSdbResponseH_t* rhp ) -{ - if( cmSdbResponseFree(rhp) != kOkSdbRC ) - return NULL; - - cmSdbRsp_t* rp = cmLhAllocZ(p->lhH,cmSdbRsp_t,1); - rp->p = p; - rp->link = p->responses; - p->responses = rp; - - rhp->h = rp; - - return rp; -} +void _cmSdbRspFree( cmSdbRsp_t* ); +cmSdbRC_t _cmSdbSeqFree( cmSdbSeq_t* ); cmSdbRC_t _cmSdbDestroy( cmSdb_t* p ) { @@ -201,14 +109,17 @@ cmSdbRC_t _cmSdbDestroy( cmSdb_t* p ) rc = cmErrMsg(&p->ctx.err,kCsvFailSdbRC,"CSV file finalize failed."); while( p->responses != NULL ) - _cmSdbRspRelease(p->responses); + _cmSdbRspFree(p->responses); + + while( p->seqs != NULL ) + _cmSdbSeqFree(p->seqs); cmLHeapDestroy(&p->lhH); cmMemFree(p); return rc; } -cmSdbRC_t cmSdbCreate( cmCtx_t* ctx, cmSdbH_t* hp, const cmChar_t* audioDir, const cmChar_t* csvFn ) +cmSdbRC_t cmSdbCreate( cmCtx_t* ctx, cmSdbH_t* hp, const cmChar_t* csvFn, const cmChar_t* audioDir ) { cmSdbRC_t rc; if((rc = cmSdbDestroy(hp)) != kOkSdbRC ) @@ -216,7 +127,8 @@ cmSdbRC_t cmSdbCreate( cmCtx_t* ctx, cmSdbH_t* hp, const cmChar_t* audioDir, co cmSdb_t* p = cmMemAllocZ(cmSdb_t,1); p->ctx = *ctx; - p->blkIdxAllocCnt = 1024; + p->blkIdxAllocCnt = 1024; + p->blkEvtAllocCnt = 1024; cmErrSetup(&p->ctx.err,&ctx->rpt,"sdb"); @@ -229,7 +141,7 @@ cmSdbRC_t cmSdbCreate( cmCtx_t* ctx, cmSdbH_t* hp, const cmChar_t* audioDir, co hp->h = p; if( csvFn != NULL ) - if((rc = cmSdbLoad(*hp,csvFn)) != kOkSdbRC ) + if((rc = cmSdbLoad(*hp,csvFn,audioDir)) != kOkSdbRC ) goto errLabel; errLabel: @@ -266,7 +178,7 @@ cmSdbRC_t _cmSdbSyntaxError(cmSdb_t* p, const cmChar_t* csvFn, unsigned rowIdx, } -cmSdbRC_t cmSdbLoad( cmSdbH_t h, const cmChar_t* csvFn ) +cmSdbRC_t cmSdbLoad( cmSdbH_t h, const cmChar_t* csvFn, const cmChar_t* audioDir ) { cmSdbRC_t rc = kOkSdbRC; unsigned i; @@ -285,7 +197,7 @@ cmSdbRC_t cmSdbLoad( cmSdbH_t h, const cmChar_t* csvFn ) cmLHeapClear(p->lhH,true); p->eV = cmLhAllocZ(p->lhH,cmSdbEvent_t,p->eN); - + for(i=0; rc==kOkSdbRC && ieN; ++i) { @@ -371,12 +283,213 @@ cmSdbRC_t cmSdbLoad( cmSdbH_t h, const cmChar_t* csvFn ) } + // store the audio directory + if( cmTextLength(audioDir) ) + p->audioDir = cmLhAllocStr(p->lhH,audioDir); + else + { + cmLhFree(p->lhH,&p->audioDir); + p->audioDir = NULL; + } errLabel: return rc; } + +cmSdbRC_t cmSdbSyncChPairs( cmSdbH_t h ) +{ + cmSdbRC_t rc = kOkSdbRC; + cmSdb_t* p = _cmSdbHandleToPtr(h); + unsigned i; + // for each multi-channel event + for(i=0; ieN; ++i) + if(p->eV[i].chCnt > 1 ) + { + const cmSdbEvent_t* ep = p->eV + i; + unsigned iV[ep->chCnt]; + unsigned j,k; + + // load iV[] with the event indexes of the channel pairs + for(j=0,k=0; jeN && kchCnt; ++j) + if( p->eV[j].baseUuid == ep->baseUuid ) + { + assert( p->eV[j].chIdx < ep->chCnt ); + + iV[p->eV[j].chIdx] = j; + ++k; + } + + if( k != ep->chCnt ) + rc = cmErrMsg(&p->ctx.err,kChPairNotFoundSdbRC,"The channel pair associated with 'id:%i instr:%s src:%s ch index:%i could not be found.",ep->uuid,cmStringNullGuard(ep->instr),cmStringNullGuard(ep->src),ep->chIdx); + else + { + unsigned mobi = ep->obi; + unsigned mibi = ep->ibi; + unsigned miei = ep->iei; + unsigned moei = ep->oei; + + // get the min onsets and max offsets + for(j=0; jchCnt; ++j) + { + mobi = cmMin(mobi,p->eV[ iV[j] ].obi); + mibi = cmMin(mibi,p->eV[ iV[j] ].ibi); + miei = cmMax(miei,p->eV[ iV[j] ].iei); + moei = cmMax(moei,p->eV[ iV[j] ].oei); + } + + // set the onsets to the min onset / offsets to max offsets + for(j=0; jchCnt; ++j) + { + p->eV[ iV[j] ].obi = mobi; + p->eV[ iV[j] ].ibi = mibi; + p->eV[ iV[j] ].iei = miei; + p->eV[ iV[j] ].oei = moei; + } + } + } + + return rc; +} + +const cmSdbEvent_t* _cmSdbEvent( cmSdb_t* p, unsigned uuid ) +{ + unsigned i; + for(i=0; ieN; ++i) + if( p->eV[i].uuid == uuid ) + return p->eV + i; + + return NULL; +} + +const cmSdbEvent_t* cmSdbEvent( cmSdbH_t h, unsigned uuid ) +{ + cmSdb_t* p = _cmSdbHandleToPtr(h); + return _cmSdbEvent(p,uuid); +} + +//================================================================================================================================ +cmSdbRsp_t* _cmSdbRspHandleToPtr( cmSdbResponseH_t h ) +{ + cmSdbRsp_t* p = (cmSdbRsp_t*)h.h; + assert( p != NULL ); + return p; +} + +void _cmSdbRspBlkFree( cmSdb_t* p, cmSdbRspBlk_t* bp ) +{ + cmLhFree(p->lhH, bp->indexV); + cmLhFree(p->lhH, bp); +} + + +cmSdbRspBlk_t* _cmSdbRspBlkUnlink( cmSdbRsp_t* rp, cmSdbRspBlk_t* bp ) +{ + cmSdbRspBlk_t* dp = rp->blocks; + cmSdbRspBlk_t* pp = NULL; + for(; dp!=NULL; dp=dp->link) + { + if( dp == bp ) + { + if( pp == NULL ) + rp->blocks = dp->link; + else + pp->link = dp->link; + + return bp; + } + + pp = dp; + } + + assert(0); + return NULL; +} + +void _cmSdbRspInsertIndex( cmSdb_t* p, cmSdbRsp_t* rp, unsigned evtIndex ) +{ + + if( rp->ebp == NULL || rp->ebp->cnt == p->blkIdxAllocCnt ) + { + cmSdbRspBlk_t* bp = cmLhAllocZ(p->lhH,cmSdbRspBlk_t,1); + bp->indexV = cmLhAllocZ(p->lhH,unsigned,p->blkIdxAllocCnt); + + if( rp->ebp != NULL ) + rp->ebp->link = bp; + + if( rp->blocks == NULL ) + rp->blocks = bp; + + rp->ebp = bp; + + } + + assert( rp->ebp!=NULL && rp->ebp->cnt < p->blkIdxAllocCnt ); + + rp->ebp->indexV[ rp->ebp->cnt++ ] = evtIndex; + rp->cnt += 1; +} + + +cmSdbRsp_t* _cmSdbRspUnlink( cmSdbRsp_t* rp ) +{ + cmSdb_t* p = rp->p; + cmSdbRsp_t* dp = p->responses; + cmSdbRsp_t* pp = NULL; + + for(; dp!=NULL; dp=dp->link) + { + if( dp == rp ) + { + if( pp == NULL ) + p->responses = dp->link; + else + pp->link = dp->link; + + return rp; + } + + pp = dp; + } + + assert( 0 ); + + return NULL; +} + + +void _cmSdbRspFree( cmSdbRsp_t* rp ) +{ + _cmSdbRspUnlink(rp); + + while( rp->blocks != NULL ) + { + cmSdbRspBlk_t* np = rp->blocks->link; + cmSdbRspBlk_t* bp; + + if((bp = _cmSdbRspBlkUnlink(rp,rp->blocks)) != NULL ) + _cmSdbRspBlkFree(rp->p,bp); + + rp->blocks = np; + } + + cmLhFree(rp->p->lhH,rp); + +} + +cmSdbRsp_t* _cmSdbRspAlloc( cmSdb_t* p ) +{ + cmSdbRsp_t* rp = cmLhAllocZ(p->lhH,cmSdbRsp_t,1); + rp->p = p; + rp->link = p->responses; + p->responses = rp; + + + return rp; +} + + // Compare 'label' to every string in tV[i] and return true if any comparision is a match. // If 'subFlV[i]' is set then 'label' must only contain tV[i] as a substring to match. // If 'negFlV[i]' is set then return true if any comparision is a mismatch. @@ -462,16 +575,20 @@ cmSdbRC_t cmSdbSelect( const cmChar_t** instrV, const cmChar_t** srcV, const cmChar_t** notesV, + const unsigned* pitchV, double minDurSec, double maxDurSec, unsigned minChCnt, cmSdbResponseH_t* rhp ) { - cmSdbRC_t rc = kOkSdbRC; - cmSdb_t* p = _cmSdbHandleToPtr(h); - unsigned i; + cmSdbRC_t rc; - cmSdbRsp_t* rp = _cmSdbRspAlloc(p,rhp); + if((rc = cmSdbResponseFree(rhp)) != kOkSdbRC ) + return rc; + + cmSdb_t* p = _cmSdbHandleToPtr(h); + cmSdbRsp_t* rp = _cmSdbRspAlloc(p); + unsigned i; // get the length of each string vector unsigned srcN = _cmSdbStrVectCnt(srcV); @@ -512,26 +629,45 @@ cmSdbRC_t cmSdbSelect( if( !_cmSdbSelectText(r,instrV,insSubFlV,insNegFlV,r->instr) ) continue; + if( pitchV != NULL ) + { + for(j=0; pitchV[j]!=kInvalidMidiPitch; ++j) + if( pitchV[j] == r->midi ) + break; + + if( pitchV[j] != r->midi ) + continue; + } + if( r->notesV != NULL ) + { for(j=0; r->notesV[j]!=NULL; ++j) if( _cmSdbSelectText(r,notesV,notSubFlV,notNegFlV,r->notesV[j]) == true ) break; - if( r->notesV[j]==NULL ) - continue; + if( r->notesV[j]==NULL ) + continue; + } _cmSdbRspInsertIndex(p,rp,i); } + rhp->h = rp; + + if(rc != kOkSdbRC ) + _cmSdbRspFree(rp); + return rc; } -cmSdbRC_t cmSdbSelectChPairs( cmSdbH_t h, const cmSdbEvent_t* ep, cmSdbResponseH_t* rhp ) +cmSdbRC_t _cmSdbSelectChPairs( cmSdb_t* p, const cmSdbEvent_t* ep, cmSdbResponseH_t* rhp ) { - cmSdbRC_t rc = kOkSdbRC; - cmSdb_t* p = _cmSdbHandleToPtr(h); - cmSdbRsp_t* rp = _cmSdbRspAlloc(p,rhp); + cmSdbRC_t rc; + if((rc = cmSdbResponseFree(rhp)) != kOkSdbRC ) + return rc; + + cmSdbRsp_t* rp = _cmSdbRspAlloc(p); unsigned i; // for each channel of this event @@ -559,10 +695,16 @@ cmSdbRC_t cmSdbSelectChPairs( cmSdbH_t h, const cmSdbEvent_t* ep, cmSdbResponseH } } - return rc; + rhp->h = rp; + return rc; } +cmSdbRC_t cmSdbSelectChPairs( cmSdbH_t h, const cmSdbEvent_t* ep, cmSdbResponseH_t* rhp ) +{ + cmSdb_t* p = _cmSdbHandleToPtr(h); + return _cmSdbSelectChPairs( p, ep,rhp ); +} unsigned cmSdbResponseCount( cmSdbResponseH_t rh ) { @@ -620,61 +762,683 @@ void cmSdbResponsePrint( cmSdbResponseH_t rh, cmRpt_t* rpt ) } } -cmSdbRC_t cmSdbSyncChPairs( cmSdbH_t h ) +//================================================================================================================================ +cmSdbSeq_t* _cmSdbSeqHandleToPtr( cmSdbSeqH_t sh ) +{ + cmSdbSeq_t* sp = (cmSdbSeq_t*)sh.h; + assert(sp !=NULL ); + return sp; +} + +void _cmSdbSeqInsertEvent( cmSdbSeq_t* sp, unsigned uuid, unsigned chIdx, double begSecs, double durSecs ) +{ + cmSdb_t* p = sp->p; + + // if no block has been allocated or the current block is full + if( sp->ebp == NULL || sp->ebp->cnt >= p->blkEvtAllocCnt ) + { + // allocate a new seq block recd + cmSdbSeqBlk_t* bp = cmLhAllocZ(sp->p->lhH,cmSdbSeqBlk_t,1); + + // allocate a seq evt array + bp->eV = cmLhAllocZ(sp->p->lhH,cmSdbSeqEvent_t,p->blkEvtAllocCnt); + + // link in the block recd + if( sp->ebp != NULL ) + sp->ebp->link = bp; + + if( sp->blocks == NULL ) + { + sp->blocks = bp; + sp->minDurSec = durSecs; + sp->maxDurSec = durSecs; + } + + sp->ebp = bp; + } + + assert( sp->ebp != NULL && sp->ebp->cnt < p->blkEvtAllocCnt ); + + // get the next seq evt recd to fill + cmSdbSeqEvent_t* ep = sp->ebp->eV + sp->ebp->cnt; + + // fill the seq evt recd + ep->uuid = uuid; + ep->begSec = begSecs; + ep->durSec = durSecs; + ep->outChIdx = chIdx; + ep->gain = 1.0; + + // incr the seq evt cnt + sp->ebp->cnt += 1; + sp->cnt += 1; + sp->chCnt = cmMax(sp->chCnt,chIdx+1); + sp->minDurSec = cmMin(sp->minDurSec,durSecs); + sp->maxDurSec = cmMax(sp->maxDurSec,durSecs); +} + + +// unlink a sequence record from p->seqs. +cmSdbSeq_t* _cmSdbSeqUnlink( cmSdbSeq_t* sp ) +{ + cmSdb_t* p = sp->p; + cmSdbSeq_t* cp = p->seqs; + cmSdbSeq_t* pp = NULL; + + for(; cp!=NULL; cp=cp->link) + { + if( cp == sp ) + { + if( pp == NULL ) + p->seqs = sp->link; + else + pp->link = sp->link; + + return sp; + } + + pp = cp; + } + + assert(0); + return NULL; +} + +// free a sequence record +cmSdbRC_t _cmSdbSeqFree( cmSdbSeq_t* sp ) +{ + cmSdb_t* p = sp->p; + + // unlink this seq. record from p->seqs + if( _cmSdbSeqUnlink(sp) == NULL ) + return cmErrMsg(&p->ctx.err,kAssertFailSdbRC,"Sequence unlink failed."); + + // release the seq blocks held by the sequence + while( sp->blocks != NULL ) + { + cmSdbSeqBlk_t* np = sp->blocks->link; + + cmLhFree(p->lhH,sp->blocks->eV); + cmLhFree(p->lhH,sp->blocks); + + sp->blocks = np; + } + + + cmLhFree(p->lhH,sp); + + return kOkSdbRC; +} + +// allocate a sequence record +cmSdbSeq_t* _cmSdbSeqAlloc( cmSdb_t* p ) +{ + cmSdbSeq_t* sp = cmLhAllocZ(p->lhH,cmSdbSeq_t,1); + + sp->p = p; + sp->link = p->seqs; + p->seqs = sp; + + return sp; +} + +cmSdbRC_t _cmSdbStoreSeqEvent( + cmSdb_t* p, + cmSdbSeq_t* sp, + cmSdbResponseH_t rh, + unsigned ri, + unsigned seqChCnt, + double begSecs, + double limitEvtDurSecs, + double* durSecsRef ) { cmSdbRC_t rc = kOkSdbRC; - cmSdb_t* p = _cmSdbHandleToPtr(h); - unsigned i; - // for each multi-channel event - for(i=0; ieN; ++i) - if(p->eV[i].chCnt > 1 ) + + // retrieve the event record + const cmSdbEvent_t* ep; + if((ep = cmSdbResponseEvent(rh,ri)) == NULL ) + { + rc = cmErrMsg(&p->ctx.err,kRspEvtNotFoundSdbRC,"A response event could not be found during random sequence generation."); + goto errLabel; + } + + cmSdbResponseH_t rh0 = cmSdbResponseNullHandle; + unsigned rn0 = 0; + unsigned ci = 0; + double maxEvtDurSecs = 0; + + // locate the channel pairs for 'ep'. + if( seqChCnt>1 && ep->chCnt>1 ) + { + if( _cmSdbSelectChPairs(p, ep, &rh0 ) != kOkSdbRC ) { - const cmSdbEvent_t* ep = p->eV + i; - unsigned iV[ep->chCnt]; - unsigned j,k; - - // load iV[] with the event indexes of the channel pairs - for(j=0,k=0; jeN && kchCnt; ++j) - if( p->eV[j].baseUuid == ep->baseUuid ) - { - assert( p->eV[j].chIdx < ep->chCnt ); - - iV[p->eV[j].chIdx] = j; - ++k; - } - - if( k != ep->chCnt ) - rc = cmErrMsg(&p->ctx.err,kChPairNotFoundSdbRC,"The channel pair associated with 'id:%i instr:%s src:%s ch index:%i could not be found.",ep->uuid,cmStringNullGuard(ep->instr),cmStringNullGuard(ep->src),ep->chIdx); - else - { - unsigned mobi = ep->obi; - unsigned mibi = ep->ibi; - unsigned miei = ep->iei; - unsigned moei = ep->oei; - - // get the min onsets and max offsets - for(j=0; jchCnt; ++j) - { - mobi = cmMin(mobi,p->eV[ iV[j] ].obi); - mibi = cmMin(mibi,p->eV[ iV[j] ].ibi); - miei = cmMax(miei,p->eV[ iV[j] ].iei); - moei = cmMax(moei,p->eV[ iV[j] ].oei); - } - - // set the onsets to the min onset / offsets to max offsets - for(j=0; jchCnt; ++j) - { - p->eV[ iV[j] ].obi = mobi; - p->eV[ iV[j] ].ibi = mibi; - p->eV[ iV[j] ].iei = miei; - p->eV[ iV[j] ].oei = moei; - } - } + rc = cmErrMsg(&p->ctx.err,kChPairNotFoundSdbRC,"A response event could not find channel pairs during random sequence generation."); + goto errLabel; } - + + rn0 = cmSdbResponseCount(rh0); + } + + while(1) + { + + // calculate the event duration + double durSecs = (double)(ep->oei - ep->obi)/ep->srate; + + // truncate the event if it is longer than limitEvtDurSecs + if( limitEvtDurSecs!=0 && durSecs>limitEvtDurSecs ) + durSecs = cmMin(limitEvtDurSecs,durSecs); + + // track the longest event + maxEvtDurSecs = cmMax(maxEvtDurSecs,durSecs); + + // store the sequence event + _cmSdbSeqInsertEvent(sp,ep->uuid,ci,begSecs,durSecs); + + // incr the output ch index + ++ci; + + // if all the out ch's are filled or the sample event has no more channels + if( ci >= seqChCnt || ci-1 >= rn0 ) + break; + + // get the next channel pair + if((ep = cmSdbResponseEvent(rh0,ci-1)) == NULL ) + { + rc = cmErrMsg(&p->ctx.err,kRspEvtNotFoundSdbRC,"A channel pair response event could not be found during random sequence generation."); + goto errLabel; + } + + } // for each sample event pair + + errLabel: + + if( durSecsRef != NULL ) + *durSecsRef = maxEvtDurSecs; + + cmSdbResponseFree(&rh0); + + return rc; + +} + +cmSdbRC_t cmSdbSeqRand( + cmSdbResponseH_t rh, + unsigned seqDurSecs, + unsigned seqChCnt, + unsigned minEvtPerSec, + unsigned maxEvtPerSec, + cmSdbSeqH_t* shp ) +{ + cmSdbRC_t rc; + + if((rc = cmSdbSeqFree(shp)) != kOkSdbRC ) + return rc; + + cmSdbRsp_t* rp = _cmSdbRspHandleToPtr(rh); + cmSdb_t* p = rp->p; + cmSdbSeq_t* sp = _cmSdbSeqAlloc(p); + + if( seqChCnt < 1 ) + return cmErrMsg(&p->ctx.err,kInvalidArgSdbRC,"The random sequence generator channel count parameter must be non-zero."); + + if( seqDurSecs <= 0 ) + return cmErrMsg(&p->ctx.err,kInvalidArgSdbRC,"The random sequence generator signal duration must be greater than 0."); + + if( maxEvtPerSec < minEvtPerSec ) + return cmErrMsg(&p->ctx.err,kInvalidArgSdbRC,"The random sequence generator max. events per second must be greater or equal to the min. events per second."); + + if((rc = cmSdbSeqFree(shp)) != kOkSdbRC ) + return rc; + + unsigned rn = cmSdbResponseCount(rh); + unsigned sec; + for(sec=0; sech = sp; + + errLabel: + if( rc != kOkSdbRC ) + _cmSdbSeqFree(sp); + return rc; } +cmSdbRC_t cmSdbSeqSerial( + cmSdbResponseH_t rh, + unsigned seqChCnt, + double gapSec, + double maxEvtDurSec, + cmSdbSeqH_t* shp ) +{ + cmSdbRC_t rc; + + if((rc = cmSdbSeqFree(shp)) != kOkSdbRC ) + return rc; + + cmSdbRsp_t* rp = _cmSdbRspHandleToPtr(rh); + cmSdb_t* p = rp->p; + cmSdbSeq_t* sp = _cmSdbSeqAlloc(p); + unsigned n = cmSdbResponseCount(rh); + double begSecs = 0; + unsigned ri; + + for(ri=0; rih = sp; + + errLabel: + if(rc != kOkSdbRC ) + _cmSdbSeqFree(sp); + + return rc; +} + +cmSdbRC_t cmSdbSeqChord( + cmSdbResponseH_t* rhp, + unsigned rn, + unsigned seqChCnt, + unsigned maxEvtDurSec, + cmSdbSeqH_t* shp ) +{ + cmSdbRC_t rc = kOkSdbRC; + + assert( shp != NULL ); + + if( rn == 0 ) + return rc; + + cmSdbResponseH_t rh = rhp[0]; + cmSdbRsp_t* rp = _cmSdbRspHandleToPtr(rh); + cmSdb_t* p = rp->p; + cmSdbSeq_t* sp = _cmSdbSeqAlloc(p); + unsigned i; + + if((rc = cmSdbSeqFree(shp)) != kOkSdbRC ) + return rc; + + // for each chord note + for(i=0; ip != p ) + { + rc = cmErrMsg(&p->ctx.err,kAssertFailSdbRC,"All chord query response handle must be derived from the same cmSdbH_t handle."); + goto errLabel; + } + + // pick one event at random from the response + unsigned n = cmSdbResponseCount(rh); + unsigned rei = cmRandUInt(0,n-1); + + // all notes start at time: 0.0. + double begSecs = 0.0; + + // store the sequence event + if((rc = _cmSdbStoreSeqEvent(p,sp,rh,rei,seqChCnt,begSecs,maxEvtDurSec,NULL)) != kOkSdbRC ) + goto errLabel; + + } + + shp->h = sp; + + errLabel: + if(rc != kOkSdbRC ) + _cmSdbSeqFree(sp); + + return rc; +} + + + +bool cmSdbSeqIsValid( cmSdbSeqH_t sh ) +{ return sh.h != NULL; } + +cmSdbRC_t cmSdbSeqFree( cmSdbSeqH_t* shp ) +{ + cmSdbRC_t rc = kOkSdbRC; + if( shp==NULL || cmSdbSeqIsValid(*shp)==false ) + return rc; + + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(*shp); + + if((rc = _cmSdbSeqFree(sp)) != kOkSdbRC ) + return rc; + + shp->h = NULL; + + return rc; +} + +unsigned cmSdbSeqCount( cmSdbSeqH_t sh ) +{ + if( cmSdbSeqIsValid(sh)==false ) + return 0; + + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + return sp->cnt; +} + +const cmSdbSeqEvent_t* cmSdbSeqEvent( cmSdbSeqH_t sh, unsigned index ) +{ + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + if( index >= sp->cnt ) + return NULL; + + cmSdbSeqBlk_t* bp = sp->blocks; + unsigned i; + for(i=0; bp!=NULL; i+=bp->cnt,bp=bp->link) + if( i <= index && index < (i + bp->cnt) ) + return bp->eV + index-i; + + cmErrMsg(&sp->p->ctx.err,kInvalidSeqIdxSdbRC,"Invalid sequence event index=%i.",index); + + return NULL; +} + +const cmSdbEvent_t* cmSdbSeqSdbEvent( cmSdbSeqH_t sh, unsigned index ) +{ + const cmSdbSeqEvent_t* ep; + if((ep = cmSdbSeqEvent(sh,index)) == NULL ) + return NULL; + + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + + return _cmSdbEvent(sp->p,ep->uuid); +} + +double cmSdbSeqDurSeconds( cmSdbSeqH_t sh ) +{ + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + + cmSdbSeqBlk_t* bp = sp->blocks; + while( bp!=NULL && bp->link!=NULL ) + bp=bp->link; + + if( bp == NULL ) + return 0; + + cmSdbSeqEvent_t* ep = bp->eV + bp->cnt - 1; + + return ep->begSec + ep->durSec; +} + +double cmSdbSeqSampleRate( cmSdbSeqH_t sh ) +{ + unsigned n = cmSdbSeqCount(sh); + unsigned i; + const cmSdbEvent_t* ep; + + for(i=0; israte != 0 ) + return ep->srate; + + return 0; +} + +cmSdbRC_t cmSdbSeqToAudio( + cmSdbSeqH_t sh, + unsigned decayMs, + double noiseDb, + double normFact, + cmSample_t** signalRef, + unsigned* sigSmpCntRef ) +{ + assert( signalRef!=NULL && sigSmpCntRef!=NULL); + + *signalRef = NULL; + *sigSmpCntRef = 0; + + cmSdbRC_t rc = kOkSdbRC; + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + cmSdb_t* p = sp->p; + unsigned qN = cmSdbSeqCount(sh); + double durSecs = cmSdbSeqDurSeconds(sh); + double srate = cmSdbSeqSampleRate(sh); + + assert(sp->maxDurSec>=sp->minDurSec); + + // verify that sequence events exist + if( qN==0 || durSecs==0 || sp->chCnt==0 || sp->maxDurSec==0) + return rc; + + // validate the sample rate + if( srate == 0 ) + return cmErrMsg(&p->ctx.err,kAssertFailSdbRC,"The sample rate of the sequence could not be determined."); + + unsigned sN = (unsigned)floor(srate * (durSecs + 0.25)); // output signal sample count + 1/4 second of silence + unsigned dN = (unsigned)floor(srate * decayMs / 1000.0); // decay env. sample count + unsigned tN = (unsigned)floor(srate * sp->maxDurSec); // length of longest audio event in samples + cmSample_t* s = cmMemAllocZ(cmSample_t,sN*sp->chCnt); // allocate the outputsignal buffer + cmSample_t* t = cmMemAllocZ(cmSample_t,tN*sp->chCnt); // audio event read buffer + cmSample_t* d = NULL; + cmSample_t* chBuf[ sp->chCnt ]; + unsigned i; + + // fill the channel buffers + for(i=0; ichCnt; ++i) + chBuf[i] = t + (i*tN); + + // if a decay rate was specified + if( dN > 0 ) + { + d = cmMemAllocZ(cmSample_t,dN); // allocate the decay env. buffer + cmVOS_LinSpace(d,dN,1.0,0.0); // calc. a decay envelope + cmVOS_PowVS(d,dN,4.0); + } + + // if a noise floor was specified + if( noiseDb != 0 ) + { + // fill the signal with low level white noise + cmVOS_Random(s,sN,-1.0,1.0); + cmVOS_MultVS(s,sN,pow(10.0,-fabs(noiseDb)/20.0)); + } + + // for each sequence event + for(i=0; rc==kOkSdbRC && ictx.err,kAssertFailSdbRC,"Unable to retrieve the sequence event at index %i.",i); + goto errLabel; + } + + // get the audio event record + if((ep = _cmSdbEvent(p,qep->uuid)) == NULL) + { + rc = cmErrMsg(&p->ctx.err,kAssertFailSdbRC,"Unable to retrieve the sample event with uuid:%i.",qep->uuid); + goto errLabel; + } + + unsigned begFrmIdx = floor(srate * qep->begSec ); // dest. index into output signal + unsigned frmCnt = floor(srate * qep->durSec ); // seq. event dur in samples + const cmChar_t* afn = NULL; // audio event file name + unsigned actFrmCnt = 0; // actual count of samples read from the audio event file + cmAudioFileInfo_t afInfo; // audio file info. record + + // form the audio event file name + if((afn = cmFsMakeFn(p->audioDir,ep->afn,NULL,NULL))==NULL) + { + rc = cmErrMsg(&p->ctx.err,kFileSysFailSdbRC,"Unable to form the file name for %s/%s.",cmStringNullGuard(p->audioDir),cmStringNullGuard(ep->afn)); + goto errLabel; + } + + assert(ep->oei-ep->obi>0 ); + + // read the audio event from the file into t[] + if( cmAudioFileGetSample(afn, ep->obi, cmMin(tN,cmMin(frmCnt,ep->oei-ep->obi)), 0, ep->chCnt, chBuf, &actFrmCnt, &afInfo, p->ctx.err.rpt ) != kOkAfRC ) + { + rc = cmErrMsg(&p->ctx.err,kFileSysFailSdbRC,"Audio event read failed for event uuid:%i in '%s'.",qep->uuid,cmStringNullGuard(afn)); + goto doneLabel; + } + + // 'actFrmCnt' now holds the length of the event signal + + // verify that the audio event sample rate matches the sequence srate + if( afInfo.srate != srate ) + cmErrWarnMsg(&p->ctx.err,kAssertFailSdbRC,"The sample rate (%f) of audio event uuid:%i in '%s' does not match the sequence sample rate:%f.",afInfo.srate,qep->uuid,cmStringNullGuard(afn),srate); + + // if a decay rate was specified + if( dN > 0 ) + { + unsigned ti = 0; // start of decay in t[] + unsigned di = 0; // start of decay in d[] + + if( actFrmCnt > dN ) + ti = actFrmCnt - dN; // decay func is applied to end of audio event + else + di = dN - actFrmCnt; // decay func is longer than audio event (shorten it) + + unsigned mn = dN - di; // decay function length + unsigned j; + + // apply the decay function + for(j=0; jchCnt; ++j) + cmVOS_MultVV(t + (j*tN) +ti , mn, d+di); + } + + // normalize the event signal + if( normFact != 0 ) + cmVOS_NormToAbsMax(t,actFrmCnt,normFact); + + + // verify the the signal event falls inside the output signal + if( begFrmIdx >= sN ) + rc = cmErrMsg(&p->ctx.err,kAssertFailSdbRC,"A sequence event start time falls after the end of the sequence signal. This should never happen."); + else + { + // if the event signal goes past the end of the signal - truncate the event + if( begFrmIdx + actFrmCnt > sN ) + actFrmCnt = sN - begFrmIdx; + + // sum the event signal into the output signal + cmVOS_AddVV(s + (qep->outChIdx*sN) + begFrmIdx,actFrmCnt,t); + } + + doneLabel: + cmFsFreeFn(afn); + + } + + *signalRef = s; + *sigSmpCntRef = sN; + errLabel: + if( rc != kOkSdbRC ) + cmMemFree(s); + + cmMemFree(d); + cmMemFree(t); + return rc; +} + +cmSdbRC_t cmSdbSeqToAudioFn( + cmSdbSeqH_t sh, + unsigned decayMs, + double noiseDb, + double evtNormFact, + double sigNormFact, + const cmChar_t* fn, + unsigned bitsPerSample + ) +{ + cmSdbRC_t rc = kOkSdbRC; + cmSample_t* s = NULL; + unsigned sN = 0; + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + cmSdb_t* p = sp->p; + double srate = cmSdbSeqSampleRate(sh); + unsigned i; + + // fill s[sN] with the sequence audio signal + if((rc = cmSdbSeqToAudio(sh,decayMs,noiseDb,evtNormFact,&s,&sN)) != kOkSdbRC ) + return rc; + + // if no audio signal was created there is nothing to do + if( sN == 0 ) + return rc; + + // the sample rate was already check by cmSdbSeqToAudio(). + assert(srate != 0 && s != NULL); + + // if requested normalize the signal + if( sigNormFact != 0 ) + cmVOS_NormToAbsMax(s,sN*sp->chCnt,sigNormFact); + + // fill the channel buffer + cmSample_t* chBuf[ sp->chCnt ]; + for(i=0; ichCnt; ++i) + chBuf[i] = s + (i*sN); + + // write the signal to an audio file + if((rc = cmAudioFileWriteFileFloat(fn, srate, bitsPerSample, sN, sp->chCnt, chBuf, p->ctx.err.rpt )) != kOkAfRC ) + { + rc = cmErrMsg(&p->ctx.err,kAudioFileFailSdbRC,"The sequence audio file '%s' could not be created.",cmStringNullGuard(fn)); + goto errLabel; + } + + errLabel: + cmMemFree(s); + return rc; +} + + +void cmSdbSeqPrint( cmSdbSeqH_t sh, cmRpt_t* rpt ) +{ + unsigned i; + unsigned n = cmSdbSeqCount(sh); + cmSdbSeq_t* sp = _cmSdbSeqHandleToPtr(sh); + const cmSdbSeqEvent_t* ep; + + cmRptPrintf(rpt,"evt cnt:%i ch cnt:%i dur min:%f max:%f \n",sp->cnt,sp->chCnt,sp->minDurSec,sp->maxDurSec); + cmRptPrintf(rpt," uuid ch beg dur gain \n"); + cmRptPrintf(rpt,"------- --- ------- ------- -------\n"); + for(i=0; iuuid,ep->outChIdx,ep->begSec,ep->durSec,ep->gain ); + +} + + cmSdbRC_t cmSdbTest( cmCtx_t* ctx ) { @@ -686,7 +1450,7 @@ cmSdbRC_t cmSdbTest( cmCtx_t* ctx ) cmErrSetup(&err,&ctx->rpt,"sdb test"); - if((rc = cmSdbCreate(ctx, &h, audioDir, csvFn )) != kOkSdbRC ) + if((rc = cmSdbCreate(ctx, &h, csvFn, audioDir )) != kOkSdbRC ) { rc = cmErrMsg(&err,rc,"sdb create failed."); goto errLabel; @@ -698,21 +1462,85 @@ cmSdbRC_t cmSdbTest( cmCtx_t* ctx ) goto errLabel; } - if(0) + if(1) { cmSdbResponseH_t rH = cmSdbResponseNullHandle; + cmSdbSeqH_t sH = cmSdbSeqNullHandle; - const cmChar_t* instrV[] = { "*viol", NULL }; + const cmChar_t* instrV[] = { "violin", NULL }; + const cmChar_t* srcV[] = { "ui", NULL }; + const cmChar_t* notesV[] = { "!vibrato", NULL }; - if((rc = cmSdbSelect(h,0,instrV,NULL,NULL,0,0,0,&rH)) != kOkSdbRC ) + if((rc = cmSdbSelect(h,0,instrV,srcV,notesV,NULL,0,0,0,&rH)) != kOkSdbRC ) { rc = cmErrMsg(&err,rc,"sdb query failed."); goto errLabel; } + //cmSdbResponsePrint(rH,&ctx->rpt); - cmSdbResponsePrint(rH,&ctx->rpt); + unsigned seqDurSecs = 15; + unsigned seqChCnt = 2; + unsigned sel = 2; + switch( sel ) + { + case 0: + { + unsigned minEvtPerSec = 1; + unsigned maxEvtPerSec = 5; + if((rc = cmSdbSeqRand(rH,seqDurSecs,seqChCnt,minEvtPerSec,maxEvtPerSec,&sH)) != kOkSdbRC ) + { + rc = cmErrMsg(&err,rc,"sdb random sequence generation failed."); + goto errLabel; + } + } + break; + + case 1: + { + double gapSec = 0.1; + double maxEvtDurSec = 1.0; + + if((rc = cmSdbSeqSerial(rH,seqChCnt,gapSec,maxEvtDurSec,&sH)) != kOkSdbRC ) + { + rc = cmErrMsg(&err,rc,"sdb serial sequence generation failed."); + goto errLabel; + } + } + break; + + case 2: + { + cmSdbResponseH_t rhV[] = { rH, rH, rH }; + unsigned rN = sizeof(rhV)/sizeof(rhV[0]); + double maxEvtDurSec = 1.0; + + if((rc = cmSdbSeqChord(rhV,rN,seqChCnt,maxEvtDurSec,&sH)) != kOkSdbRC ) + { + rc = cmErrMsg(&err,rc,"sdb chord sequence generation failed."); + goto errLabel; + } + } + break; + } + + cmSdbSeqPrint(sH,&ctx->rpt); + + const cmChar_t* afn = "/home/kevin/temp/aaa.aif"; + unsigned decayMs = 50; + double noiseDb = -70.0; + double evtNormFact = 0; //0.7; + double sigNormFact = 0.7; //0.7; + unsigned bitsPerSample = 16; + + if((rc = cmSdbSeqToAudioFn(sH,decayMs,noiseDb,evtNormFact,sigNormFact,afn,bitsPerSample)) != kOkSdbRC ) + { + rc = cmErrMsg(&err,rc,"sdb sequence audio file generation failed."); + goto errLabel; + } + + cmSdbSeqFree(&sH); cmSdbResponseFree(&rH); } diff --git a/app/cmSdb.h b/app/cmSdb.h index 4a8b30f..3b4e521 100644 --- a/app/cmSdb.h +++ b/app/cmSdb.h @@ -6,7 +6,7 @@ extern "C" { #endif /* - The data for this object is stored in a CSV file with the following column syntax. + The CSV file used to initialize a SDB object has the following column syntax. Column Name Type Description ------ -------- ----- ----------------------------------------------------- @@ -46,17 +46,26 @@ extern "C" { kOkSdbRC, kLHeapFailSdbRC, kCsvFailSdbRC, + kFileSysFailSdbRC, + kAudioFileFailSdbRC, kSyntaxErrSdbRC, kInvalidRspIdxSdbRC, - kChPairNotFoundSdbRC + kInvalidSeqIdxSdbRC, + kChPairNotFoundSdbRC, + kRspEvtNotFoundSdbRC, + kAssertFailSdbRC, + kInvalidArgSdbRC }; typedef cmHandle_t cmSdbH_t; typedef cmHandle_t cmSdbResponseH_t; + typedef cmHandle_t cmSdbSeqH_t; typedef cmRC_t cmSdbRC_t; - extern cmSdbH_t cmSdbNullHandle_t; - extern cmSdbResponseH_t cmSdbResponseNullHandle_t; + + extern cmSdbH_t cmSdbNullHandle; + extern cmSdbResponseH_t cmSdbResponseNullHandle; + extern cmSdbSeqH_t cmSdbSeqNullHandle; typedef struct { @@ -77,12 +86,29 @@ extern "C" { } cmSdbEvent_t; - cmSdbRC_t cmSdbCreate( cmCtx_t* ctx, cmSdbH_t* hp, const cmChar_t* audioDir, const cmChar_t* csvFn ); + // Create an SDB object. If 'csvFn' is non-NULL then an internal call is made to cmSdbLoad(). + cmSdbRC_t cmSdbCreate( cmCtx_t* ctx, cmSdbH_t* hp, const cmChar_t* csvFn, const cmChar_t* audioDir ); + + // Release an SDB object previously created via cmSdbCreate(). cmSdbRC_t cmSdbDestroy( cmSdbH_t* hp ); bool cmSdbIsValid( cmSdbH_t h ); - cmSdbRC_t cmSdbLoad( cmSdbH_t h, const cmChar_t* csvFn ); + // Iinitialze the internal database from the CSV file 'csvFn'. + cmSdbRC_t cmSdbLoad( cmSdbH_t h, const cmChar_t* csvFn, const cmChar_t* audioDir ); + + // Time align all channel pairs by setting the onset times to + // the minimum time among all the pairs and the offset times to + // the maximum among all the pairs. This function is applied to all + // the events contained in the sample database. + cmSdbRC_t cmSdbSyncChPairs( cmSdbH_t h ); + + // Given a sample event unique id return a pointer to the associated record. + const cmSdbEvent_t* cmSdbEvent( cmSdbH_t h, unsigned uuid ); + + //================================================================================================ + // Query Related functions + // // Select a set of events from the sample database. // @@ -122,15 +148,23 @@ extern "C" { // which case a match will be triggered by fields where // the key text is not a substring of the field text. // - // All query response handles returned from this function - // should eventualy be released by the application via a call to - // cmSdbResponseFree(). + // pitchV[] contains an array of pitch values to match. + // The last value in pitchV[] must be kInvalidMidiPitch. + // If pitchV == NULL then all pitches match. Note that + // to match non-pitched instruments set set one element + // of pitchV[] to -1. + // + // The application should release all query response handles + // returned from this function via a call to cmSdbResponseFree(). + // cmSdbDestroy() will automatically release any response + // handles not previously release by cmSdbReponseFree(). cmSdbRC_t cmSdbSelect( cmSdbH_t h, double srate, // event sample rate or 0 to ignore const cmChar_t** instrV, // array of instrument labels to match const cmChar_t** srcV, // array of 'src' labels to match const cmChar_t** notesV, // array of text 'notes' to match + const unsigned* pitchV, // array of pitches terminated w/ kInvalidMidiPitch double minDurSec, // min event duration double maxDurSec, // max event duration or 0 to ignore unsigned minChCnt, // min ch cnt or 0 to ignore @@ -155,10 +189,106 @@ extern "C" { cmSdbRC_t cmSdbResponseFree( cmSdbResponseH_t* rhp ); void cmSdbResponsePrint( cmSdbResponseH_t rh, cmRpt_t* rpt ); - // Time align all channel pairs by setting the onset times to - // the minimum time among all the pairs and the offset times to - // the maximum among all the pairs. - cmSdbRC_t cmSdbSyncChPairs( cmSdbH_t h ); + //================================================================================================ + // Sequence Related functions + // + typedef struct + { + unsigned uuid; // uuid of sample data base envent. + double begSec; // Event start time in seconds. + double durSec; // Event duration in seconds. + double gain; // Event amplitude scaling factor. + unsigned outChIdx; // Output channel index. + } cmSdbSeqEvent_t; + + // Generate a random sequence of events with a programmable + // density of events per second. + // + // 'minEvtPerSec' and 'maxEvtPerSec' specify the min and max count of events + // which may be initiated per second. + // + // The application should release all sequence handles + // returned from this function via a call to cmSdbSeqFree(). + // cmSdbDestroy() will automatically release any sequence + // handles not previously release by cmSdbSeqFree(). + // + // Note that the event selection is done with replacement. + // The same event may therefore be selected more than + // once. + cmSdbRC_t cmSdbSeqRand( + cmSdbResponseH_t rh, + unsigned seqDurSecs, + unsigned seqChCnt, + unsigned minEvtPerSec, + unsigned maxEvtPerSec, + cmSdbSeqH_t* shp ); + + // Generate a sequence of serial events w/ gapSec seconds + // between each event. Events longer than 'maxEvtDurSec' + // seconds are truncated to 'maxEvtDurSec'. + cmSdbRC_t cmSdbSeqSerial( + cmSdbResponseH_t rh, + unsigned seqChCnt, + double gapSec, + double maxEvtDurSec, + cmSdbSeqH_t* shp ); + + // Generate a chord sequence by randomly selecting one event + // from each response handle. + cmSdbRC_t cmSdbSeqChord( + cmSdbResponseH_t* rhp, // one rhp[rn] query resonse per chord note + unsigned rn, // count of response handles in rhp[]. + unsigned seqChCnt, // output sequence channel count + unsigned maxEvtDurSec, // maximum event duration or 0 to prevent truncation + cmSdbSeqH_t* shp ); + + // Release a sequence. + cmSdbRC_t cmSdbSeqFree( cmSdbSeqH_t* shp ); + + // Return the count of sequence events. + unsigned cmSdbSeqCount( cmSdbSeqH_t sh ); + + // Return a pointer to a specified cmSdbSeqEvent_t record. + // where 0 <= index < cmSdbSeqCount(sh) + const cmSdbSeqEvent_t* cmSdbSeqEvent( cmSdbSeqH_t sh, unsigned index ); + + // Given a seqence index return the associate cmSdbEvent_t. + const cmSdbEvent_t* cmSdbSeqSdbEvent( cmSdbSeqH_t sh, unsigned index ); + + // Return the total duration of the sequence in seconds. + double cmSdbSeqDurSeconds( cmSdbSeqH_t sh ); + + // Return the sample rate of the first event in the sequence that + // has a non-zero sample rate. There is no guarantee that all + // of the other events in the sequence have the same sample rate + // unless this was enforced by the query response that the + // sequence was generated from. + double cmSdbSeqSampleRate( cmSdbSeqH_t sh ); + + // Generate an audio from a sequence and return it in + // a signal vector. + cmSdbRC_t cmSdbSeqToAudio( + cmSdbSeqH_t sh, + unsigned decayMs, // decay rate for truncated events + double noiseDb, // (-70.0) pad signal with white noise to avoid digital silence + double evtNormFact, // normalize each sample event by normFact / abs(max(x[])) or 0 to skip normalization + cmSample_t** signalRef, // *signalRef[*sigSmpCntRef * sh.chCnt] returned audio signal + unsigned* sigSmpCntRef ); // count of frames in *signalRef + + // Generate an audio file from a sequence vector. + cmSdbRC_t cmSdbSeqToAudioFn( + cmSdbSeqH_t sh, + unsigned decayMs, // decay rate for truncated events + double noiseDb, // (-70.0) pad signal with white noise to avoid digital silence + double evtNormFact, // normalize each sample event by normFact / abs(max(x[])) or 0 to skip normalization + double normFact, // total signal norm factor or 0.0 to skip normalization + const cmChar_t* fn, // write the output signal to this audio file + unsigned bitsPerSample // audio file bits per sample + ); + + + // Print a sequence event listing. + void cmSdbSeqPrint( cmSdbSeqH_t sh, cmRpt_t* rpt ); cmSdbRC_t cmSdbTest( cmCtx_t* ctx );