From 78c9c3064bae15876a96388789604826169e0903 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 13 Jan 2013 16:47:50 -0800 Subject: [PATCH] cmProc4.h: Added ed_xxx edit dist testing functions and cmScAlign class. --- cmProc4.c | 1758 ++++++++++++++++++++++++++++++++++++++++++++++++++++- cmProc4.h | 221 ++++++- 2 files changed, 1976 insertions(+), 3 deletions(-) diff --git a/cmProc4.c b/cmProc4.c index 5c00152..aca2f0a 100644 --- a/cmProc4.c +++ b/cmProc4.c @@ -12,6 +12,7 @@ #include "cmJson.h" #include "cmSymTbl.h" #include "cmAudioFile.h" +#include "cmText.h" #include "cmProcObj.h" #include "cmProcTemplate.h" #include "cmMath.h" @@ -576,8 +577,8 @@ cmRC_t cmScTrkInit( cmScTrk* p, cmReal_t srate, cmScH_t scH, unsigned bufN, u p->loc = cmMemResizeZ(cmScTrkLoc_t,p->loc,p->locN); p->minVel = minVel; p->maxWndCnt = maxWndCnt; - p->minWndLookAhead= 3; //minWndLookAhead; - p->printFl = false; + p->minWndLookAhead= 4; //minWndLookAhead; + p->printFl = true; p->curLocIdx = cmInvalidIdx; p->evtIndex = 0; @@ -766,3 +767,1756 @@ unsigned cmScTrkExec( cmScTrk* p, unsigned smpIdx, unsigned status, cmMidiByte_ return ret_idx; } + +//======================================================================================================================= +//---------------------------------------------------------------------------------------- +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); +} + + +//======================================================================================================================= + +cmScAlign* cmScAlignAlloc( cmCtx* c, cmScAlign* p, cmScAlignCb_t cbFunc, void* cbArg, cmReal_t srate, cmScH_t scH, unsigned midiN, unsigned scWndN ) +{ + cmScAlign* op = cmObjAlloc(cmScAlign,c,p); + + if( srate != 0 ) + if( cmScAlignInit(op,cbFunc,cbArg,srate,scH,midiN,scWndN) != cmOkRC ) + cmScAlignFree(&op); + return op; +} + +cmRC_t cmScAlignFree( cmScAlign** pp ) +{ + cmRC_t rc = cmOkRC; + if( pp==NULL || *pp==NULL ) + return rc; + + cmScAlign* p = *pp; + if((rc = cmScAlignFinal(p)) != cmOkRC ) + return rc; + + cmMemFree(p->loc); + cmMemFree(p->midiBuf); + cmMemFree(p->m); + cmMemFree(p->p_mem); + cmMemFree(p->res); + cmObjFree(pp); + return rc; +} + +void _cmScAlignPrint( cmScAlign* p ) +{ + int i,j; + for(i=0; ilocN; ++i) + { + printf("%2i %5i ",p->loc[i].barNumb,p->loc[i].scLocIdx); + for(j=0; jloc[i].evtCnt; ++j) + printf("%s ",cmMidiToSciPitch(p->loc[i].evtV[j].pitch,NULL,0)); + printf("\n"); + } +} + + +cmRC_t cmScAlignInit( cmScAlign* p, cmScAlignCb_t cbFunc, void* cbArg, cmReal_t srate, cmScH_t scH, unsigned midiN, unsigned scWndN ) +{ + cmRC_t rc; + if((rc = cmScAlignFinal(p)) != cmOkRC ) + return rc; + + if( midiN > scWndN ) + return cmCtxRtCondition( &p->obj, cmInvalidArgRC, "The score alignment MIDI event buffer length (%i) must be less than the score window length (%i).",midiN,scWndN); + + p->cbFunc = cbFunc; + p->cbArg = cbArg; + p->srate = srate; + p->scH = scH; + p->locN = cmScoreEvtCount(scH); + p->loc = cmMemResizeZ(cmScAlignLoc_t,p->loc,p->locN); + p->mn = midiN; + p->midiBuf = cmMemResizeZ(cmScAlignMidiEvt_t,p->midiBuf,midiN); + p->mbi = midiN; + p->printFl = true; + + // Setup score structures + + // for each score location + unsigned li,ei; + + 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(cmScAlignScEvt_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; + + // setup edit distance structures + p->rn = midiN+1; + p->cn = scWndN+1; + p->m = cmMemResizeZ(cmScAlignVal_t, p->m, p->rn*p->cn ); + p->pn = p->rn + p->cn; + p->p_mem = cmMemResizeZ(cmScAlignPath_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; + p->s_opt = DBL_MAX; + p->resN = 2 * cmScoreEvtCount(scH); // make twice as many result records as there are score events + p->res = cmMemResizeZ(cmScAlignResult_t, p->res, p->resN); + p->stepCnt = 3; + p->maxStepMissCnt = 4; + + // fill in the default values for the first row + // and column of the DP matrix + unsigned i,j,k; + for(i=0; irn; ++i) + for(j=0; jcn; ++j) + { + unsigned v[] = {0,0,0,0}; + + if( i == 0 ) + { + v[kSaMinIdx] = j; + v[kSaInsIdx] = j; + } + else + if( j == 0 ) + { + v[kSaMinIdx] = i; + v[kSaDelIdx] = i; + } + + for(k=0; km[ i + (j*p->rn) ].v[k] = v[k]; + } + + // 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; + } + + //_cmScAlignPrint(p); + + return rc; +} + +cmRC_t cmScAlignFinal( cmScAlign* p ) +{ + unsigned i; + for(i=0; ilocN; ++i) + cmMemPtrFree(&p->loc[i].evtV); + + return cmOkRC; +} + +void cmScAlignReset( cmScAlign* p, unsigned begScanLocIdx ) +{ + assert( begScanLocIdx < p->locN ); + p->mbi = p->mn; + p->mni = 0; + p->begScanLocIdx = begScanLocIdx; + p->s_opt = DBL_MAX; + p->missCnt = 0; + p->scanCnt = 0; + p->ri = 0; +} + +cmScAlignVal_t* _cmScAlignValPtr( cmScAlign* p, unsigned i, unsigned j ) +{ + assert( i < p->rn ); + assert( j < p->cn ); + + return p->m + i + (j*p->rn); +} + +bool _cmScAlignIsMatch( const cmScAlignLoc_t* loc, unsigned pitch ) +{ + unsigned i; + for(i=0; ievtCnt; ++i) + if( loc->evtV[i].pitch == pitch ) + return true; + return false; +} + +bool _cmScAlignIsTrans( cmScAlign* p, const cmScAlignVal_t* v1p, unsigned i, unsigned j ) +{ + bool fl = false; + cmScAlignVal_t* v0p = _cmScAlignValPtr(p,i,j); + + if( i>=1 && j>=1 + && v1p->v[kSaMinIdx] == v1p->v[kSaSubIdx] + && v1p->matchFl == false + && v0p->v[kSaMinIdx] == v0p->v[kSaSubIdx] + && v0p->matchFl == false ) + { + unsigned c00 = p->midiBuf[i-1].pitch; + unsigned c01 = p->midiBuf[i ].pitch; + cmScAlignLoc_t* c10 = p->loc + p->begScanLocIdx + j - 1; + cmScAlignLoc_t* c11 = p->loc + p->begScanLocIdx + j; + fl = _cmScAlignIsMatch(c11,c00) && _cmScAlignIsMatch(c10,c01); + } + return fl; +} + +unsigned _cmScAlignMin( cmScAlign* p, unsigned i, unsigned j ) +{ + assert( irn && jcn ); + //return p->m[ i + (j*p->rn) ].v[kSaMinIdx]; + return _cmScAlignValPtr(p,i,j)->v[kSaMinIdx]; +} + +// Returns 'false' if the score window goes past the end of the score +// (i.e. p->begScanLocIdx + p->cn > p->locN ) +bool _cmScAlignCalcMtx( cmScAlign* p ) +{ + // the midi buffer must be full + assert( p->mbi == 0 ); + + // loc[begScanLocIdx:begScanLocIdx+p->cn-1] must be valid + if( p->begScanLocIdx + p->cn > p->locN ) + return false; + + unsigned i,j; + for(i=1; irn; ++i) + for(j=1; jcn; ++j) + { + cmScAlignLoc_t* loc = p->loc + p->begScanLocIdx + j - 1; + unsigned pitch = p->midiBuf[i-1].pitch; + cmScAlignVal_t* vp = _cmScAlignValPtr(p,i,j); + vp->matchFl = _cmScAlignIsMatch(loc,pitch); + unsigned cost = vp->matchFl ? 0 : 1; + vp->v[kSaSubIdx] = _cmScAlignMin(p,i-1,j-1) + cost; + vp->v[kSaDelIdx] = _cmScAlignMin(p,i-1,j ) + 1; + vp->v[kSaInsIdx] = _cmScAlignMin(p,i, j-1) + 1; + vp->v[kSaMinIdx] = cmMin( vp->v[kSaSubIdx], cmMin(vp->v[kSaDelIdx],vp->v[kSaInsIdx])); + vp->transFl = _cmScAlignIsTrans(p,vp,i-1,j-1); + } + + return true; +} + +void _cmScAlignPathPush( cmScAlign* r, unsigned code, unsigned ri, unsigned ci, bool matchFl, bool transFl ) +{ + assert(r->p_avl != NULL ); + cmScAlignPath_t* p = r->p_avl; + r->p_avl = r->p_avl->next; + + p->code = code; + p->ri = ri; + p->ci = ci; + p->matchFl = code==kSaSubIdx ? matchFl : false; + p->transFl = transFl; + p->next = r->p_cur; + r->p_cur = p; +} + +void _cmScAlignPathPop( cmScAlign* r ) +{ + assert( r->p_cur != NULL ); + cmScAlignPath_t* tp = r->p_cur->next; + r->p_cur->next = r->p_avl; + r->p_avl = r->p_cur; + r->p_cur = tp; +} + +double _cmScAlignScoreCandidate( cmScAlign* r ) +{ + cmScAlignPath_t* cp = r->p_cur; + cmScAlignPath_t* bp = r->p_cur; + cmScAlignPath_t* ep = NULL; + + for(; cp!=NULL; cp=cp->next) + if( cp->code != kSaInsIdx ) + { + bp = cp; + break; + } + + for(; cp!=NULL; cp=cp->next) + if( cp->code!=kSaInsIdx ) + 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 != kSaSubIdx && pc==kSaSubIdx && pfl==true ) + if( pfl==true && cp->matchFl==false ) + ++gapCnt; + + // + switch( cp->code ) + { + case kSaSubIdx: + penalty += cp->matchFl ? 0 : 1; + penalty -= cp->transFl ? 1 : 0; + break; + + case kSaDelIdx: + penalty += 1; + break; + + case kSaInsIdx: + 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 _cmScAlignEvalCandidate( cmScAlign* r, double score ) +{ + + if( r->s_opt == DBL_MAX || score < r->s_opt) + { + // copy the p_cur to p_opt[] + cmScAlignPath_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].matchFl = cp->matchFl; + r->p_opt[i].transFl = cp->transFl; + 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 + r->s_opt = score; + } +} + + + +// traverse the solution matrix from the lower-right to +// the upper-left. +void _cmScAlignGenPaths( cmScAlign* r, int i, int j ) +{ + unsigned m; + + // stop when the upper-right is encountered + if( i==0 && j==0 ) + { + _cmScAlignEvalCandidate(r, _cmScAlignScoreCandidate(r) ); + return; + } + + cmScAlignVal_t* vp = _cmScAlignValPtr(r,i,j); + + // for each possible dir: up,left,up-left + for(m=1; mv[m] == vp->v[kSaMinIdx] ) + { + // prepend to the current candidate path: r->p_cur + _cmScAlignPathPush(r,m,i,j,vp->matchFl,vp->transFl); + + int ii = i-1; + int jj = j-1; + + switch(m) + { + case kSaSubIdx: + break; + + case kSaDelIdx: + jj = j; + break; + + case kSaInsIdx: + ii = i; + break; + } + + // recurse! + _cmScAlignGenPaths(r,ii,jj); + + // remove the first element from the current path + _cmScAlignPathPop(r); + } + +} + +double _cmScAlign( cmScAlign* p ) +{ + int i = p->rn-1; + int j = p->cn-1; + unsigned m = _cmScAlignMin(p,i,j); //p->m[i + (j*p->rn)].v[kSaMinIdx]; + + if( m==cmMax(p->rn,p->cn) ) + printf("Edit distance is at max: %i. No Match.\n",m); + else + _cmScAlignGenPaths(p,i,j); + + return p->s_opt; +} + +bool cmScAlignExec( cmScAlign* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 ) +{ + bool fl = p->mbi > 0; + bool retFl = true; + + cmScAlignInputMidi(p,smpIdx,status,d0,d1); + + if( fl && p->mbi == 0 ) + { + if( cmScAlignScan(p,cmInvalidCnt) == cmInvalidIdx ) + retFl = false; + } + else + { + if( !fl && p->mbi == 0 ) + if( !cmScAlignStep(p) ) + retFl = false; + } + + return retFl; +} + +bool cmScAlignInputMidi( cmScAlign* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 ) +{ + if( status != kNoteOnMdId ) + return false; + + unsigned mi = p->mn-1; + + //printf("%3i %5.2f %4s\n",p->mni,(double)smpIdx/p->srate,cmMidiToSciPitch(d0,NULL,0)); + + // shift the new MIDI event onto the end of the MIDI buffer + memmove(p->midiBuf,p->midiBuf+1,sizeof(cmScAlignMidiEvt_t)*mi); + p->midiBuf[mi].locIdx = cmInvalidIdx; + p->midiBuf[mi].cbCnt = 0; + p->midiBuf[mi].mni = p->mni++; + p->midiBuf[mi].smpIdx = smpIdx; + p->midiBuf[mi].pitch = d0; + p->midiBuf[mi].vel = d1; + if( p->mbi > 0 ) + --p->mbi; + + return true; +} + +// If mep==NULL then the identified score location was not matched (this is an 'insert') +// these only occurr during 'scan' not 'step'. +// +// If locIdx == cmInvalidIdx then the MIDI event did not match a score location +// When this occurrs during a scan then this is a 'deleted' MIDI note otherwise +// the note was not found inside loc[esi-stepCnt:esi+stepCnt]. +// +// If mep!=NULL && scLocIdx!=cmInvalidIdx but matchFl==false then this is a +// 'substitute' with a mismatch. These only occur during 'scan'. +void _cmScAlignCb( cmScAlign* p, unsigned locIdx, cmScAlignMidiEvt_t* mep, bool matchFl, bool transFl ) +{ + // verify that the result buffer is not full + if( p->ri >= p->resN ) + { + cmCtxRtCondition( &p->obj, cmArgAssertRC, "The score alignment result buffer is full."); + return; + } + // don't report unmatched score locations + if( mep == NULL ) + return; + + ++mep->cbCnt; + + cmScAlignResult_t* rp = NULL; + + // if this is the first time this MIDI event has generated a callback ... + if( mep->cbCnt == 1 ) + rp = p->res + p->ri++; // ... then create a new record in p->res[] ... + else + if( mep->cbCnt > 1 && matchFl ) // ... otherwise if it was matched ... + { + unsigned i; + for(i=0; iri; ++i) + if(p->res[i].mni == mep->mni ) + { + if( p->res[i].matchFl == false ) // ... and it's previous recd was not matched then update the record with the match info. + rp = p->res + i; + } + } + + if(rp == NULL ) + return; + + assert( locIdx != cmInvalidIdx || mep != NULL ); + + rp->locIdx = locIdx; + rp->smpIdx = mep==NULL ? cmInvalidIdx : mep->smpIdx; + rp->mni = mep==NULL ? cmInvalidIdx : mep->mni; + rp->pitch = mep==NULL ? kInvalidMidiPitch : mep->pitch; + rp->vel = mep==NULL ? kInvalidMidiVelocity : mep->vel; + rp->matchFl = mep==NULL ? false : matchFl; + rp->transFl = mep==NULL ? false : transFl; +} + +void _cmScAlignPrintPath( cmScAlign* p, cmScAlignPath_t* cp, unsigned bsi ) +{ + assert( bsi != cmInvalidIdx ); + + cmScAlignPath_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) + { + cmScAlignLoc_t* lp = p->loc + bsi + pp->ci; + if( pp->code!=kSaDelIdx ) + { + if(lp->evtCnt > polyN) + polyN = lp->evtCnt; + + printf("%4i ",bsi+i); + ++i; + } + else + printf("%4s "," "); + } + + printf("\n"); + + // print the score notes + for(i=polyN; i>0; --i) + { + printf("%3i: ",i); + for(pp=cp; pp!=NULL; pp=pp->next) + { + int locIdx = bsi + pp->ci - 1; + assert(0 <= locIdx && locIdx <= p->locN); + cmScAlignLoc_t* lp = p->loc + locIdx; + if( pp->code!=kSaDelIdx && lp->evtCnt >= i ) + printf("%4s ",cmMidiToSciPitch(lp->evtV[i-1].pitch,NULL,0)); + else + printf("%4s ", pp->code==kSaDelIdx? "-" : " "); + } + printf("\n"); + } + + printf("mid: "); + + // print the MIDI buffer + for(pp=cp; pp!=NULL; pp=pp->next) + { + if( pp->code!=kSaInsIdx ) + printf("%4s ",cmMidiToSciPitch(p->midiBuf[pp->ri-1].pitch,NULL,0)); + else + printf("%4s ",pp->code==kSaInsIdx?"-":" "); + } + + printf("\nmni: "); + + // print the MIDI buffer index (mni) + for(pp=cp; pp!=NULL; pp=pp->next) + { + if( pp->code!=kSaInsIdx ) + printf("%4i ",p->midiBuf[pp->ri-1].mni); + else + printf("%4s ",pp->code==kSaInsIdx?"-":" "); + } + + printf("\n op: "); + + // print the substitute/insert/delete operation + for(pp=cp; pp!=NULL; pp=pp->next) + { + char c = ' '; + switch( pp->code ) + { + case kSaSubIdx: c = 's'; break; + case kSaDelIdx: c = 'd'; break; + case kSaInsIdx: c = 'i'; break; + default: + { assert(0); } + } + + printf("%4c ",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( pp->matchFl ) + s[k++] = 'm'; + + if( pp->transFl ) + s[k++] = 't'; + + s[k] = 0; + + printf("%4s ",s); + } + + printf("\nscl: "); + + // print the stored location index + for(pp=cp; pp!=NULL; pp=pp->next) + { + if( pp->locIdx == cmInvalidIdx ) + printf("%4s "," "); + else + printf("%4i ",p->loc[pp->locIdx].scLocIdx); + } + + printf("\n\n"); +} + +// Returns the p->loc[] index at the start of the min cost score window +// based on the current MIDI buffer. +// scanCnt is the number of time the score window will be shifted one +// location to the left +unsigned cmScAlignScan( cmScAlign* p, unsigned scanCnt ) +{ + unsigned bsi = cmInvalidIdx; + + assert( p->mbi == 0 ); + + // if the MIDI buf is full + if( p->mbi == 0 ) + { + double s_opt = DBL_MAX; + unsigned i; + + // Loop as long as the score window is inside the score. + // Fill the Dyn Pgm matrix: MIDI_buf to score[begScanLocIdx:begScanLocIdx+scWndN-1]. + for(i=0; _cmScAlignCalcMtx(p) && (scanCnt==cmInvalidCnt || ibegScanLocIdx; + } + + // increment the score window + p->begScanLocIdx += 1; + } + + // store the cost assoc'd with bsi + p->s_opt = s_opt; + + } + + assert( bsi != cmInvalidIdx ); + + + // Traverse the least cost path and: + // 1) Set p->esi to 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 p->missCnt: the count of trailing non-positive matches. + // p->missCnt is eventually used in cmScAlignStep() to track the number + // of consecutive trailing missed notes. + cmScAlignPath_t* cp = p->p_opt; + unsigned i = bsi; + + p->missCnt = 0; + p->esi = cmInvalidIdx; + for(i=0; cp!=NULL; cp=cp->next) + { + + if( cp->code != kSaInsIdx ) + { + assert( cp->ri > 0 ); + p->midiBuf[ cp->ri-1 ].locIdx = cmInvalidIdx; + } + + switch( cp->code ) + { + case kSaSubIdx: + if( cp->matchFl || cp->transFl) + { + p->esi = bsi + i; + p->missCnt = 0; + + if( cp->matchFl ) + p->midiBuf[ cp->ri-1 ].locIdx = bsi + i; + } + else + { + ++p->missCnt; + } + + cp->locIdx = bsi + i; + ++i; + break; + + case kSaInsIdx: + cp->locIdx = bsi + i; + ++i; + break; + + case kSaDelIdx: + cp->locIdx = cmInvalidIdx; + ++p->missCnt; + break; + + } + } + + // if no positive matches were found + if( p->esi == cmInvalidIdx ) + bsi = cmInvalidIdx; + else + { + + // report matches + for(cp=p->p_opt; cp!=NULL; cp=cp->next) + { + unsigned locIdx = cp->locIdx; + cmScAlignMidiEvt_t* mep = NULL; + + if( cp->code != kSaInsIdx ) + mep = p->midiBuf + cp->ri - 1; + + _cmScAlignCb(p,locIdx,mep,cp->matchFl,cp->transFl); + } + } + + return bsi; +} + + +bool cmScAlignStep( cmScAlign* p ) +{ + int i; + unsigned pitch = p->midiBuf[ p->mn-1 ].pitch; + unsigned locIdx = cmInvalidIdx; + + // the tracker must be sync'd to step + if( p->esi == cmInvalidIdx ) + return false; + + // if the end of the score has been reached + if( p->esi + 1 >= p->locN ) + return cmInvalidIdx; + + // attempt to match to next location first + if( _cmScAlignIsMatch(p->loc + p->esi + 1, pitch) ) + { + locIdx = p->esi + 1; + } + else + { + // + for(i=2; istepCnt; ++i) + { + // go forward + if( p->esi+i < p->locN && _cmScAlignIsMatch(p->loc + p->esi + i, pitch) ) + { + locIdx = p->esi + i; + break; + } + + // go backward + if( p->esi >= (i-1) && _cmScAlignIsMatch(p->loc + p->esi - (i-1), pitch) ) + { + locIdx = p->esi - (i-1); + break; + } + } + } + + p->midiBuf[ p->mn-1 ].locIdx = locIdx; + + if( locIdx == cmInvalidIdx ) + ++p->missCnt; + else + { + p->missCnt = 0; + p->esi = locIdx; + _cmScAlignCb(p,locIdx, p->midiBuf + p->mn - 1,true,false); + } + + if( p->missCnt >= p->maxStepMissCnt ) + { + p->begScanLocIdx = p->esi > p->rn ? p->esi - p->rn : 0; + p->s_opt = DBL_MAX; + unsigned bsi = cmScAlignScan(p,p->rn*2); + ++p->scanCnt; + + // if the scan failed find a match + if( bsi == cmInvalidIdx ) + return false; + + //if( bsi != cmInvalidIdx ) + // _cmScAlignPrintPath(p, p->p_opt, bsi ); + } + + return true; +} + + +void _cmScAlignPrintMtx( cmScAlign* r) +{ + unsigned i,j,k; + for(i=0; irn; ++i) + { + for(j=0; jcn; ++j) + { + printf("("); + + const cmScAlignVal_t* vp = _cmScAlignValPtr(r,i,j); + + for(k=0; kv[k]); + if( ktransFl?'t':' '); + + } + printf("\n"); + } +} + + +void cmScAlignPrintOpt( cmScAlign* p ) +{ + unsigned i; + for(i=0; p->p_opt[i].code!=0; ++i) + { + cmScAlignPath_t* cp = p->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",p->s_opt); +} + + + + +enum +{ + kBarSaFl = 0x01, // this is a score bar + kScNoteSaFl = 0x02, // this is a score reference note (if mni != cmInvalidIdx then it was matched) + kSubsErrSaFl = 0x04, // 'subs' mismatch midi note + kMidiErrSaFl = 0x08, // 'deleted' Midi note +}; + +typedef struct cmScAlignPrint_str +{ + unsigned flags; + unsigned scLocIdx; + unsigned smpIdx; + unsigned pitch; + unsigned vel; + unsigned mni; + bool matchFl; + bool transFl; +} cmScAlignPrint_t; + +void _cmScAlignPrintList( cmScAlignPrint_t* a, unsigned an ) +{ + cmScAlignPrint_t* pp; + unsigned i; + printf("----------------------------------------------------\n"); + printf("idx scl mni pit flg \n"); + for(i=0; iscLocIdx,pp->mni, + pp->pitch==kInvalidMidiPitch ? " " : cmMidiToSciPitch(pp->pitch,NULL,0), + pp->flags); + } + printf("\n"); +} + +// insert a black record at a[i] +unsigned _cmScAlignPrintExpand( cmScAlignPrint_t* a, unsigned aan, unsigned i, unsigned an ) +{ + assert( an < aan ); + memmove( a + i + 1, a + i, (an-i)*sizeof(cmScAlignPrint_t)); + memset( a + i, 0, sizeof(cmScAlignPrint_t)); + return an + 1; +} + +void _cmScAlignPrintOutResult( cmScAlign* p, cmScAlignResult_t* rp, const cmChar_t* label ) +{ + printf("loc:%4i scloc:%4i smp:%10i mni:%4i %4s %c %c %s\n", + rp->locIdx, + rp->locIdx==cmInvalidIdx ? -1 : p->loc[rp->locIdx].scLocIdx, + rp->smpIdx, + rp->mni, + rp->pitch<=127 ? cmMidiToSciPitch(rp->pitch,NULL,0) : " ", + rp->matchFl ? 'm' : ' ', + rp->transFl ? 't' : ' ', + label); +} + +void _cmScAlignPrintSet( cmScAlignPrint_t* pp, const cmScAlignResult_t* rp, unsigned flags, unsigned scLocIdx ) +{ + pp->scLocIdx = scLocIdx; + pp->flags = flags; + pp->smpIdx = rp->smpIdx; + pp->pitch = rp->pitch; + pp->vel = rp->vel; + pp->mni = rp->mni; + + assert( pp->scLocIdx!=cmInvalidIdx || pp->mni != cmInvalidIdx ); + +} + +unsigned _cmScAlignPrintPoly( cmScAlignPrint_t* a, unsigned an, unsigned scLocIdx ) +{ + unsigned polyN = 0; + unsigned i; + for(i=0; i polyN ) + polyN = pn; + } + + // print titles + if( titleFl ) + { + printf(" "); + for(j=bli; j0; --i) + { + printf("%3i: ",i); + for(j=bli; jmni == cmInvalidIdx && cmIsNotFlag(pp->flags,kBarSaFl) ) + printf("| %4s %4s %3s %1s "," ",cmMidiToSciPitch(pp->pitch,NULL,0)," "," "); + else + { + if( cmIsFlag(pp->flags,kBarSaFl) ) + printf("| %4s %4s %3i %1s "," "," | ",pp->pitch,"b"); + else + { + const cmChar_t* op = cmIsFlag(pp->flags,kMidiErrSaFl) ? "d" : " "; + op = cmIsFlag(pp->flags,kSubsErrSaFl) ? "s" : op; + printf("| %4i %4s %3i %1s ",pp->mni,cmMidiToSciPitch(pp->pitch,NULL,0),pp->vel,op); + } + } + } + } + printf("\n"); + } + printf("\n"); + + bli = eli; + } + +} + +void _cmScAlignPrintResult( cmScAlign* p ) +{ + // determine the scH score begin and end indexes + unsigned bsi = cmScoreLocCount(p->scH); + unsigned esi = 0; + unsigned i,j; + for(i=0; iri; ++i) + { + cmScAlignResult_t* rp = p->res + i; + + assert( rp->locIdx==cmInvalidIdx || rp->locIdxlocN); + if( rp->locIdx != cmInvalidIdx ) + { + bsi = cmMin(bsi,p->loc[ rp->locIdx ].scLocIdx); + esi = cmMax(esi,p->loc[ rp->locIdx ].scLocIdx); + } + } + + // get a count of MIDI events + score events + unsigned aan = p->ri; + for(i=bsi; i<=esi; ++i) + { + cmScoreLoc_t* lp = cmScoreLoc( p->scH, i); + + aan += lp->evtCnt; + } + + cmScAlignPrint_t* a = cmMemAllocZ(cmScAlignPrint_t,aan); + unsigned an = 0; + unsigned scNoteCnt = 0; + unsigned matchCnt = 0; // matched score notes + unsigned wrongCnt = 0; // unmatched midi notes + + // create a record for each score event + for(i=bsi; i<=esi; ++i) + { + cmScoreLoc_t* lp = cmScoreLoc( p->scH, i); + + for(j=0; jevtCnt; ++j,++an) + { + assert( an < aan ); + cmScAlignPrint_t* pp = a + an; + + assert( lp->index != cmInvalidIdx ); + + pp->scLocIdx = lp->index; + pp->mni = cmInvalidIdx; + pp->pitch = kInvalidMidiPitch; + pp->vel = cmInvalidIdx; + + switch( lp->evtArray[j]->type ) + { + case kBarEvtScId: + pp->flags = kBarSaFl; + pp->pitch = lp->evtArray[j]->barNumb; + pp->mni = cmInvalidIdx; + break; + + case kNonEvtScId: + pp->flags = kScNoteSaFl; + pp->pitch = lp->evtArray[j]->pitch; + ++scNoteCnt; + break; + } + } + } + + //_cmScAlignPrintList(a,an); + + // Update the score with matching MIDI notes + + // for each result record ... + for(i=0; iri; ++i) + { + cmScAlignResult_t* rp = p->res + i; + + rp->foundFl = false; + + // ... if this is not an errant MIDI note (delete) + if( rp->locIdx != cmInvalidIdx ) + { + assert( rp->locIdx != cmInvalidIdx && rp->locIdx < p->locN ); + + unsigned scLocIdx = p->loc[rp->locIdx].scLocIdx; + cmScAlignPrint_t* pp; + + + // ... find the score location matching the result record score location + for(j=0; jscLocIdx ) + { + // if this is a matching midi node + if( rp->matchFl && cmIsFlag(pp->flags,kScNoteSaFl) && pp->pitch == rp->pitch ) + { + //_cmScAlignPrintOutResult(p,rp,"match"); + rp->foundFl = true; + _cmScAlignPrintSet(pp, rp, pp->flags, pp->scLocIdx ); + ++matchCnt; + break; + } + + // if this is a 'substitute' non-matching note + if( rp->matchFl == false && rp->mni != cmInvalidIdx ) + { + //_cmScAlignPrintOutResult(p,rp,"mis-match"); + ++j; // insert after the a[j] + an = _cmScAlignPrintExpand(a,aan,j,an); + _cmScAlignPrintSet(a + j, rp, kSubsErrSaFl, scLocIdx ); + rp->foundFl = true; + ++wrongCnt; + break; + } + + // if this is a 'skipped' score note ('insert') alert + if( rp->mni == cmInvalidIdx ) + { + //_cmScAlignPrintOutResult(p,rp,"skip"); + rp->foundFl = true; + break; + } + + } + } + } + + if( rp->foundFl == false ) + { + // _cmScAlignPrintOutResult(p,rp,"not-found"); + } + } + + //_cmScAlignPrintList(a,an); + + // for each result record ... + for(i=0; iri; ++i) + { + cmScAlignResult_t* rp = p->res + i; + unsigned scLocIdx = cmInvalidIdx; + cmScAlignPrint_t* pp = NULL; + cmScAlignPrint_t* dpp = NULL; + unsigned dmin; + + // if this result did not have a matching score event + if(rp->foundFl) + continue; + + for(j=0; jmni!=cmInvalidId ) + { + unsigned d; + if( pp->mni > rp->mni ) + d = pp->mni - rp->mni; + else + d = rp->mni - pp->mni; + + if( dpp == NULL || d < dmin ) + { + dpp = pp; + dmin = d; + } + } + } + + assert( dpp != NULL ); + + j = dpp - a; + + if( rp->mni > dpp->mni ) + ++j; + + scLocIdx = dpp->scLocIdx; + + an = _cmScAlignPrintExpand(a,aan,j,an); + + if( rp->locIdx != cmInvalidIdx ) + { + assert( rp->locIdx != cmInvalidIdx && rp->locIdx < p->locN ); + scLocIdx = p->loc[ rp->locIdx ].scLocIdx; + } + + ++wrongCnt; + + _cmScAlignPrintSet(a + j, rp, kMidiErrSaFl, scLocIdx ); + + } + + //_cmScAlignPrintList(a,an); + + //_cmScAlignPrintReport(p,a,an,bsi,esi); + printf("score notes:%i match:%i wrong:%i \n",scNoteCnt,matchCnt,wrongCnt); +} + +unsigned cmScAlignScanToTimeLineEvent( cmScAlign* p, cmTlH_t tlH, cmTlObj_t* top, unsigned endSmpIdx ) +{ + assert( top != NULL ); + cmTlMidiEvt_t* mep = NULL; + + // get the next time MIDI msg + while( (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 == kNoteOnMdId ) + if( !cmScAlignExec(p, mep->obj.seqSmpIdx, mep->msg->status, mep->msg->u.chMsgPtr->d0, mep->msg->u.chMsgPtr->d1 ) ) + return cmInvalidIdx; + } + + return p->esi; +} + + +void cmScAlignCb( void* cbArg, unsigned scLocIdx, unsigned mni, unsigned pitch, unsigned vel ) +{ + //cmScAlign* p = (cmScAlign*)cbArg; + +} + +void cmScAlignScanMarkers( cmRpt_t* rpt, cmTlH_t tlH, cmScH_t scH ) +{ + unsigned i; + double srate = 96000; + unsigned midiN = 7; + unsigned scWndN = 10; + unsigned markN = 291; + cmCtx* ctx = cmCtxAlloc(NULL, rpt, cmLHeapNullHandle, cmSymTblNullHandle ); + cmScAlign* p = cmScAlignAlloc(ctx,NULL,cmScAlignCb,NULL,srate,scH,midiN,scWndN); + unsigned markCharCnt = 31; + cmChar_t markText[ markCharCnt+1 ]; + + p->cbArg = p; // set the callback arg. + + // for each marker + for(i=0; itext) ) + { + printf("The marker '%s' is being skipped because it has no text.\n",markText); + continue; + } + + // reset the score follower to the beginnig of the score + cmScAlignReset(p,0); + + //printf("%s %5.2f %5.2f %5.2f\n",markText,(double)(mp->obj.begSmpIdx)/p->srate,(double)(mp->obj.durSmpCnt)/p->srate,(double)(mp->obj.begSmpIdx+mp->obj.durSmpCnt)/p->srate); + + // scan to the beginning of the marker + unsigned bsi = cmScAlignScanToTimeLineEvent(p,tlH,&mp->obj,mp->obj.seqSmpIdx+mp->obj.durSmpCnt); + + if( bsi != cmInvalidIdx ) + { + //_cmScAlignPrintMtx(p); + printf("mark:%i scans:%4i loc:%4i bar:%4i score:%5.2f miss:%i text:'%s'\n",i,p->scanCnt,bsi,p->loc[bsi].barNumb,p->s_opt,p->missCnt,mp->text); + //_cmScAlignPrintPath(p, p->p_opt, bsi ); + + printf("mark:%i scans:%i midi:%i text:'%s'\n",i,p->scanCnt,p->mni,mp->text); + + _cmScAlignPrintResult(p); + + printf("\n"); + } + + //break; // ONLY USE ONE MARKER DURING TESTING + + } + + + + cmScAlignFree(&p); + cmCtxFree(&ctx); +} diff --git a/cmProc4.h b/cmProc4.h index 91fae6c..3157816 100644 --- a/cmProc4.h +++ b/cmProc4.h @@ -115,7 +115,226 @@ cmRC_t cmScTrkReset( cmScTrk* p, unsigned scoreIndex ); // Give the follower a MIDI performance event. Only MIDI note-on events are acted upon; // all others are ignored. unsigned cmScTrkExec( cmScTrk* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 ); - + +//======================================================================================================================= +// +// Simplified string alignment function based on Levenshtein edit distance. +// +enum { kEdMinIdx, kEdSubIdx, kEdDelIdx, kEdInsIdx, kEdCnt }; + +typedef struct +{ + unsigned v[kEdCnt]; + bool matchFl; + bool transFl; +} ed_val; + +typedef struct ed_path_str +{ + unsigned code; + unsigned ri; + unsigned ci; + bool matchFl; + bool transFl; + struct ed_path_str* next; +} ed_path; + +/* +Backtracking: +m[rn,cn] is organized to indicate the mutation operations +on s0[0:rn-1] or s1[0:cn-1] during backtracking. +Backtracking begins at cell m[rn-1,cn-1] and proceeds +up and left toward m[0,0]. The action to perform during +backtracking is determined by examinging which values +int m[].v[1:3] match m[].v[0]. +Match Next Cell +Index Operation Location +----- ------------------------ ------------------------ +1 Substitute char s0[ri-1] move diagonally; up-left +2 Delete char s0[ri-1] move up. +3 Delete char s1[ci-1] move left. + (same as inserting blank + into after s[ri-1] + +Note that more than one value in m[].v[1:3] may match +m[].v[0]. In this case the candidate solution branches +at this point in the candidate selection processes. +*/ +typedef struct +{ + const char* s0; // forms rows of m[] - mutate to match s1 - rn=strlen(s0) + const char* s1; // forms columns of m[] - target string - cn=strlen(s1) + unsigned rn; // length of s0 + 1 + unsigned cn; // length of s1 + 1 + ed_val* m; // m[rn,cn] + unsigned pn; // rn+cn + ed_path* p_mem; // pmem[ 2*pn ]; + ed_path* p_avl; // available path record linked list + ed_path* p_cur; // current path linked list + ed_path* p_opt; // p_opt[pn] current best alignment + double s_opt; // score of the current best alignment + +} ed_r; + +// print the DP matrix ed_r.m[rn,cn]. +void ed_print_mtx( ed_r* r ); + +// Initialize ed_r. +void ed_init( ed_r* r, const char* s0, const char* s1 ); + +// Fill in the DP matrix. +void ed_calc_mtx( ed_r* r ); + +// Traverse the possible alignments in the DP matrix and determine the optimal alignment. +void ed_align( ed_r* r ); + +// Print the optimal alignment p_opt[] +void ed_print_opt( ed_r* r ); + +// Free resource allocated by ed_init(). +void ed_free(ed_r* r); + +// Main test function. +void ed_main(); + +//======================================================================================================================= + +enum +{ + kSaMinIdx, + kSaSubIdx, // 'substitute' - may or may not match + kSaDelIdx, // 'delete' - delete a MIDI note + kSaInsIdx, // 'insert' - insert a space in the score + kSaCnt +}; + +typedef struct +{ + unsigned v[kSaCnt]; + bool matchFl; // if this is a substitute; is it also a match? + bool transFl; // if this is a substitute; is this the second element in a reversed pair? +} cmScAlignVal_t; + +typedef struct cmScAlignPath_str +{ + unsigned code; + unsigned ri; + unsigned ci; + bool matchFl; + bool transFl; + unsigned locIdx; + struct cmScAlignPath_str* next; +} cmScAlignPath_t; + +typedef struct +{ + unsigned pitch; + unsigned scEvtIdx; + bool matchFl; +} cmScAlignScEvt_t; + +typedef struct +{ + unsigned evtCnt; // + cmScAlignScEvt_t* evtV; // evtV[evtCnt] + unsigned scLocIdx; // scH score location index + int barNumb; // bar number of this location +} cmScAlignLoc_t; + +typedef struct +{ + unsigned locIdx; // location assoc'd with this MIDI evt (cmInvalidIdx if not a positive-match) + unsigned cbCnt; // count of times this event has been sent via the callback + unsigned mni; // unique identifier for this event since previous call to cmScAlignReset(). + unsigned smpIdx; // time stamp of this event + unsigned pitch; // MIDI note pitch + unsigned vel; // " " velocity +} cmScAlignMidiEvt_t; + +typedef struct +{ + unsigned locIdx; // loc[] sync. location + unsigned smpIdx; // + unsigned mni; // MIDI event unique index + unsigned pitch; // MIDI event pitch which may not match the score event pitch + unsigned vel; // " " velocity + bool matchFl; + bool transFl; + bool foundFl; +} cmScAlignResult_t; + +// +typedef void (*cmScAlignCb_t)( void* cbArg, unsigned scLocIdx, unsigned mni, unsigned pitch, unsigned vel ); + +typedef struct +{ + cmObj obj; + cmScAlignCb_t cbFunc; // + void* cbArg; // + cmScH_t scH; // + double srate; // + unsigned locN; // length of loc[] + cmScAlignLoc_t* loc; // loc[locN] score array + unsigned rn; // length of midiBuf[] (mn+1) + unsigned cn; // length of score window (scWndN+1) + unsigned mn; // length of midiBuf[] (rn-1) + cmScAlignMidiEvt_t* midiBuf; // midiBuf[ mn ] + unsigned mbi; // index of first element in midiBuf[] - this is required because the MIDI buf fills from the end to the beginning + unsigned mni; // index of event in midiBuf[p->mn] - increments on each note inserted into midiBuf[] - zeroed by cmScAlignReset(). + + cmScAlignVal_t* m; // m[rn,cn] + unsigned pn; // rn+cn + cmScAlignPath_t* p_mem; // pmem[ 2*pn ]; + cmScAlignPath_t* p_avl; // available path record linked list + cmScAlignPath_t* p_cur; // current path linked list + cmScAlignPath_t* p_opt; // p_opt[pn] current best alignment + double s_opt; // score of the current best alignment + unsigned esi; // loc[] index of latest positive match + unsigned missCnt; // count of consecutive trailing MIDI events without positive matches + unsigned scanCnt; + + bool printFl; + + unsigned begScanLocIdx; // begin the search at this score locations scWnd[begScanLocIdx:begScanLocIdx+p->cn-1] + + unsigned resN; // count of records in res[] == 2*cmScoreEvtCount() + cmScAlignResult_t* res; // res[resN] + unsigned ri; // + + int stepCnt; // count of loc[] locations to step ahead/back during a cmScAlignStep() operation. + int maxStepMissCnt; // max. consecutive trailing non-positive matches before a scan takes place. + + +} cmScAlign; + +cmScAlign* cmScAlignAlloc( cmCtx* ctx, cmScAlign* p, cmScAlignCb_t cbFunc, void* cbArg, cmReal_t srate, cmScH_t scH, unsigned midiN, unsigned scWndN ); +cmRC_t cmScAlignFree( cmScAlign** pp ); +cmRC_t cmScAlignInit( cmScAlign* p, cmScAlignCb_t cbFunc, void* cbArg, cmReal_t srate, cmScH_t scH, unsigned midiN, unsigned scWndN ); +cmRC_t cmScAlignFinal( cmScAlign* p ); +void cmScAlignReset( cmScAlign* p, unsigned begScanLocIdx ); + +bool cmScAlignExec( cmScAlign* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 ); + +bool cmScAlignInputMidi( cmScAlign* p, unsigned smpIdx, unsigned status, cmMidiByte_t d0, cmMidiByte_t d1 ); + +// Scan from p->begScanLocIdx to the end of the score looking +// for the best match to p->midiBuf[]. +// Returns the score location index which best matches the +// first note p->midiBuf[]. The score associated +// with this match is stored in s_opt. +unsigned cmScAlignScan( cmScAlign* p, unsigned scanCnt ); + +// Step forward/back by p->stepCnt from p->esi. +// If more than p->maxStepMissCnt consecutive MIDI events are +// missed then automatically run cmScAlignScan(). +bool cmScAlignStep( cmScAlign* p ); + +unsigned cmScAlignScanToTimeLineEvent( cmScAlign* p, cmTlH_t tlH, cmTlObj_t* top, unsigned endSmpIdx ); + +// Given a score, a time-line, and a marker on the time line scan the +// entire score looking for the best match between the first 'midiN' +// notes in each marker region and the score. +void cmScAlignScanMarkers( cmRpt_t* rpt, cmTlH_t tlH, cmScH_t scH ); #ifdef __cplusplus }