cwSfMatch.h/cpp, cwSfTrack.h/cpp, Makefile.am : Initial commit.

This commit is contained in:
kevin 2023-08-05 12:35:29 -04:00
parent e1d84b2861
commit 5e3130503d
5 changed files with 1770 additions and 2 deletions

View File

@ -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
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
libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwSfScoreParser.cpp src/libcw/cwSfScore.cpp
libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h
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
libcwSRC += src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScoreFollow.cpp

894
cwSfMatch.cpp Normal file
View 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
View 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
View 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
View 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