cwSfMatch.h/cpp, cwSfTrack.h/cpp, Makefile.am : Initial commit.
This commit is contained in:
parent
e1d84b2861
commit
5e3130503d
@ -67,8 +67,11 @@ libcwSRC += src/libcw/cwIoMidiRecordPlay.cpp src/libcw/cwIoAudioRecordPlay.cpp
|
|||||||
libcwHDR += src/libcw/cwIoPresetSelApp.h src/libcw/cwPianoScore.h src/libcw/cwPresetSel.h src/libcw/cwVelTableTuner.h
|
libcwHDR += src/libcw/cwIoPresetSelApp.h src/libcw/cwPianoScore.h src/libcw/cwPresetSel.h src/libcw/cwVelTableTuner.h
|
||||||
libcwSRC += src/libcw/cwIoPresetSelApp.cpp src/libcw/cwPianoScore.cpp src/libcw/cwPresetSel.cpp src/libcw/cwVelTableTuner.cpp
|
libcwSRC += src/libcw/cwIoPresetSelApp.cpp src/libcw/cwPianoScore.cpp src/libcw/cwPresetSel.cpp src/libcw/cwVelTableTuner.cpp
|
||||||
|
|
||||||
libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h src/libcw/cwSfScoreParser.h src/libcw/cwSfScore.h
|
libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h
|
||||||
libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwSfScoreParser.cpp src/libcw/cwSfScore.cpp
|
libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp
|
||||||
|
|
||||||
|
libcwHDR += src/libcw/cwSfScoreParser.h src/libcw/cwSfScore.h src/libcw/cwSfMatch.h src/libcw/cwSfTrack.h
|
||||||
|
libcwSRC += src/libcw/cwSfScoreParser.cpp src/libcw/cwSfScore.cpp src/libcw/cwSfMatch.cpp src/libcw/cwSfTrack.cpp
|
||||||
|
|
||||||
libcwHDR += src/libcw/cwScoreFollowerPerf.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScoreFollow.h
|
libcwHDR += src/libcw/cwScoreFollowerPerf.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScoreFollow.h
|
||||||
libcwSRC += src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScoreFollow.cpp
|
libcwSRC += src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScoreFollow.cpp
|
||||||
|
894
cwSfMatch.cpp
Normal file
894
cwSfMatch.cpp
Normal file
@ -0,0 +1,894 @@
|
|||||||
|
#include "cwCommon.h"
|
||||||
|
#include "cwLog.h"
|
||||||
|
#include "cwCommonImpl.h"
|
||||||
|
#include "cwMem.h"
|
||||||
|
#include "cwText.h"
|
||||||
|
#include "cwObject.h"
|
||||||
|
#include "cwMidi.h"
|
||||||
|
#include "cwFileSys.h"
|
||||||
|
#include "cwSfScore.h"
|
||||||
|
#include "cwSfMatch.h"
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace sfmatch
|
||||||
|
{
|
||||||
|
sfmatch_t* _handleToPtr( handle_t h )
|
||||||
|
{ return handleToPtr<handle_t,sfmatch_t>(h); }
|
||||||
|
|
||||||
|
rc_t _destroy( sfmatch_t* p )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
for(i=0; i<p->locN; ++i)
|
||||||
|
mem::release(p->loc[i].evtV);
|
||||||
|
|
||||||
|
mem::release(p->loc);
|
||||||
|
mem::release(p->m);
|
||||||
|
mem::release(p->p_mem);
|
||||||
|
|
||||||
|
mem::release(p);
|
||||||
|
return rc;
|
||||||
|
};
|
||||||
|
|
||||||
|
void _cmScMatchInitLoc( sfmatch_t* p )
|
||||||
|
{
|
||||||
|
unsigned li,ei;
|
||||||
|
|
||||||
|
p->locN = event_count(p->scH);
|
||||||
|
p->loc = mem::resizeZ<loc_t>(p->loc,p->locN);
|
||||||
|
|
||||||
|
|
||||||
|
// for each score location
|
||||||
|
for(li=0,ei=0; li<loc_count(p->scH); ++li)
|
||||||
|
{
|
||||||
|
unsigned i,n;
|
||||||
|
|
||||||
|
const sfscore::loc_t* lp = loc(p->scH,li);
|
||||||
|
|
||||||
|
// count the number of note events at location li
|
||||||
|
for(n=0,i=0; i<lp->evtCnt; ++i)
|
||||||
|
if( lp->evtArray[i]->type == sfscore::kNonEvtScId )
|
||||||
|
++n;
|
||||||
|
|
||||||
|
assert( ei+n <= p->locN );
|
||||||
|
|
||||||
|
// duplicate each note at location li n times
|
||||||
|
for(i=0; i<n; ++i)
|
||||||
|
{
|
||||||
|
unsigned j,k;
|
||||||
|
|
||||||
|
p->loc[ei+i].evtCnt = n;
|
||||||
|
p->loc[ei+i].evtV = mem::allocZ<event_t>(n);
|
||||||
|
p->loc[ei+i].scLocIdx = li;
|
||||||
|
p->loc[ei+i].barNumb = lp->barNumb;
|
||||||
|
|
||||||
|
for(j=0,k=0; j<lp->evtCnt; ++j)
|
||||||
|
if( lp->evtArray[j]->type == sfscore::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _cmScMatchInitMtx( sfmatch_t* p, unsigned rn, unsigned cn )
|
||||||
|
{
|
||||||
|
//if( rn >p->mrn && cn > p->mcn )
|
||||||
|
if( rn*cn > p->mrn*p->mcn )
|
||||||
|
{
|
||||||
|
return cwLogError(kInvalidArgRC, "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 kOkRC;
|
||||||
|
|
||||||
|
// 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; i<rn; ++i)
|
||||||
|
for(j=0; j<cn; ++j)
|
||||||
|
{
|
||||||
|
unsigned v[] = {0,0,0,0};
|
||||||
|
|
||||||
|
if( i == 0 )
|
||||||
|
{
|
||||||
|
v[kSmMinIdx] = j;
|
||||||
|
v[kSmInsIdx] = j;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if( j == 0 )
|
||||||
|
{
|
||||||
|
v[kSmMinIdx] = i;
|
||||||
|
v[kSmDelIdx] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero the value field
|
||||||
|
for(k=0; k<kSmCnt; ++k)
|
||||||
|
p->m[ i + (j*rn) ].v[k] = v[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
return kOkRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_t* _cmScMatchValPtr( sfmatch_t* p, unsigned i, unsigned j, unsigned rn, unsigned cn )
|
||||||
|
{
|
||||||
|
assert( i < rn && j < cn );
|
||||||
|
|
||||||
|
return p->m + i + (j*rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _cmScMatchIsTrans( sfmatch_t* p, const midi_t* midiV, const value_t* v1p, unsigned bsi, unsigned i, unsigned j, unsigned rn, unsigned cn )
|
||||||
|
{
|
||||||
|
bool fl = false;
|
||||||
|
value_t* v0p = _cmScMatchValPtr(p,i,j,rn,cn);
|
||||||
|
|
||||||
|
if( i>=1 && j>=1
|
||||||
|
&& v1p->v[kSmMinIdx] == v1p->v[kSmSubIdx]
|
||||||
|
&& cwIsNotFlag(v1p->flags,kSmMatchFl)
|
||||||
|
&& v0p->v[kSmMinIdx] == v0p->v[kSmSubIdx]
|
||||||
|
&& cwIsNotFlag(v0p->flags,kSmMatchFl)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
unsigned c00 = midiV[i-1].pitch;
|
||||||
|
unsigned c01 = midiV[i ].pitch;
|
||||||
|
loc_t* c10 = p->loc + bsi + j - 1;
|
||||||
|
loc_t* c11 = p->loc + bsi + j;
|
||||||
|
fl = is_match(c11,c00) && is_match(c10,c01);
|
||||||
|
}
|
||||||
|
return fl;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned _cmScMatchMin( sfmatch_t* 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( sfmatch_t* p, unsigned bsi, const midi_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; j<cn; ++j)
|
||||||
|
for(i=1; i<rn; ++i)
|
||||||
|
{
|
||||||
|
loc_t* loc = p->loc + bsi + j - 1;
|
||||||
|
unsigned pitch = midiV[i-1].pitch;
|
||||||
|
value_t* vp = _cmScMatchValPtr(p,i,j,rn,cn);
|
||||||
|
unsigned idx = match_index(loc,pitch);
|
||||||
|
vp->flags = idx==kInvalidIdx ? 0 : kSmMatchFl;
|
||||||
|
vp->scEvtIdx = idx==kInvalidIdx ? kInvalidIdx : loc->evtV[idx].scEvtIdx;
|
||||||
|
unsigned cost = cwIsFlag(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] = std::min( vp->v[kSmSubIdx], std::min(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( sfmatch_t* r, unsigned rn, unsigned cn)
|
||||||
|
{
|
||||||
|
unsigned i,j,k;
|
||||||
|
for(i=0; i<rn; ++i)
|
||||||
|
{
|
||||||
|
for(j=0; j<cn; ++j)
|
||||||
|
{
|
||||||
|
printf("(");
|
||||||
|
|
||||||
|
const value_t* vp = _cmScMatchValPtr(r,i,j,rn,cn);
|
||||||
|
|
||||||
|
for(k=0; k<kSmCnt; ++k)
|
||||||
|
{
|
||||||
|
printf("%i",vp->v[k]);
|
||||||
|
if( k<kSmCnt-1)
|
||||||
|
printf(", ");
|
||||||
|
else
|
||||||
|
printf(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%c%c)",cwIsFlag(vp->flags,kSmMatchFl)?'m':' ',cwIsFlag(vp->flags,kSmTransFl)?'t':' ');
|
||||||
|
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cmScMatchPathPush( sfmatch_t* r, unsigned code, unsigned ri, unsigned ci, unsigned flags, unsigned scEvtIdx )
|
||||||
|
{
|
||||||
|
assert(r->p_avl != NULL );
|
||||||
|
|
||||||
|
path_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 && cwIsFlag(flags,kSmMatchFl) ? kSmMatchFl : 0;
|
||||||
|
p->flags |= cwIsFlag(flags,kSmTransFl) ? kSmTransFl : 0;
|
||||||
|
p->scEvtIdx= scEvtIdx;
|
||||||
|
p->next = r->p_cur;
|
||||||
|
r->p_cur = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cmScMatchPathPop( sfmatch_t* r )
|
||||||
|
{
|
||||||
|
assert( r->p_cur != NULL );
|
||||||
|
path_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( sfmatch_t* r )
|
||||||
|
{
|
||||||
|
path_t* cp = r->p_cur;
|
||||||
|
path_t* bp = r->p_cur;
|
||||||
|
path_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 = cwIsFlag(bp->flags,kSmMatchFl);
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
cp = bp;
|
||||||
|
for(i=0; i<n; ++i,cp=cp->next)
|
||||||
|
{
|
||||||
|
// 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 && cwIsFlag(cp->flags,kSmMatchFl)==false )
|
||||||
|
++gapCnt;
|
||||||
|
|
||||||
|
//
|
||||||
|
switch( cp->code )
|
||||||
|
{
|
||||||
|
case kSmSubIdx:
|
||||||
|
penalty += cwIsFlag(cp->flags,kSmMatchFl) ? 0 : 1;
|
||||||
|
penalty -= cwIsFlag(cp->flags,kSmTransFl) ? 1 : 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kSmDelIdx:
|
||||||
|
penalty += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kSmInsIdx:
|
||||||
|
penalty += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pfl = cwIsFlag(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( sfmatch_t* r, double min_cost, double cost )
|
||||||
|
{
|
||||||
|
|
||||||
|
if( min_cost == DBL_MAX || cost < min_cost)
|
||||||
|
{
|
||||||
|
// copy the p_cur to p_opt[]
|
||||||
|
path_t* cp = r->p_cur;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
for(i=0; cp!=NULL && i<r->pn; 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( sfmatch_t* 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) );
|
||||||
|
|
||||||
|
value_t* vp = _cmScMatchValPtr(r,i,j,rn,cn);
|
||||||
|
|
||||||
|
// for each possible dir: up,left,up-left
|
||||||
|
for(m=1; m<kSmCnt; ++m)
|
||||||
|
if( vp->v[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( sfmatch_t* 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==std::max(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cmScMatchMidiEvtFlags( sfmatch_t* p, const loc_t* lp, unsigned evtIdx, char* s, unsigned sn )
|
||||||
|
{
|
||||||
|
const sfscore::loc_t* slp = sfscore::loc(p->scH,lp->scLocIdx);
|
||||||
|
|
||||||
|
assert( evtIdx < slp->evtCnt );
|
||||||
|
|
||||||
|
const sfscore::event_t* ep = slp->evtArray[evtIdx];
|
||||||
|
unsigned i = 0;
|
||||||
|
|
||||||
|
s[0] = 0;
|
||||||
|
|
||||||
|
if( cwIsFlag(ep->flags,sfscore::kEvenScFl) )
|
||||||
|
s[i++] = 'e';
|
||||||
|
|
||||||
|
if( cwIsFlag(ep->flags,sfscore::kTempoScFl) )
|
||||||
|
s[i++] = 't';
|
||||||
|
|
||||||
|
if( cwIsFlag(ep->flags,sfscore::kDynScFl) )
|
||||||
|
s[i++] = 'd';
|
||||||
|
|
||||||
|
if( cwIsFlag(ep->flags,sfscore::kGraceScFl) )
|
||||||
|
s[i++] = 'g';
|
||||||
|
|
||||||
|
s[i++] = 0;
|
||||||
|
|
||||||
|
assert( i <= sn );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _gen_match_test_input_from_score( sfscore::handle_t scoreH, unsigned begLocIdx, unsigned locCnt, double srate )
|
||||||
|
{
|
||||||
|
printf("[\n");
|
||||||
|
for(unsigned i=begLocIdx, k=0; i<begLocIdx+locCnt; ++i)
|
||||||
|
{
|
||||||
|
assert( begLocIdx < sfscore::loc_count(scoreH) );
|
||||||
|
|
||||||
|
const sfscore::loc_t* loc = sfscore::loc(scoreH,i);
|
||||||
|
|
||||||
|
for(unsigned j=0; j<loc->evtCnt; ++j)
|
||||||
|
{
|
||||||
|
const sfscore::event_t* e = loc->evtArray[j];
|
||||||
|
unsigned smpIdx = e->secs * srate;
|
||||||
|
printf("{ muid:%i smpIdx:%i pitch:%i vel:%i },\n",k,smpIdx,e->pitch,e->vel);
|
||||||
|
++k;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
printf("]\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::sfmatch::create( handle_t& hRef, sfscore::handle_t scoreH, unsigned maxScWndN, unsigned maxMidiWndN )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
sfmatch_t* p = nullptr;
|
||||||
|
|
||||||
|
if((rc = destroy(hRef)) != kOkRC )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
p = mem::allocZ<sfmatch_t>();
|
||||||
|
|
||||||
|
p->scH = scoreH;
|
||||||
|
p->mrn = maxMidiWndN + 1;
|
||||||
|
p->mcn = maxScWndN + 1;
|
||||||
|
p->mmn = maxMidiWndN;
|
||||||
|
p->msn = maxScWndN;
|
||||||
|
|
||||||
|
_cmScMatchInitLoc(p);
|
||||||
|
|
||||||
|
p->m = mem::resizeZ<value_t>( p->m, p->mrn*p->mcn );
|
||||||
|
p->pn = p->mrn + p->mcn;
|
||||||
|
p->p_mem = mem::resizeZ<path_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(unsigned i=0; i<p->pn; ++i)
|
||||||
|
{
|
||||||
|
p->p_mem[i].next = i<p->pn-1 ? p->p_mem + i + 1 : NULL;
|
||||||
|
p->p_opt[i].next = i<p->pn-1 ? p->p_opt + i + 1 : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hRef.set(p);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::sfmatch::destroy( handle_t& hRef )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
if(!hRef.isValid())
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
sfmatch_t* p = _handleToPtr(hRef);
|
||||||
|
|
||||||
|
if((rc = _destroy(p)) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::sfmatch::exec( handle_t h, unsigned locIdx, unsigned locN, const midi_t* midiV, unsigned midiN, double min_cost )
|
||||||
|
{
|
||||||
|
rc_t rc;
|
||||||
|
unsigned rn = midiN + 1;
|
||||||
|
unsigned cn = locN + 1;
|
||||||
|
sfmatch_t* p = _handleToPtr(h);
|
||||||
|
|
||||||
|
// set the DP matrix default values
|
||||||
|
if((rc = _cmScMatchInitMtx(p, rn, cn )) != kOkRC )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
// _cmScMatchCalcMtx() returns false if the score window exceeds the length of the score
|
||||||
|
if(!_cmScMatchCalcMtx(p,locIdx, midiV, rn, cn) )
|
||||||
|
return kEofRC;
|
||||||
|
|
||||||
|
//_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsigned cw::sfmatch::sync( handle_t h, unsigned i_opt, midi_t* midiBuf, unsigned midiN, unsigned* missCntPtr )
|
||||||
|
{
|
||||||
|
sfmatch_t* p = _handleToPtr(h);
|
||||||
|
path_t* cp = p->p_opt;
|
||||||
|
unsigned missCnt = 0;
|
||||||
|
unsigned esi = kInvalidIdx;
|
||||||
|
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 = kInvalidIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch( cp->code )
|
||||||
|
{
|
||||||
|
case kSmSubIdx:
|
||||||
|
midiBuf[ cp->ri-1 ].locIdx = i_opt + i;
|
||||||
|
midiBuf[ cp->ri-1 ].scEvtIdx = cp->scEvtIdx;
|
||||||
|
|
||||||
|
if( cwIsFlag(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 = kInvalidIdx;
|
||||||
|
++missCnt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( missCntPtr != NULL )
|
||||||
|
*missCntPtr = missCnt;
|
||||||
|
|
||||||
|
return esi;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cw::sfmatch::print_path( handle_t h, unsigned bsi, const midi_t* midiV )
|
||||||
|
{
|
||||||
|
assert( bsi != kInvalidIdx );
|
||||||
|
|
||||||
|
sfmatch_t* p = _handleToPtr(h);
|
||||||
|
path_t* cp = p->p_opt;
|
||||||
|
path_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)
|
||||||
|
{
|
||||||
|
loc_t* lp = p->loc + bsi + pp->ci;
|
||||||
|
if( pp->code!=kSmDelIdx )
|
||||||
|
{
|
||||||
|
if(lp->evtCnt > (unsigned)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 <= (int)p->locN);
|
||||||
|
loc_t* lp = p->loc + locIdx;
|
||||||
|
|
||||||
|
if( lp->evtCnt >= (unsigned)
|
||||||
|
i )
|
||||||
|
{
|
||||||
|
unsigned sn = 6;
|
||||||
|
char s[sn];
|
||||||
|
_cmScMatchMidiEvtFlags(p,lp,i-1,s,sn );
|
||||||
|
printf("%4s%-4s ",midi::midiToSciPitch(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);
|
||||||
|
loc_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 ",midi::midiToSciPitch(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)
|
||||||
|
{
|
||||||
|
char s[3];
|
||||||
|
int k = 0;
|
||||||
|
if( cwIsFlag(pp->flags,kSmMatchFl) )
|
||||||
|
s[k++] = 'm';
|
||||||
|
|
||||||
|
if( cwIsFlag(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 == kInvalidIdx )
|
||||||
|
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==kInvalidIdx || pp->scEvtIdx==kInvalidIdx )
|
||||||
|
printf("%4s%4s "," "," ");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const sfscore::event_t* ep = sfscore::event(p->scH, pp->scEvtIdx );
|
||||||
|
printf("%4i%4s ",ep->barNumb," ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
printf("\nsec: ");
|
||||||
|
|
||||||
|
// print seconds
|
||||||
|
unsigned begSmpIdx = kInvalidIdx;
|
||||||
|
for(pp=cp; pp!=NULL; pp=pp->next)
|
||||||
|
{
|
||||||
|
if( pp->code!=kSmInsIdx )
|
||||||
|
{
|
||||||
|
if( begSmpIdx == kInvalidIdx )
|
||||||
|
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");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cw::rc_t cw::sfmatch::test( const object_t* cfg )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
const char* score_csv_fname = nullptr;
|
||||||
|
sfscore::dyn_ref_t* dynRefA = nullptr;
|
||||||
|
unsigned dynRefN = 0;;
|
||||||
|
double srate = 48000.0;
|
||||||
|
const object_t* dynArrayNode = nullptr;
|
||||||
|
const object_t* perf = nullptr;
|
||||||
|
const object_t* gen_perf_example = nullptr;
|
||||||
|
bool gen_perf_enable_fl = false;
|
||||||
|
unsigned gen_perf_beg_loc_idx = 0;
|
||||||
|
unsigned gen_perf_loc_cnt = 0;
|
||||||
|
midi_t* midiA = nullptr;
|
||||||
|
unsigned maxScWndN = 10;
|
||||||
|
unsigned maxMidiWndN = 7;
|
||||||
|
sfscore::handle_t scoreH;
|
||||||
|
sfmatch::handle_t matchH;
|
||||||
|
|
||||||
|
// parse the test cfg
|
||||||
|
if((rc = cfg->getv( "cm_score_fname", score_csv_fname,
|
||||||
|
"srate", srate,
|
||||||
|
"dyn_ref", dynArrayNode,
|
||||||
|
"maxScWndN", maxScWndN,
|
||||||
|
"maxMidiWndN", maxMidiWndN,
|
||||||
|
"gen_perf_example", gen_perf_example,
|
||||||
|
"perf", perf)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"sfscore test parse params failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = gen_perf_example->getv( "enable_fl", gen_perf_enable_fl,
|
||||||
|
"beg_loc_idx", gen_perf_beg_loc_idx,
|
||||||
|
"loc_cnt", gen_perf_loc_cnt )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"sfscore test parse params 'gen_perf_example' failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = sfscore::parse_dyn_ref_cfg( dynArrayNode, dynRefA, dynRefN )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"The reference dynamics array parse failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = sfscore::create(scoreH,score_csv_fname,srate,dynRefA,dynRefN)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score test create failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( gen_perf_enable_fl )
|
||||||
|
{
|
||||||
|
_gen_match_test_input_from_score( scoreH, gen_perf_beg_loc_idx, gen_perf_loc_cnt, srate );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unsigned midiN = perf->child_count();
|
||||||
|
|
||||||
|
midiA = mem::allocZ<midi_t>(midiN);
|
||||||
|
|
||||||
|
for(unsigned i=0; i<midiN; ++i)
|
||||||
|
{
|
||||||
|
const object_t* node = perf->child_ele(i);
|
||||||
|
|
||||||
|
if((rc = node->getv("muid",midiA[i].muid,
|
||||||
|
"smpIdx",midiA[i].smpIdx,
|
||||||
|
"pitch", midiA[i].pitch,
|
||||||
|
"vel", midiA[i].vel)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"parsing 'perf' record at index '%i' failed.",i);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = create(matchH, scoreH, maxScWndN, maxMidiWndN )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score matcher create failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned locIdx = 0;
|
||||||
|
unsigned locN = maxScWndN;
|
||||||
|
unsigned perf_idx = 3;
|
||||||
|
unsigned perf_cnt = 4;
|
||||||
|
|
||||||
|
if((rc = exec(matchH, locIdx, locN, midiA + perf_idx, perf_cnt, DBL_MAX )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"score match failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned eli = kInvalidIdx;
|
||||||
|
unsigned missCnt = 0;
|
||||||
|
unsigned i_opt = locIdx;
|
||||||
|
if(( eli = cw::sfmatch::sync( matchH, i_opt, midiA + perf_idx, perf_cnt, &missCnt )) == kInvalidIdx )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"score match sync failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned bsi = locIdx;
|
||||||
|
print_path( matchH, bsi, midiA );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
sfmatch::destroy(matchH);
|
||||||
|
sfscore::destroy(scoreH);
|
||||||
|
mem::release(dynRefA);
|
||||||
|
mem::release(midiA);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
179
cwSfMatch.h
Normal file
179
cwSfMatch.h
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#ifndef cwScoreMatch_h
|
||||||
|
#define cwScoreMatch_h
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace sfmatch
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
kSmMinIdx, //
|
||||||
|
kSmSubIdx, // 'substitute' - may or may not match
|
||||||
|
kSmDelIdx, // 'delete' - delete a MIDI note
|
||||||
|
kSmInsIdx, // 'insert' - insert a space in the score
|
||||||
|
kSmCnt
|
||||||
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
kSmMatchFl = 0x01,
|
||||||
|
kSmTransFl = 0x02,
|
||||||
|
kSmTruePosFl = 0x04,
|
||||||
|
kSmFalsePosFl = 0x08,
|
||||||
|
kSmBarFl = 0x10,
|
||||||
|
kSmNoteFl = 0x20
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic Programming (DP) matrix element
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned v[kSmCnt]; // cost for each operation
|
||||||
|
unsigned flags; // cmSmMatchFl | cmSmTransFl
|
||||||
|
unsigned scEvtIdx;
|
||||||
|
} value_t;
|
||||||
|
|
||||||
|
// List record used to track a path through the DP matrix p->m[,]
|
||||||
|
typedef struct path_str
|
||||||
|
{
|
||||||
|
unsigned code; // kSmXXXIdx
|
||||||
|
unsigned ri; // matrix row index
|
||||||
|
unsigned ci; // matrix col index
|
||||||
|
unsigned flags; // cmSmMatchFl | cmSmTransFl
|
||||||
|
unsigned locIdx; // p->loc index or cmInvalidIdx
|
||||||
|
unsigned scEvtIdx; // scScore event index
|
||||||
|
struct path_str* next; //
|
||||||
|
} path_t;
|
||||||
|
|
||||||
|
typedef struct event_str
|
||||||
|
{
|
||||||
|
unsigned pitch; //
|
||||||
|
unsigned scEvtIdx; // scScore event index
|
||||||
|
} event_t;
|
||||||
|
|
||||||
|
// Score location record.
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned evtCnt; // count of score events at this location (i.e. a chord will have more than one event at a given location)
|
||||||
|
event_t* evtV; // evtV[evtCnt]
|
||||||
|
unsigned scLocIdx; // scH score location index
|
||||||
|
int barNumb; // bar number of this location
|
||||||
|
} loc_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned mni; // unique identifier for this MIDI note - used to recognize when the sfmatcher backtracks.
|
||||||
|
unsigned muid; // MIDI file event msg unique id (See cmMidiTrackMsg_t.uid)
|
||||||
|
unsigned smpIdx; // time stamp of this event
|
||||||
|
unsigned pitch; // MIDI note pitch
|
||||||
|
unsigned vel; // " " velocity
|
||||||
|
unsigned locIdx; // location assoc'd with this MIDI evt (kInvalidIdx if not a matching or non-matching 'substitute')
|
||||||
|
unsigned scEvtIdx; // sfscore event index assoc'd with this event
|
||||||
|
} midi_t;
|
||||||
|
|
||||||
|
typedef struct sfmatch_str
|
||||||
|
{
|
||||||
|
sfscore::handle_t scH; // score handle
|
||||||
|
|
||||||
|
unsigned locN; // Same as sfscore::loc_count()
|
||||||
|
loc_t* loc; // loc[locN] One element for each score location
|
||||||
|
|
||||||
|
// DP matrix
|
||||||
|
unsigned mrn; // max m[] row count (midi)
|
||||||
|
unsigned rn; // cur m[] row count
|
||||||
|
unsigned mcn; // max m[] column count (score)
|
||||||
|
unsigned cn; // cur m[] column count
|
||||||
|
value_t* m; // m[mrn,mcn] DP matrix
|
||||||
|
|
||||||
|
unsigned mmn; // max length of midiBuf[] (mrn-1)
|
||||||
|
unsigned msn; // max length of score window (mcn-1)
|
||||||
|
|
||||||
|
unsigned pn; // mrn+mcn
|
||||||
|
path_t* p_mem; // pmem[ 2*pn ] - pre-allocated path memory
|
||||||
|
path_t* p_avl; // available path record linked list
|
||||||
|
path_t* p_cur; // current path linked list
|
||||||
|
path_t* p_opt; // p_opt[pn] - current best alignment as a linked list
|
||||||
|
double opt_cost; // last p_opt cost set by exec()
|
||||||
|
} sfmatch_t;
|
||||||
|
|
||||||
|
typedef handle<struct sfmatch_str> handle_t;
|
||||||
|
|
||||||
|
// This matcher determines the optimal alignment of a short list of MIDI notes
|
||||||
|
// within a longer score window. It is designed to work for a single
|
||||||
|
// limited size alignment.
|
||||||
|
//
|
||||||
|
// 1) This matcher cannot handle multiple instances of the same pitch occuring
|
||||||
|
// at the same 'location'.
|
||||||
|
//
|
||||||
|
// 2) Because each note of a chord is spread out over multiple locations, and
|
||||||
|
// there is no way to indicate that a note in the chord is already 'in-use'.
|
||||||
|
// If a MIDI note which is part of the chord is repeated, in error, it will
|
||||||
|
// appear to be correct (a positive match will be assigned to
|
||||||
|
// the second (and possible successive notes)).
|
||||||
|
|
||||||
|
|
||||||
|
// maxScWndN - max length of the score window (also the max value of 'locN' in subsequent calls to exec()).
|
||||||
|
// maxMidiWndN - max length of the performance (MIDI) buffer to compare to the score window
|
||||||
|
// (also the max value of 'midiN' in subsequent calls to exec()).
|
||||||
|
rc_t create( handle_t& hRef, sfscore::handle_t scoreH, unsigned maxScWndN, unsigned maxMidiWndN );
|
||||||
|
rc_t destroy( handle_t& hRef );
|
||||||
|
|
||||||
|
// Locate the position in p->loc[locIdx:locIdx+locN-1] which bests matches midiV[0:midiN].
|
||||||
|
// The result of this function is to update p_opt[]
|
||||||
|
// The optimal path p_opt[] will only be updated if the edit_cost associated 'midiV[0:midiN]'.
|
||||||
|
// with the best match is less than 'min_cost'.
|
||||||
|
// Set 'min_cost' to DBL_MAX to force p_opt[] to be updated.
|
||||||
|
// Returns kEofRC if locIdx + locN > p->locN - note that this is not necessarily an error.
|
||||||
|
rc_t exec( handle_t h, unsigned locIdx, unsigned locN, const midi_t* midiV, unsigned midiN, double min_cost );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This function updates the midiBuf[] fields 'locIdx' and 'scEvtIdx' after an alignment
|
||||||
|
// has been found via an earlier call to 'exec()'.
|
||||||
|
//
|
||||||
|
// Traverse the least cost path and:
|
||||||
|
//
|
||||||
|
// 1) Returns, 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 in midiBuf[].
|
||||||
|
//
|
||||||
|
// i_opt is index into p->loc[] of p->p_opt.
|
||||||
|
unsigned sync( handle_t h, unsigned i_opt, midi_t* midiBuf, unsigned midiN, unsigned* missCntPtr );
|
||||||
|
|
||||||
|
|
||||||
|
// Print a matched path.
|
||||||
|
// cp - pointer to the first path element
|
||||||
|
// bsi - score location index of the first element in the score window which was used to form the path.
|
||||||
|
// midiV - pointer to the first element of the MIDI buffer used to form the path.
|
||||||
|
void print_path( handle_t h, unsigned bsi, const midi_t* midiV );
|
||||||
|
|
||||||
|
// Returns the index into loc->evtV[] of pitch.
|
||||||
|
inline unsigned match_index( const loc_t* loc, unsigned pitch )
|
||||||
|
{
|
||||||
|
for(unsigned i=0; i<loc->evtCnt; ++i)
|
||||||
|
if( loc->evtV[i].pitch == pitch )
|
||||||
|
return i;
|
||||||
|
return kInvalidIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if 'pitch' occurs at loc.
|
||||||
|
inline bool is_match( const loc_t* loc, unsigned pitch ) { return match_index(loc,pitch) != kInvalidIdx; }
|
||||||
|
|
||||||
|
inline unsigned max_midi_wnd_count( handle_t h ) { return handleToPtr<handle_t,sfmatch_t>(h)->mmn; }
|
||||||
|
inline unsigned max_score_wnd_count( handle_t h ) { return handleToPtr<handle_t,sfmatch_t>(h)->msn; }
|
||||||
|
inline unsigned loc_count( handle_t h ) { return handleToPtr<handle_t,sfmatch_t>(h)->locN; }
|
||||||
|
inline const cw::sfmatch::loc_t* loc_base( handle_t h ) { return handleToPtr<handle_t,sfmatch_t>(h)->loc; }
|
||||||
|
inline double cost( handle_t h ) { return handleToPtr<handle_t,sfmatch_t>(h)->opt_cost; }
|
||||||
|
inline const cw::sfmatch::path_t* optimal_path( handle_t h ) { return handleToPtr<handle_t,sfmatch_t>(h)->p_opt; }
|
||||||
|
|
||||||
|
|
||||||
|
rc_t test( const object_t* cfg );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
569
cwSfTrack.cpp
Normal file
569
cwSfTrack.cpp
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
#include "cwCommon.h"
|
||||||
|
#include "cwLog.h"
|
||||||
|
#include "cwCommonImpl.h"
|
||||||
|
#include "cwMem.h"
|
||||||
|
#include "cwText.h"
|
||||||
|
#include "cwObject.h"
|
||||||
|
#include "cwMidi.h"
|
||||||
|
#include "cwMidiFile.h"
|
||||||
|
#include "cwFileSys.h"
|
||||||
|
#include "cwSfScore.h"
|
||||||
|
#include "cwSfMatch.h"
|
||||||
|
#include "cwSfTrack.h"
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace sftrack
|
||||||
|
{
|
||||||
|
sftrack_t* _handleToPtr( handle_t h )
|
||||||
|
{ return handleToPtr<handle_t,sftrack_t>(h); }
|
||||||
|
|
||||||
|
rc_t _destroy( sftrack_t* p )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
destroy(p->matchH);
|
||||||
|
mem::release(p->midiBuf);
|
||||||
|
mem::release(p->res);
|
||||||
|
mem::release(p);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _reset( sftrack_t* p, unsigned scLocIdx )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
p->mbi = max_midi_wnd_count(p->matchH);
|
||||||
|
p->mni = 0;
|
||||||
|
p->begSyncLocIdx = kInvalidIdx;
|
||||||
|
p->s_opt = DBL_MAX;
|
||||||
|
p->missCnt = 0;
|
||||||
|
p->scanCnt = 0;
|
||||||
|
p->ri = 0;
|
||||||
|
p->eli = kInvalidIdx;
|
||||||
|
p->ili = 0;
|
||||||
|
|
||||||
|
unsigned locN = loc_count(p->matchH);
|
||||||
|
const sfmatch::loc_t* loc = loc_base(p->matchH);
|
||||||
|
|
||||||
|
// convert scLocIdx to an index into p->mp->loc[]
|
||||||
|
unsigned i = 0;
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
for(i=0; i<locN; ++i)
|
||||||
|
if( loc[i].scLocIdx == scLocIdx )
|
||||||
|
{
|
||||||
|
p->ili = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(locN>0);
|
||||||
|
if( i!=locN || scLocIdx==loc[locN-1].scLocIdx)
|
||||||
|
break;
|
||||||
|
|
||||||
|
scLocIdx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( i==locN)
|
||||||
|
{
|
||||||
|
rc = cwLogError(kOpFailRC, "Score matcher reset failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _input_midi( sftrack_t* p, unsigned smpIdx, unsigned muid, unsigned status, midi::byte_t d0, midi::byte_t d1 )
|
||||||
|
{
|
||||||
|
if( (status&0xf0) != midi::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(sfmatch::midi_t)*mi);
|
||||||
|
p->midiBuf[mi].locIdx = kInvalidIdx;
|
||||||
|
p->midiBuf[mi].scEvtIdx = kInvalidIdx;
|
||||||
|
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 _store_result( sftrack_t* p, unsigned locIdx, unsigned scEvtIdx, unsigned flags, const sfmatch::midi_t* mp )
|
||||||
|
{
|
||||||
|
// don't store missed score note results
|
||||||
|
assert( mp != NULL );
|
||||||
|
bool matchFl = cwIsFlag(flags,sfmatch::kSmMatchFl);
|
||||||
|
bool tpFl = locIdx!=kInvalidIdx && matchFl;
|
||||||
|
bool fpFl = locIdx==kInvalidIdx || matchFl==false;
|
||||||
|
result_t * rp = NULL;
|
||||||
|
unsigned i;
|
||||||
|
result_t r;
|
||||||
|
|
||||||
|
assert( tpFl==false || (tpFl==true && locIdx != kInvalidIdx ) );
|
||||||
|
|
||||||
|
// 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; i<p->ri; ++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 ? sfmatch::kSmTruePosFl : 0) | (fpFl ? sfmatch::kSmFalsePosFl : 0);
|
||||||
|
|
||||||
|
if( p->cbFunc != NULL )
|
||||||
|
p->cbFunc(p->cbArg,rp);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned _scan( sftrack_t* p, unsigned bli, unsigned hopCnt )
|
||||||
|
{
|
||||||
|
|
||||||
|
assert( p->matchH.isValid() && sfmatch::max_midi_wnd_count(p->matchH) > 0 );
|
||||||
|
|
||||||
|
unsigned i_opt = kInvalidIdx;
|
||||||
|
double s_opt = DBL_MAX;
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
unsigned mmn = sfmatch::max_midi_wnd_count(p->matchH);
|
||||||
|
unsigned msn = sfmatch::max_score_wnd_count(p->matchH);
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
// initialize the internal values set by this function
|
||||||
|
p->missCnt = 0;
|
||||||
|
p->eli = kInvalidIdx;
|
||||||
|
p->s_opt = DBL_MAX;
|
||||||
|
|
||||||
|
// if the MIDI buf is not full
|
||||||
|
if( p->mbi != 0 )
|
||||||
|
return kInvalidIdx;
|
||||||
|
|
||||||
|
// calc the edit distance from pitchV[] to a sliding score window
|
||||||
|
for(i=0; rc==kOkRC && (hopCnt==kInvalidCnt || i<hopCnt); ++i)
|
||||||
|
{
|
||||||
|
rc = sfmatch::exec(p->matchH, bli + i, msn, p->midiBuf, mmn, s_opt );
|
||||||
|
|
||||||
|
double opt_cost = cost(p->matchH);
|
||||||
|
switch(rc)
|
||||||
|
{
|
||||||
|
case kOkRC: // normal result
|
||||||
|
if( opt_cost < s_opt )
|
||||||
|
{
|
||||||
|
s_opt = opt_cost;
|
||||||
|
i_opt = bli + i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case kEofRC: // score window encountered the end of the score
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // error state
|
||||||
|
return kInvalidIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the cost assoc'd with i_opt
|
||||||
|
p->s_opt = s_opt;
|
||||||
|
|
||||||
|
if( i_opt == kInvalidIdx )
|
||||||
|
return kInvalidIdx;
|
||||||
|
|
||||||
|
|
||||||
|
// set the locIdx field in midiBuf[], trailing miss count and
|
||||||
|
// return the latest positive-match locIdx
|
||||||
|
p->eli = sfmatch::sync(p->matchH,i_opt,p->midiBuf,mmn,&p->missCnt);
|
||||||
|
|
||||||
|
// if no positive matches were found
|
||||||
|
if( p->eli == kInvalidIdx )
|
||||||
|
i_opt = kInvalidIdx;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// record result
|
||||||
|
for(const sfmatch::path_t* cp = optimal_path(p->matchH); cp!=NULL; cp=cp->next)
|
||||||
|
if( cp->code != sfmatch::kSmInsIdx )
|
||||||
|
_store_result(p, cp->locIdx, cp->scEvtIdx, cp->flags, p->midiBuf + cp->ri - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return i_opt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _step( sftrack_t* p )
|
||||||
|
{
|
||||||
|
unsigned pitch = p->midiBuf[ p->mn-1 ].pitch;
|
||||||
|
unsigned locIdx = kInvalidIdx;
|
||||||
|
unsigned pidx = kInvalidIdx;
|
||||||
|
unsigned locN = loc_count(p->matchH);
|
||||||
|
const sfmatch::loc_t* loc = loc_base(p->matchH);
|
||||||
|
|
||||||
|
// the tracker must be sync'd to step
|
||||||
|
if( p->eli == kInvalidIdx )
|
||||||
|
return cwLogError(kInvalidArgRC, "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 >= locN )
|
||||||
|
return kEofRC;
|
||||||
|
|
||||||
|
// attempt to match to next location first
|
||||||
|
if( (pidx = match_index(loc + p->eli + 1, pitch)) != kInvalidIdx )
|
||||||
|
{
|
||||||
|
locIdx = p->eli + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//
|
||||||
|
for(unsigned i=2; i<p->stepCnt; ++i)
|
||||||
|
{
|
||||||
|
// go forward
|
||||||
|
if( p->eli+i < locN && (pidx=match_index(loc + p->eli + i, pitch))!=kInvalidIdx )
|
||||||
|
{
|
||||||
|
locIdx = p->eli + i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go backward
|
||||||
|
if( p->eli >= (i-1) && (pidx=match_index(loc + p->eli - (i-1), pitch))!=kInvalidIdx )
|
||||||
|
{
|
||||||
|
locIdx = p->eli - (i-1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned scEvtIdx = locIdx==kInvalidIdx ? kInvalidIdx : loc[locIdx].evtV[pidx].scEvtIdx;
|
||||||
|
|
||||||
|
p->midiBuf[ p->mn-1 ].locIdx = locIdx;
|
||||||
|
p->midiBuf[ p->mn-1 ].scEvtIdx = scEvtIdx;
|
||||||
|
|
||||||
|
if( locIdx == kInvalidIdx )
|
||||||
|
++p->missCnt;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p->missCnt = 0;
|
||||||
|
p->eli = locIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the result
|
||||||
|
_store_result(p, locIdx, scEvtIdx, locIdx!=kInvalidIdx ? sfmatch::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 = _scan(p,begScanLocIdx,p->mn*2);
|
||||||
|
++p->scanCnt;
|
||||||
|
|
||||||
|
// if the scan failed find a match
|
||||||
|
if( bli == kInvalidIdx )
|
||||||
|
return cwLogError(kOpFailRC, "Scan resync. failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return kOkRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cw::rc_t cw::sftrack::create( handle_t& hRef,
|
||||||
|
double srate, // System sample rate.
|
||||||
|
sfscore::handle_t scH, // Score handle. See cmScore.h.
|
||||||
|
unsigned scWndN, // Length of the scores active search area. ** See Notes.
|
||||||
|
unsigned midiWndN, // Length of the MIDI active note buffer. ** See Notes.
|
||||||
|
callback_func_t cbFunc, // A cmScMatcherCb_t function to be called to notify the recipient of changes in the score matcher status.
|
||||||
|
void* cbArg ) // User argument to 'cbFunc'.
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
if((rc = destroy(hRef)) != kOkRC )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
if( midiWndN > scWndN )
|
||||||
|
return cwLogError(kInvalidArgRC, "The score alignment MIDI event buffer length (%i) must be less than the score window length (%i).",midiWndN,scWndN);
|
||||||
|
|
||||||
|
sftrack_t* p = mem::allocZ<sftrack_t>();
|
||||||
|
|
||||||
|
if(( rc = sfmatch::create(p->matchH,scH,scWndN,midiWndN)) != kOkRC )
|
||||||
|
{
|
||||||
|
cwLogError(rc,"sfmatch create failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->cbFunc = cbFunc;
|
||||||
|
p->cbArg = cbArg;
|
||||||
|
p->mn = midiWndN;
|
||||||
|
p->midiBuf = mem::resize<sfmatch::midi_t>(p->midiBuf,p->mn);
|
||||||
|
p->initHopCnt = 50;
|
||||||
|
p->stepCnt = 3;
|
||||||
|
p->maxMissCnt = p->stepCnt+1;
|
||||||
|
p->rn = 2 * event_count(scH);
|
||||||
|
p->res = mem::resize<result_t>(p->res,p->rn);
|
||||||
|
p->printFl = false;
|
||||||
|
|
||||||
|
_reset(p,0);
|
||||||
|
|
||||||
|
hRef.set(p);
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
if(rc != kOkRC )
|
||||||
|
_destroy(p);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::sftrack::destroy( handle_t& hRef )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
if(!hRef.isValid())
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
sftrack_t* p = _handleToPtr(hRef);
|
||||||
|
|
||||||
|
if((rc = _destroy(p)) != kOkRC )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
hRef.clear();
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::sftrack::reset( handle_t h, unsigned scLocIdx )
|
||||||
|
{
|
||||||
|
sftrack_t* p = _handleToPtr(h);
|
||||||
|
return _reset(p,scLocIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cw::rc_t cw::sftrack::exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned status, midi::byte_t d0, midi::byte_t d1, unsigned* scLocIdxPtr )
|
||||||
|
{
|
||||||
|
sftrack_t* p = _handleToPtr(h);
|
||||||
|
bool fl = p->mbi > 0;
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
unsigned org_eli = p->eli;
|
||||||
|
|
||||||
|
if( scLocIdxPtr != NULL )
|
||||||
|
*scLocIdxPtr = kInvalidIdx;
|
||||||
|
|
||||||
|
// update the MIDI buffer with the incoming note
|
||||||
|
if( _input_midi(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 = _scan(p,p->ili,p->initHopCnt)) == kInvalidIdx )
|
||||||
|
{
|
||||||
|
rc = kInvalidArgRC; // 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 = _step(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we lost sync
|
||||||
|
if( p->eli == kInvalidIdx )
|
||||||
|
{
|
||||||
|
// 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 )
|
||||||
|
{
|
||||||
|
const sfmatch::loc_t* loc = loc_base(p->matchH);
|
||||||
|
|
||||||
|
//printf("LOC:%i bar:%i\n",p->eli,loc[p->eli].barNumb);
|
||||||
|
*scLocIdxPtr = loc[p->eli].scLocIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cw::sftrack::print( handle_t h )
|
||||||
|
{
|
||||||
|
sftrack_t* p = _handleToPtr(h);
|
||||||
|
sfmatch::print_path( p->matchH, p->begSyncLocIdx, p->midiBuf );
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace sftrack
|
||||||
|
{
|
||||||
|
void _test_cb_func( void* arg, result_t* rp )
|
||||||
|
{
|
||||||
|
printf("mni:%i muid:%i loc:%i scevt:%i\n",rp->mni,rp->muid,rp->locIdx,rp->scEvtIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::sftrack::test( const object_t* cfg )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
const char* score_csv_fname = nullptr;
|
||||||
|
double srate = 48000.0;
|
||||||
|
unsigned maxScWndN = 10;
|
||||||
|
unsigned maxMidiWndN = 7;
|
||||||
|
bool report_midi_file_fl = false;
|
||||||
|
bool report_score_fl = false;
|
||||||
|
bool report_track_fl = false;
|
||||||
|
|
||||||
|
sfscore::dyn_ref_t* dynRefA = nullptr;
|
||||||
|
unsigned dynRefN = 0;;
|
||||||
|
const object_t* dynArrayNode = nullptr;
|
||||||
|
|
||||||
|
const object_t* perf = nullptr;
|
||||||
|
bool perf_enable_fl = false;
|
||||||
|
unsigned perf_loc_idx = 0;
|
||||||
|
const char* perf_midi_fname = nullptr;
|
||||||
|
|
||||||
|
|
||||||
|
const midi::file::trackMsg_t** midiMsgA = nullptr;
|
||||||
|
unsigned midiMsgN = 0;
|
||||||
|
sfscore::handle_t scoreH;
|
||||||
|
sftrack::handle_t trackH;
|
||||||
|
midi::file::handle_t mfH;
|
||||||
|
|
||||||
|
// parse the test cfg
|
||||||
|
if((rc = cfg->getv( "cm_score_fname", score_csv_fname,
|
||||||
|
"srate", srate,
|
||||||
|
"dyn_ref", dynArrayNode,
|
||||||
|
"maxScWndN", maxScWndN,
|
||||||
|
"maxMidiWndN", maxMidiWndN,
|
||||||
|
"report_midi_file_fl",report_midi_file_fl,
|
||||||
|
"report_score_fl",report_score_fl,
|
||||||
|
"report_track_fl",report_track_fl,
|
||||||
|
"perf", perf)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"sfscore test parse params failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = perf->getv( "enable_fl", perf_enable_fl,
|
||||||
|
"loc_idx", perf_loc_idx,
|
||||||
|
"midi_fname", perf_midi_fname )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"sfscore test parse params 'perf' failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamic reference
|
||||||
|
if((rc = sfscore::parse_dyn_ref_cfg( dynArrayNode, dynRefA, dynRefN )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"The reference dynamics array parse failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the score
|
||||||
|
if((rc = sfscore::create(scoreH,score_csv_fname,srate,dynRefA,dynRefN)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score test create failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( report_score_fl )
|
||||||
|
report(scoreH);
|
||||||
|
|
||||||
|
// create the score tracker
|
||||||
|
if((rc = create(trackH, srate, scoreH, maxScWndN, maxMidiWndN, _test_cb_func, nullptr )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"sftrack create failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = reset(trackH, perf_loc_idx )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"sftrack reset failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the MIDI file
|
||||||
|
if((rc = open(mfH, perf_midi_fname)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"midi file create failed on '%s'",cwStringNullGuard(perf_midi_fname));
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( report_midi_file_fl )
|
||||||
|
printMsgs(mfH,log::globalHandle());
|
||||||
|
|
||||||
|
midiMsgN = msgCount(mfH);
|
||||||
|
midiMsgA = msgArray(mfH);
|
||||||
|
|
||||||
|
// iterate through the MIDI file
|
||||||
|
for(unsigned i=0; i<midiMsgN; ++i)
|
||||||
|
{
|
||||||
|
unsigned scLocIdx = kInvalidIdx;
|
||||||
|
const midi::file::trackMsg_t* trk = midiMsgA[i];
|
||||||
|
unsigned smpIdx = 0;
|
||||||
|
|
||||||
|
// if this is a note on message
|
||||||
|
if( midi::isNoteOnStatus(trk->status) )
|
||||||
|
if((rc = exec(trackH, smpIdx, trk->uid, trk->status, trk->u.chMsgPtr->d0, trk->u.chMsgPtr->d1, &scLocIdx)) != kOkRC )
|
||||||
|
{
|
||||||
|
if( rc != kEofRC )
|
||||||
|
rc = cwLogError(rc,"tracker exec() failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
midi::file::close(mfH);
|
||||||
|
sftrack::destroy(trackH);
|
||||||
|
sfscore::destroy(scoreH);
|
||||||
|
mem::release(dynRefA);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
123
cwSfTrack.h
Normal file
123
cwSfTrack.h
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#ifndef cwSfTracker_h
|
||||||
|
#define cwSfTracker_h
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace sftrack
|
||||||
|
{
|
||||||
|
typedef struct result_str
|
||||||
|
{
|
||||||
|
unsigned locIdx; // index into cmScMatch_t.loc[]
|
||||||
|
unsigned scEvtIdx; // score event index
|
||||||
|
unsigned mni; // index of the performed MIDI event associated with this score location
|
||||||
|
unsigned smpIdx; // sample time index of performed MIDI event
|
||||||
|
unsigned muid; // MIDI file event msg unique id (See cmMidiTrackMsg_t.uid)
|
||||||
|
unsigned pitch; // performed pitch
|
||||||
|
unsigned vel; // performed velocity
|
||||||
|
unsigned flags; // smTruePosFl | smFalsePosFl
|
||||||
|
} result_t;
|
||||||
|
|
||||||
|
|
||||||
|
struct sftrack_str;
|
||||||
|
typedef void (*callback_func_t)( void* arg, result_t* rp );
|
||||||
|
|
||||||
|
typedef struct sftrack_str
|
||||||
|
{
|
||||||
|
callback_func_t cbFunc;
|
||||||
|
void* cbArg;
|
||||||
|
sfmatch::handle_t matchH;
|
||||||
|
unsigned mn; // size of midiBuf[]
|
||||||
|
sfmatch::midi_t* midiBuf; // midiBuf[mn]
|
||||||
|
|
||||||
|
result_t* res; // res[rn]
|
||||||
|
unsigned rn; // length of res[] (set to 2*score event count)
|
||||||
|
unsigned ri; // next avail res[] recd.
|
||||||
|
|
||||||
|
double s_opt; //
|
||||||
|
unsigned missCnt; // current count of consecutive trailing non-matches
|
||||||
|
unsigned ili; // index into loc[] to start scan following reset
|
||||||
|
unsigned eli; // index into loc[] of the last positive match.
|
||||||
|
unsigned mni; // current count of MIDI events since the last call to cmScMatcherReset()
|
||||||
|
unsigned mbi; // index of oldest MIDI event in midiBuf[]; stays at 0 when the buffer is full.
|
||||||
|
unsigned begSyncLocIdx; // start of score window, in mp->loc[], of best match in previous scan
|
||||||
|
unsigned initHopCnt; // max window hops during the initial (when the MIDI buffer fills for first time) sync scan
|
||||||
|
unsigned stepCnt; // count of forward/backward score loc's to examine for a match during cmScMatcherStep().
|
||||||
|
unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a scan.
|
||||||
|
unsigned scanCnt; // current count of times a resync-scan was executed during cmScMatcherStep()
|
||||||
|
|
||||||
|
bool printFl;
|
||||||
|
} sftrack_t;
|
||||||
|
|
||||||
|
typedef handle<struct sftrack_str> handle_t;
|
||||||
|
|
||||||
|
rc_t create( handle_t& hRef,
|
||||||
|
double srate, // System sample rate.
|
||||||
|
sfscore::handle_t scH, // Score handle. See cmScore.h.
|
||||||
|
unsigned scWndN, // Length of the scores active search area. ** See Notes.
|
||||||
|
unsigned midiWndN, // Length of the MIDI active note buffer. ** See Notes.
|
||||||
|
callback_func_t cbFunc, // A cmScMatcherCb_t function to be called to notify the recipient of changes in the score matcher status.
|
||||||
|
void* cbArg ); // User argument to 'cbFunc'.
|
||||||
|
|
||||||
|
// Notes:
|
||||||
|
// The cmScMatcher maintains an internal cmScMatch object which is used to attempt to find the
|
||||||
|
// best match between the current MIDI active note buffer and the current score search area.
|
||||||
|
// 'scWndN' is used to set the cmScMatch 'locN' argument.
|
||||||
|
// 'midiWndN' sets the length of the MIDI FIFO which is used to match to the score with
|
||||||
|
// each recceived MIDI note.
|
||||||
|
// 'midiWndN' must be <= 'scWndN'.
|
||||||
|
|
||||||
|
rc_t destroy( handle_t& hRef );
|
||||||
|
|
||||||
|
// 'scLocIdx' is a score index as used by cmScoreLoc(scH) not into p->mp->loc[].
|
||||||
|
rc_t reset( handle_t h, unsigned scLocIdx );
|
||||||
|
|
||||||
|
// Slide a score window 'hopCnt' times, beginning at 'bli' (an
|
||||||
|
// index into p->mp->loc[]) looking for the best match to p->midiBuf[].
|
||||||
|
// The score window contain scWndN (p->mp->mcn-1) score locations.
|
||||||
|
// Returns the index into p->mp->loc[] of the start of the best
|
||||||
|
// match score window. The score associated
|
||||||
|
// with this match is stored in s_opt.
|
||||||
|
//unsigned scan( handle_t h, unsigned bli, unsigned hopCnt );
|
||||||
|
|
||||||
|
// Step forward/back by p->stepCnt from p->eli.
|
||||||
|
// p->eli must therefore be valid prior to calling this function.
|
||||||
|
// If more than p->maxMissCnt consecutive MIDI events are
|
||||||
|
// missed then automatically run cmScAlignScan().
|
||||||
|
// Return cmEofRC if the end of the score is encountered.
|
||||||
|
// Return cmSubSysFailRC if an internal scan resync. failed.
|
||||||
|
//rc_t step( handle_t h );
|
||||||
|
|
||||||
|
// This function calls cmScMatcherScan() and cmScMatcherStep() internally.
|
||||||
|
// If 'status' is not kNonMidiMdId then the function returns without changing the
|
||||||
|
// state of the object. In other words the matcher only recognizes MIDI note-on messages.
|
||||||
|
// If the MIDI note passed by the call results in a successful match then
|
||||||
|
// p->eli will be updated to the location in p->mp->loc[] of the latest
|
||||||
|
// match, the MIDI note in p->midiBuf[] associated with this match
|
||||||
|
// will be assigned a valid locIdx and scLocIdx values, and *scLocIdxPtr
|
||||||
|
// will be set with the matched scLocIdx of the match.
|
||||||
|
// If this call does not result in a successful match *scLocIdxPtr is set
|
||||||
|
// to cmInvalidIdx.
|
||||||
|
// 'muid' is the unique id associated with this MIDI event under the circumstances
|
||||||
|
// that the event came from a MIDI file. See cmMidiFile.h cmMidiTrackMsg_t.uid.
|
||||||
|
// Return:
|
||||||
|
// cmOkRC - Continue processing MIDI events.
|
||||||
|
// cmEofRC - The end of the score was encountered.
|
||||||
|
// cmInvalidArgRC - scan failed or the object was in an invalid state to attempt a match.
|
||||||
|
// cmSubSysFailRC - a scan resync failed in cmScMatcherStep().
|
||||||
|
rc_t exec( handle_t h,
|
||||||
|
unsigned smpIdx,
|
||||||
|
unsigned muid,
|
||||||
|
unsigned status,
|
||||||
|
midi::byte_t d0,
|
||||||
|
midi::byte_t d1,
|
||||||
|
unsigned* scLocIdxPtr );
|
||||||
|
|
||||||
|
void print( handle_t h );
|
||||||
|
|
||||||
|
rc_t test( const object_t* cfg );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user