#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 "cmFloatTypes.h" #include "cmComplexTypes.h" #include "cmFile.h" #include "cmFileSys.h" #include "cmJson.h" #include "cmSymTbl.h" #include "cmAudioFile.h" #include "cmText.h" #include "cmProcObj.h" #include "cmProcTemplate.h" #include "cmMath.h" #include "cmTime.h" #include "cmMidi.h" #include "cmMidiFile.h" #include "cmProc.h" #include "cmProc2.h" #include "cmVectOps.h" #include "cmTimeLine.h" #include "cmScore.h" #include "cmProc4.h" //======================================================================================================================= //---------------------------------------------------------------------------------------- void ed_print_mtx( ed_r* r) { unsigned i,j,k; for(i=0; irn; ++i) { for(j=0; jcn; ++j) { printf("("); const ed_val* vp = r->m + i + (j*r->rn); for(k=0; kv[k]); if( ktransFl?'t':' '); } printf("\n"); } } void ed_init( ed_r* r, const char* s0, const char* s1 ) { unsigned i,j,k; r->rn = strlen(s0)+1; r->cn = strlen(s1)+1; r->m = cmMemAllocZ(ed_val, r->rn*r->cn ); r->pn = r->rn + r->cn; r->p_mem = cmMemAllocZ(ed_path, 2*r->pn ); r->p_avl = r->p_mem; r->p_cur = NULL; r->p_opt = r->p_mem + r->pn; r->s_opt = DBL_MAX; r->s0 = s0; r->s1 = s1; for(i=0; irn; ++i) for(j=0; jcn; ++j) { unsigned v[] = {0,0,0,0}; if( i == 0 ) { v[kEdMinIdx] = j; v[kEdInsIdx] = j; } else if( j == 0 ) { v[kEdMinIdx] = i; v[kEdDelIdx] = i; } for(k=0; km[ i + (j*r->rn) ].v[k] = v[k]; } // put pn path records on the available list for(i=0; ipn; ++i) r->p_mem[i].next = ipn-1 ? r->p_mem + i + 1 : NULL; } unsigned _ed_min( ed_r* r, unsigned i, unsigned j ) { assert( irn && jcn ); return r->m[ i + (j*r->rn) ].v[kEdMinIdx]; } bool _ed_is_trans( ed_r* r, const ed_val* v1p, unsigned i, unsigned j ) { bool fl = false; ed_val* v0p = r->m + i + (j*r->rn); if( i>=1 && j>=1 && v1p->v[kEdMinIdx] == v1p->v[kEdSubIdx] && v1p->matchFl == false && v0p->v[kEdMinIdx] == v0p->v[kEdSubIdx] && v0p->matchFl == false ) { char c00 = r->s0[i-1]; char c01 = r->s0[i ]; char c10 = r->s1[j-1]; char c11 = r->s1[j ]; fl = c00==c11 && c01==c10; } return fl; } void ed_calc_mtx( ed_r* r ) { unsigned i,j; for(i=1; irn; ++i) for(j=1; jcn; ++j) { ed_val* vp = r->m + i + (j*r->rn); vp->matchFl = r->s0[i-1] == r->s1[j-1]; unsigned cost = vp->matchFl ? 0 : 1; vp->v[kEdSubIdx] = _ed_min(r,i-1,j-1) + cost; vp->v[kEdDelIdx] = _ed_min(r,i-1,j ) + 1; vp->v[kEdInsIdx] = _ed_min(r,i, j-1) + 1; vp->v[kEdMinIdx] = cmMin( vp->v[kEdSubIdx], cmMin(vp->v[kEdDelIdx],vp->v[kEdInsIdx])); vp->transFl = _ed_is_trans(r,vp,i-1,j-1); } } void ed_path_push( ed_r* r, unsigned code, unsigned ri, unsigned ci, bool matchFl, bool transFl ) { assert(r->p_avl != NULL ); ed_path* p = r->p_avl; r->p_avl = r->p_avl->next; p->code = code; p->ri = ri; p->ci = ci; p->matchFl = matchFl; p->transFl = transFl; p->next = r->p_cur; r->p_cur = p; } void ed_path_pop( ed_r* r ) { assert( r->p_cur != NULL ); ed_path* tp = r->p_cur->next; r->p_cur->next = r->p_avl; r->p_avl = r->p_cur; r->p_cur = tp; } double ed_score_candidate( ed_r* r ) { ed_path* cp = r->p_cur; ed_path* bp = r->p_cur; ed_path* ep = NULL; for(; cp!=NULL; cp=cp->next) if( cp->code != kEdInsIdx ) { bp = cp; break; } for(; cp!=NULL; cp=cp->next) if( cp->code!=kEdInsIdx ) ep = cp; assert( ep!=NULL && bp!=ep); unsigned n=1; for(cp=bp; cp!=ep; cp=cp->next) ++n; double gapCnt = 0; double penalty = 0; bool pfl = bp->matchFl; unsigned i; cp = bp; for(i=0; inext) { // a gap is a transition from a matching subst. to an insert or deletion //if( pc != cp->code && cp->code != kEdSubIdx && pc==kEdSubIdx && pfl==true ) if( pfl==true && cp->matchFl==false ) ++gapCnt; // switch( cp->code ) { case kEdSubIdx: penalty += cp->matchFl ? 0 : 1; penalty -= cp->transFl ? 1 : 0; break; case kEdDelIdx: penalty += 1; break; case kEdInsIdx: penalty += 1; break; } pfl = cp->matchFl; } double score = gapCnt/n + penalty; printf("n:%i gaps:%f gap_score:%f penalty:%f score:%f\n",n,gapCnt,gapCnt/n,penalty,score); return score; } void ed_eval_candidate( ed_r* r, double score ) { if( r->s_opt == DBL_MAX || r->s_opt > score) { // copy the p_cur to p_opt[] ed_path* cp = r->p_cur; unsigned i; for(i=0; cp!=NULL && ipn; cp=cp->next,++i) { r->p_opt[i].code = cp->code; r->p_opt[i].ri = cp->ri; r->p_opt[i].ci = cp->ci; r->p_opt[i].matchFl = cp->matchFl; r->p_opt[i].transFl = cp->transFl; } assert( i < r->pn ); r->p_opt[i].code = 0; // terminate with code=0 r->s_opt = score; } } void ed_print_opt( ed_r* r ) { unsigned i; for(i=0; r->p_opt[i].code!=0; ++i) { ed_path* cp = r->p_opt + i; char c0 = cp->matchFl ? 'm' : ' '; char c1 = cp->transFl ? 't' : ' '; printf("%2i code:%i ri:%2i ci:%2i %c%c\n",i,cp->code,cp->ri,cp->ci,c0,c1); } printf("score:%f\n",r->s_opt); } void ed_print_candidate( ed_r* r ) { ed_path* cp = r->p_cur; unsigned pn = r->pn; unsigned i; char s0[pn+1]; char s1[pn+1]; char s2[pn+1]; char s3[pn+1]; s0[pn] = 0; s1[pn] = 0; s2[pn] = 0; s3[pn] = 0; for(i=0; inext) { switch(cp->code) { case kEdSubIdx: // subst assert( 0 <= cp->ri && cp->ri <= r->rn ); assert( 0 <= cp->ci && cp->ci <= r->cn ); s0[i] = r->s0[cp->ri]; s1[i] = r->s1[cp->ci]; s2[i] = 's'; s3[i] = cp->matchFl ? 'm' : ' '; break; case kEdDelIdx: // delete assert( 0 <= cp->ri && cp->ri <= r->rn ); s0[i] = r->s0[cp->ri]; s1[i] = ' '; s2[i] = 'd'; s3[i] = ' '; break; case kEdInsIdx: // insert assert( 0 <= cp->ci && cp->ci <= r->cn ); s0[i] = ' '; s1[i] = r->s1[cp->ci]; s2[i] = 'i'; s3[i] = ' '; break; } } if( i < pn ) { s0[i] = 0; s1[i] = 0; s2[i] = 0; s3[i] = 0; } printf("\ns0:%s\n",s0); printf("s1:%s\n",s1); printf("s2:%s\n",s2); printf("s3:%s\n",s3); } // traverse the solution matrix from the lower-right to // the upper-left. void ed_node( ed_r* r, int i, int j ) { unsigned m; // stop when the upper-right is encountered if( i==0 && j==0 ) { ed_print_candidate(r); ed_eval_candidate(r, ed_score_candidate(r) ); return; } ed_val* vp = r->m + i + (j*r->rn); // for each possible dir: up,left,up-left for(m=1; mv[m] == vp->v[kEdMinIdx] ) { unsigned ii = i-1; unsigned jj = j-1; switch(m) { case kEdSubIdx: break; case kEdDelIdx: jj = j; break; case kEdInsIdx: ii = i; break; } // prepend to the current candidate path: r->p_cur ed_path_push(r,m,ii,jj,vp->matchFl,vp->transFl); // recurse! ed_node(r,ii,jj); // remove the first element from the current path ed_path_pop(r); } } void ed_align( ed_r* r ) { int i = r->rn-1; int j = r->cn-1; unsigned m = r->m[i + (j*r->rn)].v[kEdMinIdx]; if( m==cmMax(r->rn,r->cn) ) printf("Edit distance is at max: %i. No Match.\n",m); else ed_node(r,i,j); } void ed_free( ed_r* r ) { cmMemFree(r->m); cmMemFree(r->p_mem); } void ed_main() { const char* s0 = "YHCQPGK"; const char* s1 = "LAHYQQKPGKA"; s0 = "ABCDE"; s1 = "ABDCE"; //s1 = "FGHIJK"; ed_r r; ed_init(&r,s0,s1); ed_calc_mtx(&r); ed_print_mtx(&r); ed_align(&r); ed_print_opt(&r); ed_free(&r); } //======================================================================================================================= cmScMatch* cmScMatchAlloc( cmCtx* c, cmScMatch* p, cmScH_t scH, unsigned maxScWndN, unsigned maxMidiWndN ) { cmScMatch* op = cmObjAlloc(cmScMatch,c,p); if( cmScoreIsValid(scH) ) if( cmScMatchInit(op,scH,maxScWndN,maxMidiWndN) != cmOkRC ) cmScMatchFree(&op); return op; } cmRC_t cmScMatchFree( cmScMatch** pp ) { cmRC_t rc = cmOkRC; if( pp==NULL || *pp==NULL ) return rc; cmScMatch* p = *pp; if((rc = cmScMatchFinal(p)) != cmOkRC ) return rc; cmMemFree(p->loc); cmMemFree(p->m); cmMemFree(p->p_mem); cmObjFree(pp); return rc; } void _cmScMatchInitLoc( cmScMatch* p ) { unsigned li,ei; p->locN = cmScoreEvtCount(p->scH); p->loc = cmMemResizeZ(cmScMatchLoc_t,p->loc,p->locN); // for each score location for(li=0,ei=0; liscH); ++li) { unsigned i,n; const cmScoreLoc_t* lp = cmScoreLoc(p->scH,li); // count the number of note events at location li for(n=0,i=0; ievtCnt; ++i) if( lp->evtArray[i]->type == kNonEvtScId ) ++n; assert( ei+n <= p->locN ); // duplicate each note at location li n times for(i=0; iloc[ei+i].evtCnt = n; p->loc[ei+i].evtV = cmMemAllocZ(cmScMatchEvt_t,n); p->loc[ei+i].scLocIdx = li; p->loc[ei+i].barNumb = lp->barNumb; for(j=0,k=0; jevtCnt; ++j) if( lp->evtArray[j]->type == kNonEvtScId ) { p->loc[ei+i].evtV[k].pitch = lp->evtArray[j]->pitch; p->loc[ei+i].evtV[k].scEvtIdx = lp->evtArray[j]->index; ++k; } } ei += n; } assert(ei<=p->locN); p->locN = ei; } cmRC_t cmScMatchInit( cmScMatch* p, cmScH_t scH, unsigned maxScWndN, unsigned maxMidiWndN ) { unsigned i; cmRC_t rc; if((rc = cmScMatchFinal(p)) != cmOkRC ) return rc; p->scH = scH; p->mrn = maxMidiWndN + 1; p->mcn = maxScWndN + 1; p->mmn = maxMidiWndN; p->msn = maxScWndN; _cmScMatchInitLoc(p); p->m = cmMemResizeZ(cmScMatchVal_t, p->m, p->mrn*p->mcn ); p->pn = p->mrn + p->mcn; p->p_mem = cmMemResizeZ(cmScMatchPath_t, p->p_mem, 2*p->pn ); p->p_avl = p->p_mem; p->p_cur = NULL; p->p_opt = p->p_mem + p->pn; // put pn path records on the available list for(i=0; ipn; ++i) { p->p_mem[i].next = ipn-1 ? p->p_mem + i + 1 : NULL; p->p_opt[i].next = ipn-1 ? p->p_opt + i + 1 : NULL; } return rc; } cmRC_t cmScMatchFinal( cmScMatch* p ) { unsigned i; if( p != NULL ) for(i=0; ilocN; ++i) cmMemPtrFree(&p->loc[i].evtV); return cmOkRC; } cmRC_t _cmScMatchInitMtx( cmScMatch* p, unsigned rn, unsigned cn ) { //if( rn >p->mrn && cn > p->mcn ) if( rn*cn > p->mrn*p->mcn ) { return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "MIDI sequence length must be less than %i. Score sequence length must be less than %i.",p->mmn,p->msn); } // if the size of the mtx is not changing then there is nothing to do if( rn == p->rn && cn == p->cn ) return cmOkRC; // update the mtx size p->rn = rn; p->cn = cn; // fill in the default values for the first row // and column of the DP matrix unsigned i,j,k; for(i=0; im[ i + (j*rn) ].v[k] = v[k]; } return cmOkRC; } cmScMatchVal_t* _cmScMatchValPtr( cmScMatch* p, unsigned i, unsigned j, unsigned rn, unsigned cn ) { assert( i < rn && j < cn ); return p->m + i + (j*rn); } unsigned _cmScMatchIsMatchIndex( const cmScMatchLoc_t* loc, unsigned pitch ) { unsigned i; for(i=0; ievtCnt; ++i) if( loc->evtV[i].pitch == pitch ) return i; return cmInvalidIdx; } bool _cmScMatchIsMatch( const cmScMatchLoc_t* loc, unsigned pitch ) { return _cmScMatchIsMatchIndex(loc,pitch) != cmInvalidIdx; } bool _cmScMatchIsTrans( cmScMatch* p, const cmScMatchMidi_t* midiV, const cmScMatchVal_t* v1p, unsigned bsi, unsigned i, unsigned j, unsigned rn, unsigned cn ) { bool fl = false; cmScMatchVal_t* v0p = _cmScMatchValPtr(p,i,j,rn,cn); if( i>=1 && j>=1 && v1p->v[kSmMinIdx] == v1p->v[kSmSubIdx] && cmIsNotFlag(v1p->flags,kSmMatchFl) && v0p->v[kSmMinIdx] == v0p->v[kSmSubIdx] && cmIsNotFlag(v0p->flags,kSmMatchFl) ) { unsigned c00 = midiV[i-1].pitch; unsigned c01 = midiV[i ].pitch; cmScMatchLoc_t* c10 = p->loc + bsi + j - 1; cmScMatchLoc_t* c11 = p->loc + bsi + j; fl = _cmScMatchIsMatch(c11,c00) && _cmScMatchIsMatch(c10,c01); } return fl; } unsigned _cmScMatchMin( cmScMatch* p, unsigned i, unsigned j, unsigned rn, unsigned cn ) { return _cmScMatchValPtr(p,i,j,rn,cn)->v[kSmMinIdx]; } // Return false if bsi + cn > p->locN // pitchV[rn-1] bool _cmScMatchCalcMtx( cmScMatch* p, unsigned bsi, const cmScMatchMidi_t* midiV, unsigned rn, unsigned cn ) { // loc[begScanLocIdx:begScanLocIdx+cn-1] must be valid if( bsi + cn > p->locN ) return false; unsigned i,j; for(j=1; jloc + bsi + j - 1; unsigned pitch = midiV[i-1].pitch; cmScMatchVal_t* vp = _cmScMatchValPtr(p,i,j,rn,cn); unsigned idx = _cmScMatchIsMatchIndex(loc,pitch); vp->flags = idx==cmInvalidIdx ? 0 : kSmMatchFl; vp->scEvtIdx = idx==cmInvalidIdx ? cmInvalidIdx : loc->evtV[idx].scEvtIdx; unsigned cost = cmIsFlag(vp->flags,kSmMatchFl) ? 0 : 1; vp->v[kSmSubIdx] = _cmScMatchMin(p,i-1,j-1, rn, cn) + cost; vp->v[kSmDelIdx] = _cmScMatchMin(p,i-1,j , rn, cn) + 1; vp->v[kSmInsIdx] = _cmScMatchMin(p,i, j-1, rn, cn) + 1; vp->v[kSmMinIdx] = cmMin( vp->v[kSmSubIdx], cmMin(vp->v[kSmDelIdx],vp->v[kSmInsIdx])); vp->flags |= _cmScMatchIsTrans(p,midiV,vp,bsi,i-1,j-1,rn,cn) ? kSmTransFl : 0; } return true; } void _cmScMatchPrintMtx( cmScMatch* r, unsigned rn, unsigned cn) { unsigned i,j,k; for(i=0; iv[k]); if( kflags,kSmMatchFl)?'m':' ',cmIsFlag(vp->flags,kSmTransFl)?'t':' '); } printf("\n"); } } void _cmScMatchPathPush( cmScMatch* r, unsigned code, unsigned ri, unsigned ci, unsigned flags, unsigned scEvtIdx ) { assert(r->p_avl != NULL ); cmScMatchPath_t* p = r->p_avl; r->p_avl = r->p_avl->next; p->code = code; p->ri = ri; p->ci = ci; p->flags = code==kSmSubIdx && cmIsFlag(flags,kSmMatchFl) ? kSmMatchFl : 0; p->flags |= cmIsFlag(flags,kSmTransFl) ? kSmTransFl : 0; p->scEvtIdx= scEvtIdx; p->next = r->p_cur; r->p_cur = p; } void _cmScMatchPathPop( cmScMatch* r ) { assert( r->p_cur != NULL ); cmScMatchPath_t* tp = r->p_cur->next; r->p_cur->next = r->p_avl; r->p_avl = r->p_cur; r->p_cur = tp; } double _cmScMatchCalcCandidateCost( cmScMatch* r ) { cmScMatchPath_t* cp = r->p_cur; cmScMatchPath_t* bp = r->p_cur; cmScMatchPath_t* ep = NULL; // skip leading inserts for(; cp!=NULL; cp=cp->next) if( cp->code != kSmInsIdx ) { bp = cp; break; } // skip to trailing inserts for(; cp!=NULL; cp=cp->next) if( cp->code!=kSmInsIdx ) ep = cp; // count remaining path length assert( ep!=NULL ); unsigned n=1; for(cp=bp; cp!=ep; cp=cp->next) ++n; double gapCnt = 0; double penalty = 0; bool pfl = cmIsFlag(bp->flags,kSmMatchFl); unsigned i; cp = bp; for(i=0; inext) { // a gap is a transition from a matching subst. to an insert or deletion //if( pc != cp->code && cp->code != kSmSubIdx && pc==kSmSubIdx && pfl==true ) if( pfl==true && cmIsFlag(cp->flags,kSmMatchFl)==false ) ++gapCnt; // switch( cp->code ) { case kSmSubIdx: penalty += cmIsFlag(cp->flags,kSmMatchFl) ? 0 : 1; penalty -= cmIsFlag(cp->flags,kSmTransFl) ? 1 : 0; break; case kSmDelIdx: penalty += 1; break; case kSmInsIdx: penalty += 1; break; } pfl = cmIsFlag(cp->flags,kSmMatchFl); } double cost = gapCnt/n + penalty; //printf("n:%i gaps:%f gap_score:%f penalty:%f score:%f\n",n,gapCnt,gapCnt/n,penalty,score); return cost; } double _cmScMatchEvalCandidate( cmScMatch* r, double min_cost, double cost ) { if( min_cost == DBL_MAX || cost < min_cost) { // copy the p_cur to p_opt[] cmScMatchPath_t* cp = r->p_cur; unsigned i; for(i=0; cp!=NULL && ipn; cp=cp->next,++i) { r->p_opt[i].code = cp->code; r->p_opt[i].ri = cp->ri; r->p_opt[i].ci = cp->ci; r->p_opt[i].flags = cp->flags; r->p_opt[i].scEvtIdx= cp->scEvtIdx; r->p_opt[i].next = cp->next==NULL ? NULL : r->p_opt + i + 1; } assert( i < r->pn ); r->p_opt[i].code = 0; // terminate with code=0 min_cost = cost; } return min_cost; } // NOTE: IF THE COST CALCULATION WAS BUILT INTO THE RECURSION THEN // THIS FUNCTION COULD BE MADE MORE EFFICIENT BECAUSE PATHS WHICH // EXCEEDED THE min_cost COULD BE SHORT CIRCUITED. // // traverse the solution matrix from the lower-right to // the upper-left. double _cmScMatchGenPaths( cmScMatch* r, int i, int j, unsigned rn, unsigned cn, double min_cost ) { unsigned m; // stop when the upper-right is encountered if( i==0 && j==0 ) return _cmScMatchEvalCandidate(r, min_cost, _cmScMatchCalcCandidateCost(r) ); cmScMatchVal_t* vp = _cmScMatchValPtr(r,i,j,rn,cn); // for each possible dir: up,left,up-left for(m=1; mv[m] == vp->v[kSmMinIdx] ) { // prepend to the current candidate path: r->p_cur _cmScMatchPathPush(r,m,i,j,vp->flags,vp->scEvtIdx); int ii = i-1; int jj = j-1; switch(m) { case kSmSubIdx: break; case kSmDelIdx: jj = j; break; case kSmInsIdx: ii = i; break; default: { assert(0); } } // recurse! min_cost = _cmScMatchGenPaths(r,ii,jj,rn,cn,min_cost); // remove the first element from the current path _cmScMatchPathPop(r); } return min_cost; } double _cmScMatchAlign( cmScMatch* p, unsigned rn, unsigned cn, double min_cost ) { int i = rn-1; int j = cn-1; unsigned m = _cmScMatchMin(p,i,j,rn,cn); if( m==cmMax(rn,cn) ) printf("Edit distance is at max: %i. No Match.\n",m); else min_cost = _cmScMatchGenPaths(p,i,j,rn,cn,min_cost); return min_cost; } cmRC_t cmScMatchExec( cmScMatch* p, unsigned locIdx, unsigned locN, const cmScMatchMidi_t* midiV, unsigned midiN, double min_cost ) { cmRC_t rc; unsigned rn = midiN + 1; unsigned cn = locN + 1; // set the DP matrix default values if((rc = _cmScMatchInitMtx(p, rn, cn )) != cmOkRC ) return rc; // _cmScMatchCalcMtx() returns false if the score window exceeds the length of the score if(!_cmScMatchCalcMtx(p,locIdx, midiV, rn, cn) ) return cmEofRC; //_cmScMatchPrintMtx(p,rn,cn); // locate the path through the DP matrix with the lowest edit distance (cost) p->opt_cost = _cmScMatchAlign(p, rn, cn, min_cost); return rc; } // Traverse the least cost path and: // 1) Return, esi, the score location index of the last MIDI note // which has a positive match with the score and assign // the internal score index to cp->locIdx. // // 2) Set cmScAlignPath_t.locIdx - index into p->loc[] associated // with each path element that is a 'substitute' or an 'insert'. // // 3) Set *missCnPtr: the count of trailing non-positive matches. // // i_opt is index into p->loc[] of p->p_opt. unsigned cmScMatchDoSync( cmScMatch* p, unsigned i_opt, cmScMatchMidi_t* midiBuf, unsigned midiN, unsigned* missCntPtr ) { cmScMatchPath_t* cp = p->p_opt; unsigned missCnt = 0; unsigned esi = cmInvalidIdx; unsigned i; for(i=0; cp!=NULL; cp=cp->next) { // there is no MIDI note associated with 'inserts' if( cp->code != kSmInsIdx ) { assert( cp->ri > 0 ); midiBuf[ cp->ri-1 ].locIdx = cmInvalidIdx; } switch( cp->code ) { case kSmSubIdx: midiBuf[ cp->ri-1 ].locIdx = i_opt + i; midiBuf[ cp->ri-1 ].scEvtIdx = cp->scEvtIdx; if( cmIsFlag(cp->flags,kSmMatchFl) ) { esi = i_opt + i; missCnt = 0; } else { ++missCnt; } // fall through case kSmInsIdx: cp->locIdx = i_opt + i; ++i; break; case kSmDelIdx: cp->locIdx = cmInvalidIdx; ++missCnt; break; } } if( missCntPtr != NULL ) *missCntPtr = missCnt; return esi; } void _cmScMatchMidiEvtFlags( cmScMatch* p, const cmScMatchLoc_t* lp, unsigned evtIdx, char* s, unsigned sn ) { const cmScoreLoc_t* slp = cmScoreLoc(p->scH,lp->scLocIdx); assert( evtIdx < slp->evtCnt ); const cmScoreEvt_t* ep = slp->evtArray[evtIdx]; unsigned i = 0; s[0] = 0; if( cmIsFlag(ep->flags,kEvenScFl) ) s[i++] = 'e'; if( cmIsFlag(ep->flags,kTempoScFl) ) s[i++] = 't'; if( cmIsFlag(ep->flags,kDynScFl) ) s[i++] = 'd'; if( cmIsFlag(ep->flags,kGraceScFl) ) s[i++] = 'g'; s[i++] = 0; assert( i <= sn ); } void _cmScMatchPrintPath( cmScMatch* p, cmScMatchPath_t* cp, unsigned bsi, const cmScMatchMidi_t* midiV ) { assert( bsi != cmInvalidIdx ); cmScMatchPath_t* pp = cp; int polyN = 0; int i; printf("loc: "); // get the polyphony count for the score window for(i=0; pp!=NULL; pp=pp->next) { cmScMatchLoc_t* lp = p->loc + bsi + pp->ci; if( pp->code!=kSmDelIdx ) { if(lp->evtCnt > polyN) polyN = lp->evtCnt; printf("%4i%4s ",bsi+i," "); ++i; } else printf("%4s%4s "," "," "); } printf("\n"); // print the score notes for(i=polyN; i>0; --i) { printf("%3i: ",i); for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->code!=kSmDelIdx ) { int locIdx = bsi + pp->ci - 1; assert(0 <= locIdx && locIdx <= p->locN); cmScMatchLoc_t* lp = p->loc + locIdx; if( lp->evtCnt >= i ) { unsigned sn = 6; char s[sn]; _cmScMatchMidiEvtFlags(p,lp,i-1,s,sn ); printf("%4s%-4s ",cmMidiToSciPitch(lp->evtV[i-1].pitch,NULL,0),s); } else printf("%4s%4s "," "," "); } else printf("%4s%4s ", (pp->code==kSmDelIdx? "-" : " ")," "); /* int locIdx = bsi + pp->ci - 1; assert(0 <= locIdx && locIdx <= p->locN); cmScMatchLoc_t* lp = p->loc + locIdx; if( pp->code!=kSmDelIdx && lp->evtCnt >= i ) printf("%4s ",cmMidiToSciPitch(lp->evtV[i-1].pitch,NULL,0)); else printf("%4s ", pp->code==kSmDelIdx? "-" : " "); */ } printf("\n"); } printf("mid: "); // print the MIDI buffer for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->code!=kSmInsIdx ) printf("%4s%4s ",cmMidiToSciPitch(midiV[pp->ri-1].pitch,NULL,0)," "); else printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," "); } printf("\nvel: "); // print the MIDI velocity for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->code!=kSmInsIdx ) printf("%4i%4s ",midiV[pp->ri-1].vel," "); else printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," "); } printf("\nmni: "); // print the MIDI buffer index (mni) for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->code!=kSmInsIdx ) printf("%4i%4s ",midiV[pp->ri-1].mni," "); else printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," "); } printf("\n op: "); // print the substitute/insert/delete operation for(pp=cp; pp!=NULL; pp=pp->next) { char c = ' '; switch( pp->code ) { case kSmSubIdx: c = 's'; break; case kSmDelIdx: c = 'd'; break; case kSmInsIdx: c = 'i'; break; default: { assert(0); } } printf("%4c%4s ",c," "); } printf("\n "); // give substitute attribute (match or transpose) for(pp=cp; pp!=NULL; pp=pp->next) { cmChar_t s[3]; int k = 0; if( cmIsFlag(pp->flags,kSmMatchFl) ) s[k++] = 'm'; if( cmIsFlag(pp->flags,kSmTransFl) ) s[k++] = 't'; s[k] = 0; printf("%4s%4s ",s," "); } printf("\nscl: "); // print the stored location index for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->locIdx == cmInvalidIdx ) printf("%4s%4s "," "," "); else printf("%4i%4s ",p->loc[pp->locIdx].scLocIdx," "); } printf("\nbar: "); // print the stored location index for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->locIdx==cmInvalidIdx || pp->scEvtIdx==cmInvalidIdx ) printf("%4s%4s "," "," "); else { const cmScoreEvt_t* ep = cmScoreEvt(p->scH, pp->scEvtIdx ); printf("%4i%4s ",ep->barNumb," "); } } printf("\nsec: "); // print seconds unsigned begSmpIdx = cmInvalidIdx; for(pp=cp; pp!=NULL; pp=pp->next) { if( pp->code!=kSmInsIdx ) { if( begSmpIdx == cmInvalidIdx ) begSmpIdx = midiV[pp->ri-1].smpIdx; printf("%2.2f%4s ", (double)(midiV[pp->ri-1].smpIdx - begSmpIdx)/96000.0," "); } else printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," "); } printf("\n\n"); } //======================================================================================================================= cmScMatcher* cmScMatcherAlloc( cmCtx* c, cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg ) { cmScMatcher* op = cmObjAlloc(cmScMatcher,c,p); if( op != NULL ) op->mp = cmScMatchAlloc(c,NULL,cmScNullHandle,0,0); if( srate != 0 ) { if( cmScMatcherInit(op,srate,scH,scWndN,midiWndN,cbFunc,cbArg) != cmOkRC ) cmScMatcherFree(&op); } return op; } cmRC_t cmScMatcherFree( cmScMatcher** pp ) { cmRC_t rc = cmOkRC; if( pp==NULL || *pp==NULL ) return rc; cmScMatcher* p = *pp; if((rc = cmScMatcherFinal(p)) != cmOkRC ) return rc; cmScMatchFree(&p->mp); cmMemFree(p->midiBuf); cmMemFree(p->res); cmObjFree(pp); return rc; } cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg ) { cmRC_t rc; if((rc = cmScMatcherFinal(p)) != cmOkRC ) return rc; if( midiWndN > scWndN ) return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The score alignment MIDI event buffer length (%i) must be less than the score window length (%i).",midiWndN,scWndN); if(( rc = cmScMatchInit(p->mp,scH,scWndN,midiWndN)) != cmOkRC ) return rc; p->cbFunc = cbFunc; p->cbArg = cbArg; p->mn = midiWndN; p->midiBuf = cmMemResizeZ(cmScMatchMidi_t,p->midiBuf,p->mn); p->initHopCnt = 50; p->stepCnt = 3; p->maxMissCnt = p->stepCnt+1; p->rn = 2 * cmScoreEvtCount(scH); p->res = cmMemResizeZ(cmScMatcherResult_t,p->res,p->rn); p->printFl = false; cmScMatcherReset(p,0); return rc; } cmRC_t cmScMatcherFinal( cmScMatcher* p ) { return cmScMatchFinal(p->mp); } cmRC_t cmScMatcherReset( cmScMatcher* p, unsigned scLocIdx ) { p->mbi = p->mp->mmn; p->mni = 0; p->begSyncLocIdx = cmInvalidIdx; p->s_opt = DBL_MAX; p->missCnt = 0; p->scanCnt = 0; p->ri = 0; p->eli = cmInvalidIdx; p->ili = 0; // convert scLocIdx to an index into p->mp->loc[] unsigned i = 0; while(1) { for(i=0; imp->locN; ++i) if( p->mp->loc[i].scLocIdx == scLocIdx ) { p->ili = i; break; } assert(p->mp->locN>0); if( i!=p->mp->locN || scLocIdx==p->mp->loc[p->mp->locN-1].scLocIdx) break; scLocIdx += 1; } if( i==p->mp->locN) return cmCtxRtCondition( &p->obj, cmSubSysFailRC, "Score matcher reset failed."); return cmOkRC; } bool cmScMatcherInputMidi( cmScMatcher* p, unsigned smpIdx, unsigned muid, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 ) { if( (status&0xf0) != kNoteOnMdId) return false; if( d1 == 0 ) return false; unsigned mi = p->mn-1; //printf("%3i %4s\n",p->mni,cmMidiToSciPitch(d0,NULL,0)); // shift the new MIDI event onto the end of the MIDI buffer memmove(p->midiBuf, p->midiBuf+1, sizeof(cmScMatchMidi_t)*mi); p->midiBuf[mi].locIdx = cmInvalidIdx; p->midiBuf[mi].scEvtIdx = cmInvalidIdx; p->midiBuf[mi].mni = p->mni++; p->midiBuf[mi].smpIdx = smpIdx; p->midiBuf[mi].muid = muid; p->midiBuf[mi].pitch = d0; p->midiBuf[mi].vel = d1; if( p->mbi > 0 ) --p->mbi; return true; } void _cmScMatcherStoreResult( cmScMatcher* p, unsigned locIdx, unsigned scEvtIdx, unsigned flags, const cmScMatchMidi_t* mp ) { // don't store missed score note results assert( mp != NULL ); bool matchFl = cmIsFlag(flags,kSmMatchFl); bool tpFl = locIdx!=cmInvalidIdx && matchFl; bool fpFl = locIdx==cmInvalidIdx || matchFl==false; cmScMatcherResult_t * rp = NULL; unsigned i; cmScMatcherResult_t r; assert( tpFl==false || (tpFl==true && locIdx != cmInvalidIdx ) ); // it is possible that the same MIDI event is reported more than once // (due to step->scan back tracking) - try to find previous result records // associated with this MIDI event for(i=0; iri; ++i) if( p->res[i].mni == mp->mni ) { // if this is not the first time this note was reported and it is a true positive if( tpFl ) { rp = p->res + i; break; } // a match was found but this was not a true-pos so ignore it return; } if( rp == NULL ) { if( p->ri >= p->rn ) { rp = &r; memset(rp,0,sizeof(r)); } else { rp = p->res + p->ri; ++p->ri; } } rp->locIdx = locIdx; rp->scEvtIdx = scEvtIdx; rp->mni = mp->mni; rp->muid = mp->muid; rp->smpIdx = mp->smpIdx; rp->pitch = mp->pitch; rp->vel = mp->vel; rp->flags = flags | (tpFl ? kSmTruePosFl : 0) | (fpFl ? kSmFalsePosFl : 0); if( p->cbFunc != NULL ) p->cbFunc(p,p->cbArg,rp); } void cmScMatcherPrintPath( cmScMatcher* p ) { _cmScMatchPrintPath(p->mp, p->mp->p_opt, p->begSyncLocIdx, p->midiBuf ); } unsigned cmScMatcherScan( cmScMatcher* p, unsigned bli, unsigned hopCnt ) { assert( p->mp != NULL && p->mp->mmn > 0 ); unsigned i_opt = cmInvalidIdx; double s_opt = DBL_MAX; cmRC_t rc = cmOkRC; unsigned i; // initialize the internal values set by this function p->missCnt = 0; p->eli = cmInvalidIdx; p->s_opt = DBL_MAX; // if the MIDI buf is not full if( p->mbi != 0 ) return cmInvalidIdx; // calc the edit distance from pitchV[] to a sliding score window for(i=0; rc==cmOkRC && (hopCnt==cmInvalidCnt || imp, bli + i, p->mp->msn, p->midiBuf, p->mp->mmn, s_opt ); switch(rc) { case cmOkRC: // normal result if( p->mp->opt_cost < s_opt ) { s_opt = p->mp->opt_cost; i_opt = bli + i; } break; case cmEofRC: // score window encountered the end of the score break; default: // error state return cmInvalidIdx; } } // store the cost assoc'd with i_opt p->s_opt = s_opt; if( i_opt == cmInvalidIdx ) return cmInvalidIdx; // set the locIdx field in midiBuf[], trailing miss count and // return the latest positive-match locIdx p->eli = cmScMatchDoSync(p->mp,i_opt,p->midiBuf,p->mp->mmn,&p->missCnt); // if no positive matches were found if( p->eli == cmInvalidIdx ) i_opt = cmInvalidIdx; else { cmScMatchPath_t* cp; // record result for(cp=p->mp->p_opt; cp!=NULL; cp=cp->next) if( cp->code != kSmInsIdx ) _cmScMatcherStoreResult(p, cp->locIdx, cp->scEvtIdx, cp->flags, p->midiBuf + cp->ri - 1); } return i_opt; } cmRC_t cmScMatcherStep( cmScMatcher* p ) { int i; unsigned pitch = p->midiBuf[ p->mn-1 ].pitch; unsigned locIdx = cmInvalidIdx; unsigned pidx = cmInvalidIdx; // the tracker must be sync'd to step if( p->eli == cmInvalidIdx ) return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The p->eli value must be valid to perform a step operation."); // if the end of the score has been reached if( p->eli + 1 >= p->mp->locN ) return cmEofRC; // attempt to match to next location first if( (pidx = _cmScMatchIsMatchIndex(p->mp->loc + p->eli + 1, pitch)) != cmInvalidIdx ) { locIdx = p->eli + 1; } else { // for(i=2; istepCnt; ++i) { // go forward if( p->eli+i < p->mp->locN && (pidx=_cmScMatchIsMatchIndex(p->mp->loc + p->eli + i, pitch))!=cmInvalidIdx ) { locIdx = p->eli + i; break; } // go backward if( p->eli >= (i-1) && (pidx=_cmScMatchIsMatchIndex(p->mp->loc + p->eli - (i-1), pitch))!=cmInvalidIdx ) { locIdx = p->eli - (i-1); break; } } } unsigned scEvtIdx = locIdx==cmInvalidIdx ? cmInvalidIdx : p->mp->loc[locIdx].evtV[pidx].scEvtIdx; p->midiBuf[ p->mn-1 ].locIdx = locIdx; p->midiBuf[ p->mn-1 ].scEvtIdx = scEvtIdx; if( locIdx == cmInvalidIdx ) ++p->missCnt; else { p->missCnt = 0; p->eli = locIdx; } // store the result _cmScMatcherStoreResult(p, locIdx, scEvtIdx, locIdx!=cmInvalidIdx ? kSmMatchFl : 0, p->midiBuf + p->mn - 1); if( p->missCnt >= p->maxMissCnt ) { unsigned begScanLocIdx = p->eli > p->mn ? p->eli - p->mn : 0; p->s_opt = DBL_MAX; unsigned bli = cmScMatcherScan(p,begScanLocIdx,p->mn*2); ++p->scanCnt; // if the scan failed find a match if( bli == cmInvalidIdx ) return cmCtxRtCondition( &p->obj, cmSubSysFailRC, "Scan resync. failed."); } return cmOkRC; } cmRC_t cmScMatcherExec( cmScMatcher* p, unsigned smpIdx, unsigned muid, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1, unsigned* scLocIdxPtr ) { bool fl = p->mbi > 0; cmRC_t rc = cmOkRC; unsigned org_eli = p->eli; if( scLocIdxPtr != NULL ) *scLocIdxPtr = cmInvalidIdx; // update the MIDI buffer with the incoming note if( cmScMatcherInputMidi(p,smpIdx,muid,status,d0,d1) == false ) return rc; // if the MIDI buffer transitioned to full then perform an initial scan sync. if( fl && p->mbi == 0 ) { if( (p->begSyncLocIdx = cmScMatcherScan(p,p->ili,p->initHopCnt)) == cmInvalidIdx ) { rc = cmInvalidArgRC; // signal init. scan sync. fail } else { //cmScMatcherPrintPath(p); } } else { // if the MIDI buffer is full then perform a step sync. if( !fl && p->mbi == 0 ) rc = cmScMatcherStep(p); } // if we lost sync if( p->eli == cmInvalidIdx ) { // IF WE LOST SYNC THEN WE BETTER DO SOMETHING - LIKE INCREASE THE SCAN HOPS // ON THE NEXT EVENT. p->eli = org_eli; } else { if( scLocIdxPtr!=NULL && p->eli != org_eli ) { //printf("LOC:%i bar:%i\n",p->eli,p->mp->loc[p->eli].barNumb); *scLocIdxPtr = p->mp->loc[p->eli].scLocIdx; } } return rc; } double cmScMatcherFMeas( cmScMatcher* p ) { unsigned bli = p->mp->locN; unsigned eli = 0; unsigned scNoteCnt = 0; // total count of score notes unsigned matchCnt = 0; // count of matched notes (true positives) unsigned wrongCnt = 0; // count of incorrect notes (false positives) unsigned missCnt = 0; // count of missed score notes (false negatives) unsigned i; for(i=0; iri; ++i) if( p->res[i].locIdx != cmInvalidIdx ) { bli = cmMin(bli,p->res[i].locIdx); eli = cmMax(eli,p->res[i].locIdx); if( cmIsFlag(p->res[i].flags,kSmTruePosFl) ) ++matchCnt; if( cmIsFlag(p->res[i].flags,kSmFalsePosFl) ) ++wrongCnt; } scNoteCnt = eli - bli + 1; missCnt = scNoteCnt - matchCnt; double prec = (double)2.0 * matchCnt / (matchCnt + wrongCnt); double rcal = (double)2.0 * matchCnt / (matchCnt + missCnt); double fmeas = prec * rcal / (prec + rcal); //printf("total:%i match:%i wrong:%i miss:%i\n",scNoteCnt,matchCnt,wrongCnt,missCnt); return fmeas; } typedef struct cmScMatcherPrint_str { unsigned flags; unsigned scLocIdx; unsigned mni; unsigned pitch; unsigned vel; unsigned barNumb; } cmScMatcherPrint_t; void _cmScMatcherInsertPrint(cmScMatcherPrint_t* a, unsigned i, unsigned* anp, unsigned aan, const cmScMatcherResult_t* rp, unsigned scLocIdx ) { assert( *anp + 1 <= aan ); memmove(a + i + 1, a + i, (*anp-i)*sizeof(cmScMatcherPrint_t)); memset( a + i, 0, sizeof(cmScMatcherPrint_t)); *anp += 1; a[i].flags = rp->flags; a[i].scLocIdx = scLocIdx; a[i].mni = rp->mni; a[i].pitch = rp->pitch; a[i].vel = rp->vel; } void cmScMatcherPrint( cmScMatcher* p ) { unsigned bsli = cmScoreEvtCount(p->mp->scH); unsigned esli = 0; unsigned i,j,k; // get first/last scLocIdx from res[] - this is the range of // score events that the score matcher has identified for(i=0; iri; ++i) if( p->res[i].locIdx != cmInvalidIdx ) { bsli = cmMin(bsli,p->mp->loc[p->res[i].locIdx].scLocIdx); esli = cmMax(esli,p->mp->loc[p->res[i].locIdx].scLocIdx); } unsigned an = 0; unsigned aan = p->ri; // calc the count of score events between bsli and esli. for(i=bsli; i<=esli; ++i) { cmScoreLoc_t* lp = cmScoreLoc(p->mp->scH, i); assert(lp != NULL); aan += lp->evtCnt; } // allocate an array of 'aan' print records cmScMatcherPrint_t* a = cmMemAllocZ(cmScMatcherPrint_t,aan); // fill the cmScMatcherPrint_t array with note and bar events from the score for(i=bsli; i<=esli; ++i) { unsigned scLocIdx = i; cmScoreLoc_t* lp = cmScoreLoc(p->mp->scH, scLocIdx ); // for each score event which occurs at this location for(j=0; jevtCnt; ++j) { assert( an < aan ); cmScoreEvt_t* ep = lp->evtArray[j]; cmScMatcherPrint_t* pp = a + an; an += 1; switch( ep->type ) { case kBarEvtScId: pp->flags = kSmBarFl; break; case kNonEvtScId: pp->flags = kSmNoteFl; break; } pp->scLocIdx = scLocIdx; pp->mni = cmInvalidIdx; pp->pitch = ep->pitch; pp->vel = kInvalidMidiVelocity; pp->barNumb = ep->barNumb; } } // // a[an] now contains a record for each note and bar event in the // time range associated with the score matcher's result array. // // for each result record for(i=0; iri; ++i) { cmScMatcherResult_t* rp = p->res + i; // if this result recd matched a score event if( cmIsFlag(rp->flags,kSmTruePosFl) ) { // locate the matching score event in a[an] for(k=0; kmp->loc[rp->locIdx].scLocIdx && a[k].pitch==rp->pitch ) { a[k].mni = rp->mni; a[k].vel = rp->vel; a[k].flags |= kSmMatchFl; break; } } // if this result did not match a score event if( cmIsFlag(rp->flags,kSmFalsePosFl) ) { unsigned d_min = 0; cmScMatcherPrint_t* dp = NULL; unsigned scLocIdx = cmInvalidIdx; // if this result does not have a valid locIdx // (e.g. errant MIDI notes: scan:'delete' note or a step:mis-match note) if( rp->locIdx == cmInvalidIdx ) { // find the print recd with the closet 'mni' for(k=0; k rp->mni ) d = a[k].mni - rp->mni; else d = rp->mni - a[k].mni; if( dp==NULL || d < d_min ) { dp = a + k; d_min = d; } } k = dp - a; assert( k < an ); scLocIdx = p->mp->loc[k].scLocIdx; if( a[k].mni < rp->mni ) ++k; } else // result w/ a valid locIdx (e.g. scan 'substitute' with no match) { scLocIdx = p->mp->loc[rp->locIdx].scLocIdx; // find the print recd with the closest scIdx for(k=0; k scLocIdx ) d = a[k].scLocIdx - scLocIdx; else d = scLocIdx - a[k].scLocIdx; if( dp==NULL || d < d_min ) { dp = a + k; d_min = d; } } k = dp - a; assert( k < an ); if( a[k].scLocIdx < scLocIdx ) ++k; } // create a new print recd to represent the false-positive result recd assert( dp != NULL ); _cmScMatcherInsertPrint(a, k, &an,aan,rp,scLocIdx); } } printf("sloc bar mni ptch flag\n"); printf("---- ---- ---- ---- ----\n"); for(i=0; imp = cmScMatchAlloc( c, NULL, cmScNullHandle, 0, 0 ); if( cmScoreIsValid(scH) ) if( cmScMeasInit(op,scH,srate,dynRefArray,dynRefCnt) != cmOkRC ) cmScMeasFree(&op); return op; } cmRC_t cmScMeasFree( cmScMeas** pp ) { cmRC_t rc = cmOkRC; if( pp==NULL || *pp==NULL ) return rc; cmScMeas* p = *pp; if((rc = cmScMeasFinal(p)) != cmOkRC ) return rc; cmScMatchFree(&p->mp); cmMemFree(p->midiBuf); cmMemFree(p->set); cmMemFree(p->dynRef); cmObjFree(pp); return rc; } void _cmScMeasPrint( cmScMeas* p ) { unsigned i; for(i=0; isn; ++i) { cmScMeasSet_t* sp = p->set + i; printf("%4i: sli:%4i %4i li:%4i %4i\n", i, sp->bsli, sp->esli, sp->bli, sp->eli ); } } int _cmScMeasSortFunc( const void* p0, const void* p1 ) { const cmScMeasSet_t* s0 = (const cmScMeasSet_t*)p0; const cmScMeasSet_t* s1 = (const cmScMeasSet_t*)p1; return s0->esli - s1->esli; } cmRC_t cmScMeasInit( cmScMeas* p, cmScH_t scH, double srate, const unsigned* dynRefArray, unsigned dynRefCnt ) { cmRC_t rc; unsigned i,j; unsigned si; unsigned maxScWndN = 0; if((rc = cmScMeasFinal(p)) != cmOkRC ) return rc; p->mii = 0; p->mn = 2 * cmScoreEvtCount(scH); p->midiBuf = cmMemResizeZ(cmScMatchMidi_t,p->midiBuf,p->mn); p->sn = cmScoreSetCount(scH); p->set = cmMemResizeZ(cmScMeasSet_t,p->set,p->sn); p->dynRef = cmMemResizeZ(unsigned,p->dynRef,dynRefCnt); p->dn = dynRefCnt; p->srate = srate; memcpy(p->dynRef,dynRefArray,sizeof(dynRefArray[0])*dynRefCnt); unsigned n = cmScoreLocCount(scH); // for each score location for(i=0,si=0; isetList; // for each set that ends on this score location for(; sp!=NULL; sp=sp->llink,++si) { assert(si < p->sn); cmScMeasSet_t* msp = p->set + si; msp->sp = sp; msp->bsli = cmScoreLocCount(scH); msp->esli = 0; msp->bsei = cmScoreEvtCount(scH); msp->esei = 0; msp->bli = cmInvalidIdx; msp->eli = cmInvalidIdx; for(j=0; jeleCnt; ++j) { msp->bsli = cmMin(msp->bsli,sp->eleArray[j]->locIdx); msp->esli = cmMax(msp->esli,sp->eleArray[j]->locIdx); msp->bsei = cmMin(msp->bsei,sp->eleArray[j]->index); msp->esei = cmMax(msp->esei,sp->eleArray[j]->index); } } } // initialize p->mp so that mp->loc[] is loaded - use dummy scWndN and midiN if((rc = cmScMatchInit(p->mp, scH, 11, 10 )) != cmOkRC ) return rc; // assign set[].bli and set[].eli // for each measurment set for(j=0; jsn; ++j) { cmScMeasSet_t* msp = p->set + j; // for each score location for(i=0; imp->locN; ++i) { // if this set starts on this score location if( msp->bli==cmInvalidIdx && msp->bsli==p->mp->loc[i].scLocIdx ) msp->bli = i; // store the index of the score location // if this set end on this score location if( msp->esli==p->mp->loc[i].scLocIdx ) msp->eli = i; // store the index of the score location } assert( msp->eli > msp->bli ); // track the longest set. maxScWndN = cmMax( maxScWndN, msp->eli - msp->bli + 1 ); } // setup the match if((rc = cmScMatchInit(p->mp, scH, 2*maxScWndN+1, 2*maxScWndN )) != cmOkRC ) return rc; // sort set[] on cmScMeasSet_t.esli qsort(p->set, p->sn, sizeof(cmScMeasSet_t), _cmScMeasSortFunc ); //_cmScMeasPrint(p); cmScMeasReset(p); return rc; } cmRC_t cmScMeasFinal( cmScMeas* p ) { return cmScMatchFinal(p->mp); } cmRC_t cmScMeasReset( cmScMeas* p ) { cmRC_t rc = cmOkRC; p->mii = 0; p->nsi = cmInvalidIdx; p->vsi = cmInvalidIdx; p->nsli = cmInvalidIdx; unsigned i; for(i=0; isn; ++i) { p->set[i].value = DBL_MAX; p->set[i].tempo = 0; p->set[i].match_cost = 0; } return rc; } typedef struct { unsigned scLocIdx; // score loc index double frac; // score based fraction of beat unsigned smpIdx; // time of assoc'd MIDI event unsigned cnt; // double val; // unsigned pitch; } _cmScMeasTimeEle_t; typedef struct { unsigned setN; // set length unsigned midiN; // count of MIDI events to match to score unsigned alignN; // count of score events in the alignment (<= setN) unsigned matchN; // count of positive matches double tempo; double value; } _cmScMeasResult_t; double _cmScMeasCalcTempo( const _cmScMeasTimeEle_t* b, unsigned bn, double srate ) { assert( bn >= 2 ); assert( b[bn-1].smpIdx != cmInvalidIdx ); assert( b[0].smpIdx != cmInvalidIdx ); double durSmpCnt = b[bn-1].smpIdx - b[0].smpIdx; double beats = 0; unsigned i; for(i=0; isp->eleCnt; _cmScMeasTimeEle_t* b = NULL; int bn = 0; bool tempoFl = false; unsigned matchN = 0; assert( an!=0); // alloc a 'score set' element array _cmScMeasTimeEle_t a[an]; // get the scLocIdx of each set element from the score for(i=0,j=0; isp->eleArray[i-1]->locIdx != sp->sp->eleArray[i]->locIdx ) { assert( sp->sp->eleArray[i]->locIdx != cmInvalidIdx ); a[j].scLocIdx = sp->sp->eleArray[i]->locIdx; a[j].frac = sp->sp->eleArray[i]->frac; a[j].smpIdx = cmInvalidIdx; a[j].cnt = 0; ++j; } an = j; // set the count of unique score locations (new length of a[]) // collect the 'smpIdx' for each MIDI event which matches a set element for(i=0; imp->loc[m[i].locIdx].scLocIdx == a[j].scLocIdx ) { a[j].smpIdx += m[i].smpIdx; a[j].cnt += 1; a[j].pitch = m[i].pitch; if( a[j].cnt == 1 ) matchN += 1; // only cnt one match per sc loc. break; } } // remove leading missing values for(i=0; i=0; --i,--bn) if( b[i].smpIdx != cmInvalidIdx ) break; // can't measure evenness against less than 2 values if( bn < 2 ) { return 0; } assert(b[0].smpIdx != cmInvalidIdx && b[bn-1].smpIdx != cmInvalidIdx); // calc avg. smpIdx, insert missing values, and convert b[].smpIdx to delta smp index for(i=0,j=0; i 1 ) b[i].smpIdx /= b[i].cnt; if( b[i].smpIdx == cmInvalidIdx ) ++j; // incr missing value count else { if( i > 0 ) { // fill in missing values ++j; unsigned d = (b[i].smpIdx - b[i-j].smpIdx)/j; for(k=0; ksetN = an; rp->midiN = mn; rp->alignN = bn; rp->matchN = matchN; rp->tempo = 0; rp->value = 0; // calculate tempo if( tempoFl ) rp->tempo = _cmScMeasCalcTempo(b,bn,p->srate); assert(bn<=cn); // TODO: this copy should be eliminated // copy to output for(i=0; isp->eleCnt; _cmScMeasTimeEle_t b[bn]; unsigned i; if((bn = _cmScMeasTimeAlign(p,sp,m,mn,b,bn,rp)) == 0 ) return DBL_MAX; // calc avg. delta time double d_avg = 0; for(i=0; ivalue = val; return val; } // return Tempo estimation in BPM double _cmScMeasTempo( cmScMeas* p, cmScMeasSet_t* sp, cmScMatchMidi_t* m, unsigned mn, _cmScMeasResult_t* rp ) { unsigned bn = sp->sp->eleCnt; _cmScMeasTimeEle_t b[bn]; if((bn= _cmScMeasTimeAlign(p,sp,m,mn,b,bn,rp)) == 0 ) return DBL_MAX; return rp->tempo; } double _cmScMeasDyn( cmScMeas* p, cmScMeasSet_t* sp, cmScMatchMidi_t* m, unsigned mn, _cmScMeasResult_t* rp ) { typedef struct { unsigned scEvtIdx; unsigned vel; double val; } ele_t; int i,j; int n = sp->sp->eleCnt; double vv = 0; unsigned vn = 0; unsigned matchN = 0; unsigned alignN = 0; assert( n!=0); ele_t a[n]; // get the scEvtIdx of each set element for(i=0; imp->scH, sp->sp->eleArray[i]->index ); assert( ep != NULL ); a[i].scEvtIdx = sp->sp->eleArray[i]->index; a[i].vel = 0; a[i].val = ep->dynVal; } // set the performed vel. of each note in the set // (if a note was not played it's a[].vel is left at 0) for(i=0; i 0 ) { unsigned mnv = 0; // lower bound for first dyn's category for(j=0; jdn; ++j) { if( mnv <= a[i].vel && a[i].vel < p->dynRef[j] ) { // accum. the diff. between the ref. and performed dyn. category vv += fabs(a[i].val - j); vn += 1; break; } mnv = p->dynRef[j]; } assert(jdn); } rp->setN = n; rp->midiN = mn; rp->alignN = alignN; rp->matchN = matchN; rp->tempo = 0; rp->value = vn == 0 ? DBL_MAX : vv/vn; return rp->value; } unsigned MEAS_MATCH_CNT = 0; void _cmScMeasPrintResult( cmScMeas* p, cmScMeasSet_t* sp, _cmScMeasResult_t* rp, unsigned bli, const cmScMatchMidi_t* mb ) { const char* label = ""; switch( sp->sp->varId ) { case kEvenVarScId: label = "even"; break; case kDynVarScId: label = "dyn"; break; case kTempoVarScId: label = "tempo"; break; } const cmChar_t* msg = ""; if( rp->value == DBL_MAX ) { msg = "Measure FAILED."; sp->value = 0; } printf("%i set:%i %s bsli:%i esli:%i [set:%i match:%i] cost:%f val:%f %s",MEAS_MATCH_CNT, p->nsi, label, sp->bsli, sp->esli, rp->setN, rp->matchN, p->mp->opt_cost, sp->value, msg); if( rp->tempo != 0 ) printf(" tempo:%f ",rp->tempo); printf("\n"); _cmScMatchPrintPath(p->mp, p->mp->p_opt, bli, mb ); } void _cmScMeasCalcVal( cmScMeas* p, cmScMeasSet_t* sp, int n_mii ) { unsigned mn = 0; int i,k = cmInvalidIdx; if( n_mii == 0 ) return; // Determine the count of MIDI notes to match to the set score // by searching from the MIDI note just recieved (midiBuf[n_mii] // back toward the beginning until a MIDI event which occurs just // prior to the set's begScLocIdx. for(i=n_mii; i>=0; --i) { if( p->midiBuf[i].locIdx != cmInvalidIdx ) { k = i; unsigned scLocIdx = p->mp->loc[ p->midiBuf[i].locIdx ].scLocIdx; if( scLocIdx < sp->bsli ) break; } } assert(k != cmInvalidIdx); mn = n_mii - k + 1; i = k; assert(i>=0); assert(mn>0); // Create a copy of the the MIDI buffer to prevent the // p->midiBuf[].locIdx from being overwritten by cmScMatchDoSync(). cmScMatchMidi_t mb[ mn ]; unsigned j; for(j=0; jmidiBuf[i+j]; mb[j].locIdx = cmInvalidIdx; } // In general the first and last MIDI event should be assigned // to a score location - it's possible however that no MIDI // event's prior to the one at p->midiBuf[n_mii] were assigned. assert( (i==0 || p->midiBuf[i].locIdx!=cmInvalidIdx) && p->midiBuf[i+mn-1].locIdx != cmInvalidIdx); unsigned l0i = cmMin(p->midiBuf[i].locIdx,p->midiBuf[i+mn-1].locIdx); unsigned l1i = cmMax(p->midiBuf[i].locIdx,p->midiBuf[i+mn-1].locIdx); unsigned bli = l0i; unsigned ln = l1i - bli + 1; double min_cost = DBL_MAX; _cmScMeasResult_t r; memset(&r,0,sizeof(r)); // match MIDI to score if( cmScMatchExec(p->mp, bli, ln, mb, mn, min_cost ) != cmOkRC ) return; // sync the score and MIDI based on the match information if( cmScMatchDoSync(p->mp, bli, mb, mn, NULL ) == cmInvalidIdx ) return; if( p->mp->opt_cost != DBL_MAX ) sp->match_cost = p->mp->opt_cost / sp->sp->eleCnt; switch( sp->sp->varId ) { case kEvenVarScId: sp->value = _cmScMeasEven(p, sp, mb, mn, &r ); break; case kDynVarScId: sp->value = _cmScMeasDyn(p, sp, mb, mn, &r ); break; case kTempoVarScId: sp->value = _cmScMeasTempo(p, sp, mb, mn, &r ); break; default: { assert(0); } } sp->tempo = r.tempo; // print the result //_cmScMeasPrintResult(p, sp, &r, bli, mb ); MEAS_MATCH_CNT++; } cmRC_t cmScMeasExec( cmScMeas* p, unsigned mni, unsigned locIdx, unsigned scEvtIdx, unsigned flags, unsigned smpIdx, unsigned pitch, unsigned vel ) { cmRC_t rc = cmOkRC; // if the midi buffer is full if( p->mii >= p->mn ) return cmCtxRtCondition( &p->obj, cmEofRC, "The MIDI buffer is full."); int n_mii = cmInvalidIdx; // locate the MIDI event assoc'd with 'mni' ... if( p->mii>0 && mni <= p->midiBuf[p->mii-1].mni ) { if( locIdx != cmInvalidIdx ) { for(n_mii=p->mii-1; n_mii>=0; --n_mii) if( p->midiBuf[n_mii].mni == mni ) break; if( n_mii<0 ) n_mii = cmInvalidIdx; } } else // ... or push a new record onto p->midiBuf[] { n_mii = p->mii; ++p->mii; } // store the MIDI event p->midiBuf[n_mii].mni = mni; p->midiBuf[n_mii].locIdx = locIdx; p->midiBuf[n_mii].scEvtIdx = scEvtIdx; p->midiBuf[n_mii].smpIdx = smpIdx; p->midiBuf[n_mii].pitch = pitch; p->midiBuf[n_mii].vel = vel; // setting vsi=nsi and vsli=nsli will indicate to the calling // program that no new sets are ready. p->vsi = p->nsi; p->vsli = p->nsli; if( locIdx == cmInvalidIdx ) return cmOkRC; // unsigned scLocIdx = p->mp->loc[ locIdx ].scLocIdx; unsigned maxScLocIdx = cmScoreLocCount(p->mp->scH)-1; // if this cmScMeas object has not yet synchronized to the cmScMatcher // (if p->nsli is not valid) if( p->nsli == cmInvalidIdx ) { unsigned i; for(i=0; isn; ++i) if( p->set[i].esli+1 == scLocIdx ) { p->nsli = scLocIdx; p->nsi = i; break; } if(i==p->sn) return rc; } p->vsi = p->nsi; p->vsli = p->nsli; // for each cmScore location between p->nsli and scLocIdx for(; p->nsli<=scLocIdx && p->nsi < p->sn; ++p->nsli) { // if this score location index (p->nsli) is one score location // ahead of the next sets ending location. while( cmMin(maxScLocIdx,p->set[p->nsi].esli+1) == p->nsli ) { // calculate the value assoc'd with p->set[p->nsi] _cmScMeasCalcVal(p, p->set + p->nsi, n_mii ); // advance the set index ++p->nsi; } } return rc; } //======================================================================================================================= cmRC_t cmScAlignScanToTimeLineEvent( cmScMatcher* p, cmTlH_t tlH, cmTlObj_t* top, unsigned endSmpIdx ) { assert( top != NULL ); cmTlMidiEvt_t* mep = NULL; cmRC_t rc = cmOkRC; // as long as more MIDI events are available get the next MIDI msg while( rc==cmOkRC && (mep = cmTlNextMidiEvtObjPtr(tlH, top, top->seqId )) != NULL ) { top = &mep->obj; // if the msg falls after the end of the marker then we are through if( mep->obj.seqSmpIdx != cmInvalidIdx && mep->obj.seqSmpIdx > endSmpIdx ) break; // if the time line MIDI msg a note-on if( (mep->msg->status&0xf0) == kNoteOnMdId ) { rc = cmScMatcherExec(p, mep->obj.seqSmpIdx, mep->msg->uid, mep->msg->status, mep->msg->u.chMsgPtr->d0, mep->msg->u.chMsgPtr->d1, NULL ); switch( rc ) { case cmOkRC: // continue processing MIDI events break; case cmEofRC: // end of the score was encountered break; case cmInvalidArgRC: // p->eli was not set correctly break; case cmSubSysFailRC: // scan resync failed break; default: { assert(0); } } } } if( rc == cmEofRC ) rc = cmOkRC; return rc; } // This callback connects/feeds the cmScMeas object from the cmScMatcher object. // (See _cmScMatcherStoreResult().) void cmScMatcherCb( cmScMatcher* p, void* arg, cmScMatcherResult_t* rp ) { cmScMeas* mp = (cmScMeas*)arg; cmScMeasExec(mp, rp->mni, rp->locIdx, rp->scEvtIdx, rp->flags, rp->smpIdx, rp->pitch, rp->vel ); } void cmScAlignScanMarkers( cmRpt_t* rpt, cmTlH_t tlH, cmScH_t scH ) { unsigned i; double srate = cmTimeLineSampleRate(tlH); unsigned midiN = 7; unsigned scWndN = 10; unsigned markN = 291; unsigned dynRefArray[] = { 14, 28, 42, 56, 71, 85, 99, 113,128 }; unsigned dynRefCnt = sizeof(dynRefArray)/sizeof(dynRefArray[0]); cmCtx* ctx = cmCtxAlloc(NULL, rpt, cmLHeapNullHandle, cmSymTblNullHandle ); cmScMeas* mp = cmScMeasAlloc(ctx,NULL,scH,srate,dynRefArray,dynRefCnt); cmScMatcher* p = cmScMatcherAlloc(ctx,NULL,srate,scH,scWndN,midiN,cmScMatcherCb,mp); double scoreThresh = 0.5; unsigned candCnt = 0; unsigned initFailCnt = 0; unsigned otherFailCnt = 0; unsigned scoreFailCnt = 0; bool printFl = false; unsigned markCharCnt = 31; cmChar_t markText[ markCharCnt+1 ]; cmTimeSpec_t t0,t1; cmTimeGet(&t0); // for each marker for(i=0; itext) ) { if( printFl ) printf("The marker '%s' is being skipped because it has no text.\n\n",markText); continue; } printf("=================== MARKER:%s ===================\n",markText); cmScMatcherReset(p,0); // reset the score follower to the beginnig of the score cmScMeasReset(mp); ++candCnt; // scan to the beginning of the marker cmRC_t rc = cmScAlignScanToTimeLineEvent(p,tlH,&tlmp->obj,tlmp->obj.seqSmpIdx+tlmp->obj.durSmpCnt); bool pfl = true; if( rc != cmOkRC || p->begSyncLocIdx==cmInvalidIdx) { bool fl = printFl; printFl = true; // if a no alignment was found if( p->begSyncLocIdx == cmInvalidIdx ) rc = cmInvalidArgRC; if( p->mni == 0 ) { if( printFl ) printf("mark:%i midi:%i Not enough MIDI notes to fill the scan buffer.\n",i,p->mni); pfl = false; } else { switch(rc) { case cmInvalidArgRC: if( printFl ) printf("mark:%i INITIAL SYNC FAIL\n",i); ++initFailCnt; pfl = false; break; case cmSubSysFailRC: if( printFl ) printf("mark:%i SCAN RESYNC FAIL\n",i); ++otherFailCnt; break; default: if( printFl ) printf("mark:%i UNKNOWN FAIL\n",i); ++otherFailCnt; } } printFl = fl; } if( pfl ) { double fmeas = cmScMatcherFMeas(p); if( printFl ) printf("mark:%i midi:%i loc:%i bar:%i cost:%f f-meas:%f text:%s\n",i,p->mni,p->begSyncLocIdx,p->mp->loc[p->begSyncLocIdx].barNumb,p->s_opt,fmeas,tlmp->text); if( fmeas < scoreThresh ) ++scoreFailCnt; } //print score and match for entire marker //cmScMatcherPrint(p); // ONLY USE ONE MARKER DURING TESTING // break; if( printFl ) printf("\n"); } printf("cand:%i fail:%i - init:%i score:%i other:%i\n\n",candCnt,initFailCnt+scoreFailCnt+otherFailCnt,initFailCnt,scoreFailCnt,otherFailCnt); cmTimeGet(&t1); printf("elapsed:%f\n", (double)cmTimeElapsedMicros(&t0,&t1)/1000000.0 ); cmScMatcherFree(&p); cmScMeasFree(&mp); cmCtxFree(&ctx); } //======================================================================================================================= cmScModulator* cmScModulatorAlloc( cmCtx* c, cmScModulator* p, cmCtx_t* ctx, cmSymTblH_t stH, double srate, unsigned samplesPerCycle, const cmChar_t* fn, const cmChar_t* modLabel, cmScModCb_t cbFunc, void* cbArg ) { cmScModulator* op = cmObjAlloc(cmScModulator,c,p); if( ctx != NULL ) if( cmScModulatorInit(op,ctx,stH,srate,samplesPerCycle,fn,modLabel,cbFunc,cbArg) != cmOkRC ) cmScModulatorFree(&op); return op; } cmRC_t cmScModulatorFree( cmScModulator** pp ) { cmRC_t rc = cmOkRC; if( pp==NULL || *pp==NULL ) return rc; cmScModulator* p = *pp; if((rc = cmScModulatorFinal(p)) != cmOkRC ) return rc; //cmMemFree(p->earray); //cmMemFree(p->xlist); cmObjFree(pp); return rc; } typedef struct { unsigned typeId; unsigned minArgCnt; const cmChar_t* label; } _cmScModTypeMap_t; _cmScModTypeMap_t _cmScModTypeArray[] = { { kDeclModTId, 0, "decl" }, { kSetModTId, 1, "set" }, { kLineModTId, 2, "line" }, { kSetLineModTId, 3, "sline" }, { kPostModTId, 4, "post" }, { kExecModTId, 5, "exec" }, { kInputModTId, 6, "input" }, { kCrossModTId, 7, "cross" }, { kInvalidModTId, 0, ""} }; const _cmScModTypeMap_t* _cmScModTypeLabelToMap( const cmChar_t* label ) { unsigned i; for(i=0; _cmScModTypeArray[i].typeId!=kInvalidModTId; ++i) if( strcmp(_cmScModTypeArray[i].label,label) == 0 ) return _cmScModTypeArray + i; return NULL; } const _cmScModTypeMap_t* _cmScModTypeIdToMap( unsigned typeId ) { unsigned i; for(i=0; _cmScModTypeArray[i].typeId!=kInvalidModTId; ++i) if( _cmScModTypeArray[i].typeId == typeId ) return _cmScModTypeArray + i; return NULL; } cmScModVar_t* _cmScModSymToVar( cmScModulator* p, unsigned varSymId ) { cmScModVar_t* vp = p->vlist; for(; vp!=NULL; vp=vp->vlink) if( varSymId == vp->varSymId ) return vp; return NULL; } cmScModVar_t* _cmScModulatorInsertVar( cmScModulator* p, unsigned varSymId, unsigned flags ) { cmScModVar_t* vp = _cmScModSymToVar(p,varSymId); // if the specified variable was not found then create one if( vp == NULL ) { vp = cmMemAllocZ(cmScModVar_t,1); vp->varSymId = varSymId; vp->varId = cmInvalidId; vp->vlink = p->vlist; p->vlist = vp; } vp->flags = flags; vp->value = DBL_MAX; vp->min = DBL_MAX; vp->max = DBL_MAX; vp->rate = DBL_MAX; vp->phase = 0; vp->entry = NULL; vp->alink = NULL; return vp; } cmScModEntry_t* _cmScModulatorInsertEntry(cmScModulator* p, cmScModEntryGroup_t* g, unsigned idx, unsigned scLocIdx, unsigned varSymId, unsigned typeId, unsigned paramCnt ) { assert( idx < g->en ); g->earray[idx].scLocIdx = scLocIdx; g->earray[idx].typeId = typeId; return g->earray + idx; } /* { [ { loc:123, mod:modlabel, var:varlabel, param:[ ] } ] } */ // Parameter values are found as values of the 'data','min' or 'max' fields. // A parameter value may be either a symbol identifier (mapped to a variable) // or a literal number. This function determines which form the paramter // value takes and parses it accordingly. cmRC_t _cmScModulatorParseParam( cmScModulator* p, cmSymTblH_t stH, cmJsonNode_t* np, cmScModParam_t* pp ) { cmRC_t rc = cmOkRC; switch( np->typeId ) { case kIntTId: case kRealTId: if( cmJsonRealValue(np, &pp->val ) != kOkJsRC ) { rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Error parsing in Modulator literal value." ); goto errLabel; } pp->pid = kLiteralModPId; break; case kStringTId: { const cmChar_t* label = NULL; if( cmJsonStringValue(np, &label) != kOkJsRC ) { rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Error parsing in Modulator symbol label." ); goto errLabel; } pp->symId = cmSymTblRegisterSymbol(stH,label); pp->pid = kSymbolModPId; } break; default: rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Parameter value is not a number or identifier." ); goto errLabel; break; } errLabel: return rc; } // If the requested parameter has a value then return it in *valPtr. // If it does not then do nothing. This function applies scaling to RHS values. cmRC_t _cmScModGetParam( cmScModulator* p, const cmScModParam_t* pp, double* valPtr ) { cmRC_t rc = cmOkRC; switch( pp->pid ) { case kInvalidModPId: rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "An invalid parameter was encountered."); goto errLabel; break; case kLiteralModPId: *valPtr = pp->val; break; case kSymbolModPId: { cmScModVar_t* vp; // get a pointer to the parameter variable if((vp = _cmScModSymToVar(p, pp->symId )) == NULL ) { rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Variable '%s' not found.",cmSymTblLabel(p->stH,pp->symId)); goto errLabel; } // if this is not a 'calculated' paramter then scale it here. if( cmIsFlag(vp->flags,kCalcModFl ) && vp->min!=DBL_MAX && vp->max!=DBL_MAX ) *valPtr = (vp->value - vp->min)/(vp->max-vp->min); else *valPtr = vp->value; } break; default: { assert(0); } } errLabel: return rc; } void _cmScModDumpParam( cmScModulator* p, const cmChar_t* label, const cmScModParam_t* pp ) { printf("%s: ",label); switch( pp->pid ) { case kInvalidModPId: printf(""); break; case kLiteralModPId: if( pp->val == DBL_MAX ) printf(" "); else printf("%f ",pp->val); break; case kSymbolModPId: printf("%s ",cmSymTblLabel(p->stH,pp->symId)); break; default: { assert(0); } } } void _cmScModDumpVal( cmChar_t* label, double val ) { printf("%s:",label); if( val == DBL_MAX ) printf(" " ); else printf("%f ",val); } void _cmScModDumpVar( cmScModulator* p, const cmScModVar_t* vp ) { printf("%7s %3i fl:0x%x entry:%p alink:%p %s",cmSymTblLabel(p->stH,vp->varSymId),vp->varId,vp->flags,vp->entry,vp->alink, cmIsFlag(vp->flags,kInputModFl)?"input":""); _cmScModDumpVal("val",vp->value); _cmScModDumpVal("min",vp->min); _cmScModDumpVal("max",vp->max); _cmScModDumpVal("rate",vp->rate); _cmScModDumpVal("v0",vp->v0); } void _cmScModDumpEntry( cmScModulator* p, const cmScModEntry_t* ep) { const _cmScModTypeMap_t* tm = _cmScModTypeIdToMap( ep->typeId ); printf("%10i ",ep->scLocIdx); printf("%5s %7s", tm==NULL ? "invld" : tm->label, cmSymTblLabel(p->stH,ep->varPtr->varSymId)); _cmScModDumpParam(p," beg", &ep->beg); _cmScModDumpParam(p," end", &ep->end); _cmScModDumpParam(p," min", &ep->min); _cmScModDumpParam(p," max", &ep->max); _cmScModDumpParam(p," rate",&ep->rate); printf("\n"); } cmRC_t _cmScModParseEntryGroup( cmScModulator* p, cmCtx_t* ctx, cmJsonH_t jsH, cmSymTblH_t stH, cmJsonNode_t* jnp, cmScModEntryGroup_t* g, const cmChar_t* fn ) { cmRC_t rc = cmOkRC; unsigned prvScLocIdx = cmInvalidIdx; const cmChar_t* prvVarLabel = ""; const cmChar_t* prvTypeLabel = NULL; unsigned i; // allocate the entry array unsigned entryCnt = cmJsonChildCount(jnp); g->earray = cmMemResizeZ(cmScModEntry_t,g->earray,entryCnt); g->en = entryCnt; for(i=0; iobj, cmInvalidArgRC, "Error:%s on record at index %i in file:%s",errLabelPtr,i,cmStringNullGuard(fn) ); else rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Synax error in Modulator record at index %i in file:%s",i,cmStringNullGuard(fn) ); goto errLabel; } // if the score location was not given use the previous score location if( scLocIdx == cmInvalidIdx ) scLocIdx = prvScLocIdx; else prvScLocIdx = scLocIdx; // if the var label was not given use the previous one if( varLabel == NULL ) varLabel = prvVarLabel; else prvVarLabel = varLabel; if( varLabel == NULL ) { rc = cmCtxRtCondition(&p->obj, cmInvalidArgRC, "No 'var' label has been set in mod file '%s'.",cmStringNullGuard(fn)); goto errLabel; } // if the type label was not given use the previous one if( typeLabel == NULL ) typeLabel = prvTypeLabel; else prvTypeLabel = typeLabel; if( typeLabel == NULL ) { rc = cmCtxRtCondition(&p->obj, cmInvalidArgRC, "No 'type' label has been set in mod file '%s'.",cmStringNullGuard(fn)); goto errLabel; } // validate the entry type label if((map = _cmScModTypeLabelToMap(typeLabel)) == NULL ) { rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Unknown entry type '%s' in Modulator record at index %i in file:%s",cmStringNullGuard(typeLabel),i,cmStringNullGuard(fn) ); goto errLabel; } unsigned varSymId = cmSymTblRegisterSymbol(stH,varLabel); // get the count of the elmenets in the data array unsigned paramCnt = cmJsonChildCount(onp); // fill the entry record and find or create the target var cmScModEntry_t* ep = _cmScModulatorInsertEntry(p,g,i,scLocIdx,varSymId,map->typeId,paramCnt); typedef struct { const cmChar_t* label; cmScModParam_t* param; } map_t; // parse the var and parameter records map_t mapArray[] = { { "min", &ep->min }, { "max", &ep->max }, { "rate",&ep->rate }, { "val", &ep->beg }, { "end", &ep->end }, { "dur", &ep->dur }, { "arg", &ep->arg }, { NULL, NULL } }; unsigned j=0; for(j=0; mapArray[j].param!=NULL; ++j) if((dnp = cmJsonFindValue(jsH,mapArray[j].label, onp, kInvalidTId )) != NULL ) if((rc = _cmScModulatorParseParam(p,stH,dnp,mapArray[j].param)) != cmOkRC ) goto errLabel; // create the variable associated with this entry ep->varPtr = _cmScModulatorInsertVar(p,varSymId, ep->typeId==kInputModTId ? kInputModFl : 0); // set the variable id value if( ep->varPtr->varId == cmInvalidIdx ) { if( ep->typeId != kInputModTId ) ep->varPtr->varId = p->outVarCnt++; else ep->varPtr->varId = p->inVarCnt++; } } errLabel: return rc; } cmRC_t _cmScModulatorParse( cmScModulator* p, cmCtx_t* ctx, cmSymTblH_t stH, const cmChar_t* fn ) { cmRC_t rc = cmOkRC; cmJsonNode_t* jnp = NULL; cmJsonH_t jsH = cmJsonNullHandle; unsigned i = cmInvalidIdx; cmScModEntryGroup_t* g0 = NULL; cmScModEntryGroup_t* g1 = p->glist; // read the JSON file if( cmJsonInitializeFromFile(&jsH, fn, ctx ) != kOkJsRC ) return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "JSON file parse failed on the modulator file: %s.",cmStringNullGuard(fn) ); jnp = cmJsonRoot(jsH); unsigned groupCnt = cmJsonChildCount(jnp); // for each entry group for(i=0; iglist = g1; else g0->link = g1; } // get the entry group pair node cmJsonNode_t* gnp = cmJsonMemberAtIndex(jnp,i); if( gnp == NULL || !cmJsonIsPair(gnp) ) { rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Invalid entry group at index %i in score modulator file:%s",i,cmStringNullGuard(fn) ); goto errLabel; } // store the group label g1->symId = cmSymTblRegisterSymbol(stH,cmJsonPairLabel(gnp)); // get and validate the group array value if( (gnp = cmJsonPairValue(gnp))==NULL || cmJsonIsArray(gnp)==false ) return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Group entry '%s' does not contain an array of entries in file:%s",cmStringNullGuard(cmJsonPairLabel(gnp)),cmStringNullGuard(fn) ); // parse the entry group if((rc = _cmScModParseEntryGroup( p, ctx, jsH, stH, gnp, g1, fn )) != cmOkRC ) { rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Syntax error in entry group at index %i in score modulator file:%s",i,cmStringNullGuard(fn) ); goto errLabel; } g0 = g1; g1 = g1->link; } errLabel: // release the JSON tree if( cmJsonIsValid(jsH) ) cmJsonFinalize(&jsH); return rc; } cmRC_t _cmScModulatorReset( cmScModulator* p, cmCtx_t* ctx, unsigned scLocIdx, unsigned entryGroupSymId ) { cmRC_t rc = cmOkRC; p->alist = NULL; p->elist = NULL; p->nei = 0; p->outVarCnt = 0; p->inVarCnt = 0; // reload the file if((rc = _cmScModulatorParse(p,ctx,p->stH,p->fn)) != cmOkRC ) goto errLabel; // select the entry group to execute if( entryGroupSymId != cmInvalidId ) { cmScModEntryGroup_t* g = p->glist; for(; g!=NULL; g=g->link) if( g->symId == entryGroupSymId ) { cmCtxPrint(p->obj.ctx,"%s selected.\n",cmSymTblLabel(p->stH,entryGroupSymId)); p->xlist = g; break; } } // if an active entry group has not been set - set it to the first entry group if( p->xlist == NULL ) { if( entryGroupSymId != cmInvalidId ) cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The modulator entry group '%s' was not found. The active entry group was set to the first group in the file.",cmSymTblLabel(p->stH,entryGroupSymId) ); p->xlist = p->glist; } // clear the active flag on all variables cmScModVar_t* vp = p->vlist; for(; vp!=NULL; vp=vp->vlink) { vp->flags = cmClrFlag(vp->flags,kActiveModFl); vp->alink = NULL; } errLabel: return rc; } cmRC_t cmScModulatorInit( cmScModulator* p, cmCtx_t* ctx, cmSymTblH_t stH, double srate, unsigned samplesPerCycle, const cmChar_t* fn, const cmChar_t* modLabel, cmScModCb_t cbFunc, void* cbArg ) { cmRC_t rc; if((rc = cmScModulatorFinal(p)) != cmOkRC ) return rc; p->fn = cmMemAllocStr(fn); p->stH = stH; p->modSymId = cmSymTblRegisterSymbol(stH,modLabel); p->cbFunc = cbFunc; p->cbArg = cbArg; p->samplesPerCycle = samplesPerCycle; p->srate = srate; if( rc != cmOkRC ) cmScModulatorFinal(p); else _cmScModulatorReset(p,ctx,0,cmInvalidId); return rc; } cmRC_t cmScModulatorFinal( cmScModulator* p ) { cmMemFree(p->fn); // release each var record cmScModVar_t* vp = p->vlist; while( vp!=NULL ) { cmScModVar_t* np = vp->vlink; cmMemFree(vp); vp=np; } // release each group record cmScModEntryGroup_t* g = p->glist; while( g != NULL ) { cmScModEntryGroup_t* g0 = g->link; cmMemFree(g->earray); cmMemFree(g); g = g0; } p->glist = NULL; return cmOkRC; } unsigned cmScModulatorOutVarCount( cmScModulator* p ) { return p->outVarCnt; } cmScModVar_t* cmScModulatorOutVar( cmScModulator* p, unsigned idx ) { unsigned i; cmScModEntryGroup_t* g = p->glist; for(; g!=NULL; g=g->link) for(i=0; ien; ++i) if( cmIsNotFlag(g->earray[i].varPtr->flags,kInputModFl) && g->earray[i].varPtr->varId == idx ) return g->earray[i].varPtr; return NULL; } unsigned cmScModulatorInVarCount( cmScModulator* p ) { return p->inVarCnt; } cmScModVar_t* cmScModulatorInVar( cmScModulator* p, unsigned idx ) { unsigned i; cmScModEntryGroup_t* g = p->glist; for(; g!=NULL; g=g->link) for(i=0; ien; ++i) if( cmIsFlag(g->earray[i].varPtr->flags,kInputModFl) && g->earray[i].varPtr->varId == idx ) return g->earray[i].varPtr; return NULL; } cmScModVar_t* _cmScModSetValuePrefix( cmScModulator* p, unsigned varSymId ) { cmScModVar_t* vp; // if the var does not exist .... if((vp = _cmScModSymToVar(p, varSymId )) == NULL ) { // ... then create it vp = _cmScModulatorInsertVar(p,varSymId,kCalcModFl); assert(vp!=NULL); } return vp; } cmRC_t cmScModulatorSetValueMinMax( cmScModulator* p, unsigned varSymId, double value, double min, double max ) { cmScModVar_t* vp = _cmScModSetValuePrefix(p, varSymId ); assert( min <= max); vp->min = min; vp->max = max; vp->value = value; return cmOkRC; } cmRC_t cmScModulatorSetValue( cmScModulator* p, unsigned varSymId, double value ) { cmScModVar_t* vp = _cmScModSetValuePrefix(p, varSymId ); vp->value = value; return cmOkRC; } cmRC_t cmScModulatorReset( cmScModulator* p, cmCtx_t* ctx, unsigned scLocIdx, unsigned entryGroupSymId ) { _cmScModulatorReset(p,ctx,scLocIdx,entryGroupSymId); return cmScModulatorExec(p,scLocIdx); } void _cmScModUnlinkActive( cmScModulator* p, cmScModVar_t* vp, cmScModVar_t* pp ) { // if vp is the first link on the chain if( vp == p->alist ) p->alist = vp->alink; // if vp is the last link on the chain if( vp == p->elist ) p->elist = pp; if( pp != NULL ) pp->alink = vp->alink; vp->flags = cmClrFlag(vp->flags,kActiveModFl); vp->alink = NULL; vp->entry = NULL; } cmRC_t _cmScModActivateEntries( cmScModulator* p, cmScModEntry_t* earray, unsigned* idxRef, unsigned cnt, unsigned scLocIdx ); cmRC_t _cmScModActivateGroup( cmScModulator* p, cmScModEntry_t* ep ) { cmScModEntryGroup_t* g = p->glist; for(; g!=NULL; g=g->link) if( g->symId == ep->beg.symId ) { unsigned idx = 0; printf("Activating:%s\n",cmSymTblLabel(p->stH,ep->beg.symId)); return _cmScModActivateEntries( p, g->earray, &idx, g->en, ep->beg.symId ); } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Entry group '%s' not found.",cmSymTblLabel(p->stH,ep->beg.symId)); } cmRC_t _cmScModGetCrossParam( cmScModulator* p, cmScModEntry_t* ep, cmScModParam_t* pp, const cmChar_t* label, double* valRef ) { cmRC_t rc; if((rc = _cmScModGetParam(p, pp, valRef )) != cmOkRC ) rc = cmCtxRtCondition( &p->obj, cmInvalidArgRC, "Invalid %s parameter for cross variable:%s",label,cmSymTblLabel(p->stH,ep->varPtr->varSymId) ); return rc; } cmRC_t _cmScModExecCross( cmScModulator* p, cmScModEntry_t* ep ) { cmRC_t rc = cmOkRC; double x = 0.0; double y0 = 0.0, y1 = 0.0; if((rc = _cmScModGetCrossParam(p, ep, &ep->arg, "src var", &x )) != cmOkRC ) return rc; if((rc = _cmScModGetCrossParam(p, ep, &ep->min, "dst min", &y0 )) != cmOkRC ) return rc; if((rc = _cmScModGetCrossParam(p, ep, &ep->max, "dst max", &y1 )) != cmOkRC ) return rc; //printf("%s x:%f y0:%f y1:%f\n",__FUNCTION__,x,y0,y1); ep->varPtr->value = y0 + x * (y1-y0); return rc; } // Type specific variable activation - cmRC_t _cmScModActivate(cmScModulator* p, cmScModEntry_t* ep ) { cmRC_t rc = cmOkRC; cmScModVar_t* vp = ep->varPtr; // optionally update the min/max/rate values in the target var if( ep->min.pid != kInvalidModPId ) if((rc = _cmScModGetParam(p,&ep->min,&vp->min)) != cmOkRC ) goto errLabel; if( ep->max.pid != kInvalidModPId ) if((rc = _cmScModGetParam(p,&ep->max,&vp->max)) != cmOkRC ) goto errLabel; if( ep->rate.pid != kInvalidModPId ) if((rc = _cmScModGetParam(p,&ep->rate,&vp->rate)) != cmOkRC ) goto errLabel; switch( ep->typeId ) { case kDeclModTId: case kSetModTId: break; case kLineModTId: vp->v0 = vp->value; vp->phase = 0; break; case kSetLineModTId: _cmScModGetParam(p,&ep->beg,&vp->value); // starting value vp->v0 = vp->value; // set initial value vp->phase = 0; // reset phase break; case kPostModTId: p->postFl = vp->value; break; case kExecModTId: rc = _cmScModActivateGroup(p,ep); break; case kInputModTId: break; case kCrossModTId: break; default: { assert(0); } } errLabel: return rc; } // Callback the application with a new variable value. cmRC_t _cmScModExecSendValue( cmScModulator* p, cmScModVar_t* vp ) { cmRC_t rc = cmOkRC; bool sendFl = true; double v = vp->value; // scale the output value - this is equiv to scaling the LHS if( cmIsFlag(vp->flags,kCalcModFl) && vp->min!=DBL_MAX && vp->max!=DBL_MAX ) v = vp->min + v * (vp->max - vp->min); // if an output rate throttle is in effect .... if( vp->rate!=DBL_MAX && vp->phase!=0 ) sendFl = remainder(vp->phase*p->samplesPerCycle, p->srate*vp->rate/1000 ) < p->samplesPerCycle; if(sendFl) p->cbFunc(p->cbArg,vp->varSymId,v,p->postFl); return rc; } // Execute a variable. // Return true if vp should be deactivated otherwise return false. bool _cmScModExec( cmScModulator* p, cmScModVar_t* vp ) { cmRC_t rc = cmOkRC; bool fl = false; bool sendFl = true; switch( vp->entry->typeId ) { case kDeclModTId: sendFl = false; fl = true; break; case kSetModTId: { // Get a new value for the variable *vp. if((rc = _cmScModGetParam(p,&vp->entry->beg,&vp->value)) != cmOkRC ) goto errLabel; vp->phase = 0; // force the value to be sent fl = true; } break; case kSetLineModTId: case kLineModTId: { double v1=0, td=0; // get the target value if((rc = _cmScModGetParam(p,&vp->entry->end,&v1)) != cmOkRC) goto errLabel; // get the time duration if((rc = _cmScModGetParam(p,&vp->entry->dur,&td)) != cmOkRC) goto errLabel; // update the value of the var *vp double v = vp->v0 + (v1-vp->v0) * (vp->phase * p->samplesPerCycle) / (p->srate * td); if((fl = (vp->value <= v1 && v >= v1) || (vp->value >= v1 && v <= v1 )) == true ) v = v1; vp->value = v; } break; case kPostModTId: sendFl = false; break; case kExecModTId: sendFl = false; break; case kInputModTId: sendFl = false; break; case kCrossModTId: _cmScModExecCross(p,vp->entry); vp->phase = 0; // force the value to be sent fl = true; break; default: { assert(0); } } // notify the application that a new variable value has been generated if(sendFl) { rc = _cmScModExecSendValue(p,vp); // increment the phase - after send because send notices when phase is zero vp->phase += 1; } errLabel: if( rc != cmOkRC ) fl = true; return fl; } // Execute the entries in xparray[] begining with entry xparray[idx] and continuing until: // 1) cnt - idx entries have been executed // 2) an entry is located whose scLocIdx != scLocIdx and also not -1 cmRC_t _cmScModActivateEntries( cmScModulator* p, cmScModEntry_t* earray, unsigned* idxRef, unsigned cnt, unsigned scLocIdx ) { assert( idxRef != NULL ); cmRC_t trc; cmRC_t rc = cmOkRC; unsigned idx = *idxRef; // trigger entries that have expired since the last call to this function for(; idxvarPtr->flags,kActiveModFl) == false ) { // ... then append it to the end of the active list ... ep->varPtr->flags |= kActiveModFl; if( p->elist == NULL ) p->elist = ep->varPtr; else { p->elist->alink = ep->varPtr; p->elist = ep->varPtr; } p->elist->alink = NULL; if( p->alist == NULL ) p->alist = ep->varPtr; } // do type specific activation if((trc = _cmScModActivate(p,ep)) != cmOkRC ) rc = trc; ep->varPtr->entry = ep; } *idxRef = idx; return rc; } cmRC_t cmScModulatorExec( cmScModulator* p, unsigned scLocIdx ) { cmRC_t rc = cmOkRC; rc = _cmScModActivateEntries(p, p->xlist->earray, &p->nei, p->xlist->en, scLocIdx ); // Update the active variables cmScModVar_t* pp = NULL; cmScModVar_t* vp = p->alist; for(; vp!=NULL; vp=vp->alink) { if( _cmScModExec(p,vp) ) _cmScModUnlinkActive(p,vp,pp); else pp = vp; } return rc; } cmRC_t cmScModulatorDump( cmScModulator* p ) { cmRC_t rc = cmOkRC; printf("MOD:\n"); printf("nei:%i alist:%p outVarCnt:%i inVarCnt:%i\n",p->nei,p->alist,p->outVarCnt,p->inVarCnt); printf("ENTRIES:\n"); cmScModEntryGroup_t* g = p->glist; for(; g!=NULL; g=g->link) { printf("%s\n",cmSymTblLabel(p->stH,g->symId)); unsigned i; for(i=0; ien; ++i) { cmScModEntry_t* ep = g->earray + i; printf("%3i ",i); _cmScModDumpEntry(p,ep); } } printf("VARIABLES\n"); cmScModVar_t* vp = p->vlist; for(; vp!=NULL; vp=vp->vlink) { _cmScModDumpVar(p,vp); printf("\n"); } return rc; } //======================================================================================================================= cmRecdPlay* cmRecdPlayAlloc( cmCtx* c, cmRecdPlay* p, double srate, unsigned fragCnt, unsigned chCnt, double initFragSecs, double maxLaSecs, double curLaSecs ) { cmRecdPlay* op = cmObjAlloc(cmRecdPlay,c,p); if( cmRecdPlayInit(op,srate,fragCnt,chCnt,initFragSecs,maxLaSecs,curLaSecs) != cmOkRC ) cmRecdPlayFree(&op); return op; } cmRC_t cmRecdPlayFree( cmRecdPlay** pp ) { cmRC_t rc = cmOkRC; if( pp==NULL || *pp==NULL ) return rc; cmRecdPlay* p = *pp; if((rc = cmRecdPlayFinal(p)) != cmOkRC ) return rc; cmObjFree(pp); return rc; } cmRC_t cmRecdPlayInit( cmRecdPlay* p, double srate, unsigned fragCnt, unsigned chCnt, double initFragSecs, double maxLaSecs, double curLaSecs ) { cmRC_t rc; unsigned i; if( curLaSecs > maxLaSecs ) return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The initial look-ahead time %f is greater than the maximum look-ahead time %f.",curLaSecs,maxLaSecs); if((rc = cmRecdPlayFinal(p)) != cmOkRC ) return rc; if( chCnt == 0 ) return cmOkRC; p->frags = cmMemAllocZ(cmRecdPlayFrag,fragCnt); p->fragCnt = fragCnt; p->srate = srate; p->chCnt = chCnt; p->initFragSecs = initFragSecs; p->maxLaSmpCnt = floor(maxLaSecs*srate); p->curLaSmpCnt = floor(curLaSecs*srate); p->laChs = cmMemAllocZ(cmSample_t*,chCnt); p->laSmpIdx = 0; for(i=0; ilaChs[i] = cmMemAllocZ(cmSample_t,p->maxLaSmpCnt); return rc; } cmRC_t cmRecdPlayFinal( cmRecdPlay* p ) { unsigned i,j; // free the fragments for(i=0; ifragCnt; ++i) { for(j=0; jchCnt; ++j) cmMemFree(p->frags[i].chArray[j]); cmMemFree(p->frags[i].chArray); } // free the look-ahead buffers for(i=0; ichCnt; ++i) cmMemFree(p->laChs[i]); cmMemPtrFree(&p->laChs); cmMemPtrFree(&p->frags); p->fragCnt = 0; p->chCnt = 0; p->rlist = NULL; p->plist = NULL; return cmOkRC; } cmRC_t cmRecdPlayRegisterFrag( cmRecdPlay* p, unsigned fragIdx, unsigned labelSymId ) { assert( fragIdx < p->fragCnt ); unsigned i; p->frags[ fragIdx ].labelSymId = labelSymId; p->frags[ fragIdx ].chArray = cmMemResizeZ(cmSample_t*,p->frags[fragIdx].chArray,p->chCnt); for(i=0; ichCnt; ++i) { p->frags[ fragIdx ].allocCnt = floor(p->initFragSecs * p->srate); p->frags[ fragIdx ].chArray[i] = cmMemResizeZ(cmSample_t,p->frags[ fragIdx ].chArray[i],p->frags[fragIdx].allocCnt); } return cmOkRC; } cmRC_t cmRecdPlaySetLaSecs( cmRecdPlay* p, double curLaSecs ) { p->curLaSmpCnt = floor(curLaSecs*p->srate); return cmOkRC; } cmRC_t cmRecdPlayRewind( cmRecdPlay* p ) { unsigned i; // zero the look-ahead buffers p->laSmpIdx = 0; for(i=0; ichCnt; ++i) cmVOS_Zero(p->laChs[i],p->maxLaSmpCnt); // remove all the active players while( p->plist != NULL ) cmRecdPlayEndPlay(p,p->plist->labelSymId); // remove all the active recorders while( p->rlist != NULL ) cmRecdPlayEndRecord(p,p->rlist->labelSymId); // rewind all the fragments play posn. for(i=0; ifragCnt; ++i) p->frags[i].playIdx = 0; return cmOkRC; } cmRC_t cmRecdPlayBeginRecord( cmRecdPlay* p, unsigned labelSymId ) { unsigned i; for(i=0; ifragCnt; ++i) if( p->frags[i].labelSymId == labelSymId ) { // if the frag is not already on the recd list if( p->frags[i].rlink == NULL ) { p->frags[i].recdIdx = 0; p->frags[i].playIdx = 0; p->frags[i].rlink = p->rlist; p->rlist = p->frags + i; // handle LA buf longer than frag buf. int cpyCnt = cmMin(p->curLaSmpCnt,p->frags[i].allocCnt); // go backwards in LA buf from newest sample to find init src offset int srcOffs = p->laSmpIdx - cpyCnt; // if the src is before the first sample in the LA buf then wrap to end of buf if( srcOffs < 0 ) srcOffs += p->maxLaSmpCnt; assert( 0 <= srcOffs && srcOffs < p->maxLaSmpCnt ); // cnt of samples to copy from LA buf (limited by end of LA buf) int n0 = cmMin(cpyCnt,p->maxLaSmpCnt - srcOffs); // if necessary wrap to begin of LA buf for remaining samples int n1 = cpyCnt>n0 ? n1 = cpyCnt-n0 : 0; int j; assert(n0+n1 == cpyCnt ); for(j=0; jchCnt; ++j) cmVOS_Copy(p->frags[i].chArray[j],n0,p->laChs[j]+srcOffs); if( n1 > 0 ) { for(j=0; jchCnt; ++j) cmVOS_Copy(p->frags[i].chArray[j]+n0,n1,p->laChs[j]); } p->frags[i].recdIdx = cpyCnt; p->frags[i].playIdx = 0; } return cmOkRC; } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The fragment label symbol id '%i' not found for 'begin record'.",labelSymId); } cmRC_t cmRecdPlayEndRecord( cmRecdPlay* p, unsigned labelSymId ) { cmRecdPlayFrag* fp = p->rlist; cmRecdPlayFrag* pp = NULL; for(; fp != NULL; fp=fp->rlink ) { if( fp->labelSymId == labelSymId ) { if( pp == NULL ) p->rlist = fp->rlink; else pp->rlink = fp->rlink; fp->rlink = NULL; return cmOkRC; } pp = fp; } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The fragment label symbol id '%i' not found for 'end record'.",labelSymId); } cmRC_t cmRecdPlayInsertRecord(cmRecdPlay* p, unsigned labelSymId, const cmChar_t* wavFn ) { cmRC_t rc = cmOkRC; unsigned i; for(i=0; ifragCnt; ++i) if( p->frags[i].labelSymId == labelSymId ) { cmAudioFileH_t afH = cmNullAudioFileH; cmAudioFileInfo_t afInfo; cmRC_t afRC = kOkAfRC; // open the audio file if( cmAudioFileIsValid( afH = cmAudioFileNewOpen(wavFn, &afInfo, &afRC, p->obj.err.rpt )) == false ) return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The audio file '%s' could not be opened'.",cmStringNullGuard(wavFn)); // ignore blank if( afInfo.frameCnt == 0 ) return cmOkRC; // allocate buffer space unsigned j; for(j=0; jchCnt; ++j) p->frags[i].chArray[j] = cmMemResize(cmSample_t,p->frags[i].chArray[j],afInfo.frameCnt); p->frags[i].allocCnt = afInfo.frameCnt; // read samples into the buffer space unsigned chIdx = 0; unsigned chCnt = cmMin(p->chCnt,afInfo.chCnt); unsigned actFrmCnt = 0; if( cmAudioFileReadSample(afH,afInfo.frameCnt,chIdx,chCnt,p->frags[i].chArray, &actFrmCnt) != kOkAfRC ) return cmCtxRtCondition(&p->obj, cmSubSysFailRC, "Read failed on the audio file '%s'.",cmStringNullGuard(wavFn)); p->frags[i].recdIdx = actFrmCnt; return rc; } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The fragment label symbol id '%i' not found for 'begin record'.",labelSymId); } cmRC_t cmRecdPlayBeginPlay( cmRecdPlay* p, unsigned labelSymId ) { unsigned i; for(i=0; ifragCnt; ++i) if( p->frags[i].labelSymId == labelSymId ) { // if the frag is not already on the play list if( p->frags[i].plink == NULL ) { p->frags[i].playIdx = 0; p->frags[i].fadeSmpIdx = 0; p->frags[i].fadeDbPerSec = 0.0; p->frags[i].plink = p->plist; p->plist = p->frags + i; } return cmOkRC; } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The fragment label symbol id '%i' not found for 'begin play'.",labelSymId); } cmRC_t cmRecdPlayEndPlay( cmRecdPlay* p, unsigned labelSymId ) { cmRecdPlayFrag* fp = p->plist; cmRecdPlayFrag* pp = NULL; for(; fp != NULL; fp=fp->plink ) { if( fp->labelSymId == labelSymId ) { if( pp == NULL ) p->plist = fp->plink; else pp->plink = fp->plink; fp->plink = NULL; return cmOkRC; } pp = fp; } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The fragment label symbol id '%i' not found for 'end play'.",labelSymId); } cmRC_t cmRecdPlayBeginFade( cmRecdPlay* p, unsigned labelSymId, double fadeDbPerSec ) { cmRecdPlayFrag* fp = p->plist; for(; fp != NULL; fp=fp->plink ) if( fp->labelSymId == labelSymId ) { fp->fadeDbPerSec = -fabs(fadeDbPerSec); return cmOkRC; } return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The fragment label symbol id '%i' not found for 'fade begin'.",labelSymId); } cmRC_t cmRecdPlayExec( cmRecdPlay* p, const cmSample_t** iChs, cmSample_t** oChs, unsigned chCnt, unsigned smpCnt ) { unsigned i; chCnt = cmMin(chCnt, p->chCnt); //------------------------------------------------------------------- // copy incoming audio into the look-ahead buffers // // if the number of incoming samples is longer than the look-head buffer // then copy exactly maxLaSmpCnt samples from the end of the incoming sample // buffer to the look-ahead buffer. unsigned srcOffs = 0; unsigned srcSmpCnt = smpCnt; if( srcSmpCnt > p->maxLaSmpCnt ) { // advance incoming sample buffer so that there are maxLaSmpCnt samples remaining srcOffs = srcSmpCnt-p->maxLaSmpCnt; srcSmpCnt = p->maxLaSmpCnt; // decrease the total samples to copy } // count of samples from cur posn to end of the LA buffer. unsigned n0 = cmMin(srcSmpCnt, p->maxLaSmpCnt - p->laSmpIdx ); // count of samples past the end of the LA buffer to be wrapped into begin of buffer unsigned n1 = srcSmpCnt>n0 ? srcSmpCnt-n0 : 0; assert(n0+n1 == srcSmpCnt); // copy first block to end of LA buffer for(i=0; ilaChs[i]+p->laSmpIdx,n0); else cmVOS_Copy(p->laChs[i]+p->laSmpIdx,n0,iChs[i] + srcOffs); p->laSmpIdx += n0; if( n1!=0) { // copy second block to begin of LA buffer for(i=0; ilaChs[i],n1); else cmVOS_Copy(p->laChs[i],n1,iChs[i] + srcOffs + n0); p->laSmpIdx = n1; } //------------------------------------------------------------------- // copy incoming audio into the active record buffers // cmRecdPlayFrag* fp = p->rlist; for(; fp!=NULL; fp=fp->rlink) { assert( fp->recdIdx <= fp->allocCnt); unsigned n = cmMin(fp->allocCnt - fp->recdIdx,smpCnt); unsigned i; for(i=0; ichCnt; ++i) if( iChs[i] == NULL ) cmVOS_Zero(fp->chArray[i] + fp->recdIdx, n ); else cmVOS_Copy(fp->chArray[i] + fp->recdIdx, n, iChs[i] ); fp->recdIdx += n; } //------------------------------------------------------------------- // copy outgoing audio out of the active play buffers // fp = p->plist; for(; fp!=NULL; fp=fp->rlink) { assert( fp->playIdx <= fp->recdIdx); double gain = pow(10.0,((fp->fadeSmpIdx / p->srate) * fp->fadeDbPerSec)/20.0); unsigned n = cmMin(fp->recdIdx - fp->playIdx,smpCnt); unsigned i; for(i=0; ichCnt; ++i) if( oChs[i] != NULL ) cmVOS_MultVVS(oChs[i],n,fp->chArray[i] + fp->playIdx,gain); fp->playIdx += n; // if a fade rate has been set then advance the fade phase if(fp->fadeDbPerSec!=0.0) fp->fadeSmpIdx += smpCnt; } return cmOkRC; }