cwScoreFollow2.h/cpp,cwScoreFollow2Test.h/cpp,Makefile.am : Initial commit.
This commit is contained in:
parent
c789d70ce2
commit
3ced2ba0fb
@ -86,6 +86,9 @@ libcwSRC += src/libcw/cwSfMatch.cpp src/libcw/cwSfTrack.cpp src/libcw/cwSfAnalys
|
|||||||
libcwHDR += src/libcw/cwScoreFollowerPerf.h src/libcw/cwScoreFollower.h src/libcw/cwPerfMeas.h src/libcw/cwScoreFollowTest.h
|
libcwHDR += src/libcw/cwScoreFollowerPerf.h src/libcw/cwScoreFollower.h src/libcw/cwPerfMeas.h src/libcw/cwScoreFollowTest.h
|
||||||
libcwSRC += src/libcw/cwScoreFollower.cpp src/libcw/cwPerfMeas.cpp src/libcw/cwScoreFollowTest.cpp
|
libcwSRC += src/libcw/cwScoreFollower.cpp src/libcw/cwPerfMeas.cpp src/libcw/cwScoreFollowTest.cpp
|
||||||
|
|
||||||
|
libcwHDR += src/libcw/cwScoreFollow2Test.h src/libcw/cwScoreFollow2.h
|
||||||
|
libcwSRC += src/libcw/cwScoreFollow2Test.cpp src/libcw/cwScoreFollow2.cpp
|
||||||
|
|
||||||
libcwHDR += src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScoreFollow.h
|
libcwHDR += 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
|
||||||
|
|
||||||
|
966
cwScoreFollow2.cpp
Normal file
966
cwScoreFollow2.cpp
Normal file
@ -0,0 +1,966 @@
|
|||||||
|
|
||||||
|
|
||||||
|
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
||||||
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
||||||
|
#include "cwCommon.h"
|
||||||
|
#include "cwLog.h"
|
||||||
|
#include "cwCommonImpl.h"
|
||||||
|
#include "cwTest.h"
|
||||||
|
#include "cwMem.h"
|
||||||
|
#include "cwText.h"
|
||||||
|
#include "cwObject.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "cwMidi.h"
|
||||||
|
#include "cwTime.h"
|
||||||
|
#include "cwFile.h"
|
||||||
|
#include "cwCsv.h"
|
||||||
|
#include "cwVectOps.h"
|
||||||
|
|
||||||
|
#include "cwDynRefTbl.h"
|
||||||
|
#include "cwScoreParse.h"
|
||||||
|
#include "cwSfScore.h"
|
||||||
|
#include "cwSfTrack.h"
|
||||||
|
#include "cwPerfMeas.h"
|
||||||
|
#include "cwPianoScore.h"
|
||||||
|
#include "cwScoreFollow2.h"
|
||||||
|
|
||||||
|
#define INVALID_SEC (-1.0)
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace score_follow_2
|
||||||
|
{
|
||||||
|
struct sf_str;
|
||||||
|
|
||||||
|
typedef struct trkr_str
|
||||||
|
{
|
||||||
|
struct sf_str* sf;
|
||||||
|
|
||||||
|
unsigned new_note_idx;
|
||||||
|
|
||||||
|
bool end_fl;
|
||||||
|
|
||||||
|
unsigned* note_match_cntA; // note_match_cntA[ sf->noteN ]
|
||||||
|
unsigned* loc_match_cntA; // loc_match_cntA[ sf->locN ]
|
||||||
|
float* expV; // expV[ sf->pitchN ]
|
||||||
|
|
||||||
|
unsigned prv_loc_idx; // loc reported on the previous cycle
|
||||||
|
unsigned exp_loc_idx; // expected location on this cycle
|
||||||
|
|
||||||
|
unsigned search_bni; // current search window
|
||||||
|
unsigned search_eni; //
|
||||||
|
|
||||||
|
double beg_score_sec; // score time of the first match
|
||||||
|
double beg_perf_sec; // perf time of the first match
|
||||||
|
|
||||||
|
double time_delta_sum;
|
||||||
|
unsigned time_delta_cnt;
|
||||||
|
double time_fact; // score_time * time_fact = perf_time
|
||||||
|
|
||||||
|
double prv_match_perf_sec;
|
||||||
|
unsigned decay_cnt;
|
||||||
|
|
||||||
|
} trkr_t;
|
||||||
|
|
||||||
|
typedef struct aff_str
|
||||||
|
{
|
||||||
|
unsigned loc_id; // location where this affinity function should be applied
|
||||||
|
unsigned bni; // pitch index associated with envA[0]
|
||||||
|
float* envA; // envA[ envN ]
|
||||||
|
unsigned envN; // count of pitch cells covered by envA[]
|
||||||
|
} aff_t;
|
||||||
|
|
||||||
|
typedef struct wnd_str
|
||||||
|
{
|
||||||
|
unsigned loc_id; // location where this search window is used
|
||||||
|
unsigned bni; // window start pitch index
|
||||||
|
unsigned eni; // window end pitch index
|
||||||
|
} wnd_t;
|
||||||
|
|
||||||
|
typedef struct loc_str
|
||||||
|
{
|
||||||
|
unsigned loc_id; // id this location represents
|
||||||
|
unsigned meas_num; // measure number of this location
|
||||||
|
double sec; // score time in seconds
|
||||||
|
unsigned* note_idxA; // note_idxA[ note_idxN ] indexes into noteA[] of notes starting at this location
|
||||||
|
unsigned note_idxN; //
|
||||||
|
} loc_t;
|
||||||
|
|
||||||
|
typedef struct note_str
|
||||||
|
{
|
||||||
|
unsigned uid; // unique id identifying this note
|
||||||
|
unsigned loc_id; // location this note falls on
|
||||||
|
unsigned pitch; // note of this note
|
||||||
|
unsigned vel; // velocity of this note
|
||||||
|
} note_t;
|
||||||
|
|
||||||
|
typedef struct result_str
|
||||||
|
{
|
||||||
|
unsigned perf_uid;
|
||||||
|
unsigned perf_pitch;
|
||||||
|
unsigned perf_vel;
|
||||||
|
unsigned match_loc_id;
|
||||||
|
} result_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct sf_str
|
||||||
|
{
|
||||||
|
args_t args;
|
||||||
|
|
||||||
|
loc_t* locA; // locA[ locN ]
|
||||||
|
unsigned* locMapA; // locMapA[ max_loc_id ] maps loc_id to loc_index
|
||||||
|
aff_t* loc_affA; // loc_affA[ locN ]
|
||||||
|
wnd_t* loc_wndA; // loc_wndA[ locN ]
|
||||||
|
unsigned locAllocN; //
|
||||||
|
unsigned locN; //
|
||||||
|
unsigned max_loc_id; //
|
||||||
|
|
||||||
|
note_t* noteA; // noteA[ noteN ]
|
||||||
|
unsigned noteAllocN; //
|
||||||
|
unsigned noteN; //
|
||||||
|
|
||||||
|
unsigned beg_loc_id;
|
||||||
|
unsigned end_loc_id;
|
||||||
|
|
||||||
|
result_t* resultA; // resultA[ resultN ]
|
||||||
|
unsigned resultAllocN;
|
||||||
|
unsigned resultN;
|
||||||
|
|
||||||
|
trkr_t* trk;
|
||||||
|
|
||||||
|
} sf_t;
|
||||||
|
|
||||||
|
|
||||||
|
//================================================================================================================
|
||||||
|
//
|
||||||
|
// trk_t
|
||||||
|
//
|
||||||
|
|
||||||
|
void _trkr_destroy( trkr_t* trk )
|
||||||
|
{
|
||||||
|
if( trk != nullptr )
|
||||||
|
{
|
||||||
|
mem::release(trk->note_match_cntA);
|
||||||
|
mem::release(trk->loc_match_cntA);
|
||||||
|
mem::release(trk->expV);
|
||||||
|
mem::release(trk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _trkr_apply_affinity( trkr_t* trk, unsigned loc_id )
|
||||||
|
{
|
||||||
|
assert( loc_id <= trk->sf->max_loc_id );
|
||||||
|
|
||||||
|
unsigned loc_idx = trk->sf->locMapA[ loc_id ];
|
||||||
|
const aff_t* a = trk->sf->loc_affA + loc_idx;
|
||||||
|
|
||||||
|
assert( a->loc_id == loc_id );
|
||||||
|
|
||||||
|
//printf("apply: %i %i : %i %i : decay:%i\n",loc_id,loc_idx,a->bni, a->bni + a->envN - 1, trk->decay_cnt);
|
||||||
|
trk->decay_cnt = 0;
|
||||||
|
|
||||||
|
for(unsigned i=0; i<a->envN; ++i)
|
||||||
|
trk->expV[ a->bni + i ] += a->envA[i];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _trkr_reset( trkr_t* trk, unsigned beg_loc_id )
|
||||||
|
{
|
||||||
|
assert( beg_loc_id <= trk->sf->max_loc_id );
|
||||||
|
|
||||||
|
trk->new_note_idx = 0;
|
||||||
|
trk->end_fl = false;
|
||||||
|
|
||||||
|
//printf("reset expV: %i\n",beg_loc_id);
|
||||||
|
|
||||||
|
for(unsigned ni=0; ni<trk->sf->noteN; ++ni)
|
||||||
|
{
|
||||||
|
trk->note_match_cntA[ni] = 0;
|
||||||
|
trk->expV[ni] = 0;
|
||||||
|
}
|
||||||
|
for(unsigned li=0; li<trk->sf->locN; ++li)
|
||||||
|
trk->loc_match_cntA[li] = 0;
|
||||||
|
|
||||||
|
trk->prv_loc_idx = kInvalidIdx;
|
||||||
|
trk->exp_loc_idx = trk->sf->locMapA[ beg_loc_id ];
|
||||||
|
|
||||||
|
trk->search_bni = kInvalidIdx; // current search window
|
||||||
|
trk->search_eni = kInvalidIdx; //
|
||||||
|
|
||||||
|
trk->beg_score_sec = INVALID_SEC; // score time of the first match
|
||||||
|
trk->beg_perf_sec = INVALID_SEC; // perf time of the first match
|
||||||
|
|
||||||
|
trk->time_delta_sum = 0;
|
||||||
|
trk->time_delta_cnt = 0;
|
||||||
|
trk->time_fact = 1.0; // score_time * time_fact = perf_time
|
||||||
|
|
||||||
|
trk->prv_match_perf_sec = INVALID_SEC;
|
||||||
|
|
||||||
|
_trkr_apply_affinity(trk,beg_loc_id);
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return kOkRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
trkr_t* _trkr_create( sf_t* sf )
|
||||||
|
{
|
||||||
|
trkr_t* trk = mem::allocZ<trkr_t>();
|
||||||
|
|
||||||
|
trk->sf = sf;
|
||||||
|
trk->note_match_cntA = mem::allocZ<unsigned>(sf->noteN);
|
||||||
|
trk->loc_match_cntA = mem::allocZ<unsigned>(sf->locN);
|
||||||
|
trk->expV = mem::allocZ<float>(sf->noteN);
|
||||||
|
|
||||||
|
_trkr_reset(trk,sf->locA[0].loc_id);
|
||||||
|
|
||||||
|
return trk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _trkr_rpt_debug( trkr_t* trk, unsigned pitch, unsigned vel, unsigned loc_idx )
|
||||||
|
{
|
||||||
|
unsigned loc_id = trk->sf->locA[loc_idx].loc_id;
|
||||||
|
|
||||||
|
printf("loc:%i pitch:%i vel:%i bni:%i eni:%i\n",loc_id,pitch,vel,trk->search_bni,trk->search_eni);
|
||||||
|
|
||||||
|
for(unsigned ni=trk->search_bni; ni<=trk->search_eni; ++ni)
|
||||||
|
printf("%s%4i%s ", trk->sf->noteA[ni].loc_id==loc_id ? "(":"", trk->note_match_cntA[ni], trk->sf->noteA[ni].loc_id==loc_id ? ")":"");
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for(unsigned ni=trk->search_bni; ni<=trk->search_eni; ++ni)
|
||||||
|
printf("%s%4.2f%s ", trk->sf->noteA[ni].loc_id==loc_id ? "(":"", trk->expV[ni], trk->sf->noteA[ni].loc_id==loc_id ? ")":"");
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for(unsigned ni=trk->search_bni; ni<=trk->search_eni; ++ni)
|
||||||
|
printf("%s%4i%s ", trk->sf->noteA[ni].loc_id==loc_id ? "(":"", trk->sf->noteA[ni].pitch, trk->sf->noteA[ni].loc_id==loc_id ? ")":"");
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _trkr_on_new_note( trkr_t* trk, double sec, unsigned pitch, unsigned vel, bool rpt_fl, unsigned& matched_loc_id_ref )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
double d_corr_sec = 0.0;
|
||||||
|
bool d_corr_sec_valid_fl = false;
|
||||||
|
double d_match_score_sec = 0.0;
|
||||||
|
bool d_match_score_sec_valid_fl = false;
|
||||||
|
double d_match_perf_sec = 0.0;
|
||||||
|
bool d_match_perf_sec_valid_fl = false;
|
||||||
|
int d_loc_id = 0;
|
||||||
|
bool d_loc_id_valid_fl = false;
|
||||||
|
unsigned prv_loc_id = trk->prv_loc_idx==kInvalidIdx ? kInvalidIdx : trk->sf->locA[ trk->prv_loc_idx ].loc_id;
|
||||||
|
unsigned match_loc_id = kInvalidId;
|
||||||
|
unsigned exp_loc_id = kInvalidId;
|
||||||
|
const char* rpt_status = "";
|
||||||
|
unsigned match_ni = kInvalidIdx;
|
||||||
|
double match_val = 0;
|
||||||
|
|
||||||
|
matched_loc_id_ref = kInvalidId;
|
||||||
|
|
||||||
|
assert( trk->exp_loc_idx != kInvalidIdx && trk->exp_loc_idx < trk->sf->locN );
|
||||||
|
|
||||||
|
trk->search_bni = trk->sf->loc_wndA[ trk->exp_loc_idx ].bni;
|
||||||
|
trk->search_eni = trk->sf->loc_wndA[ trk->exp_loc_idx ].eni;
|
||||||
|
|
||||||
|
// set 'match_ni' to the best match candidate
|
||||||
|
for(unsigned ni=trk->search_bni; ni<=trk->search_eni; ++ni)
|
||||||
|
if( trk->note_match_cntA[ni]==0 && trk->sf->noteA[ni].pitch==pitch && (match_ni==kInvalidIdx || trk->expV[ni]>match_val))
|
||||||
|
{
|
||||||
|
match_ni = ni;
|
||||||
|
match_val = trk->expV[ni];
|
||||||
|
}
|
||||||
|
|
||||||
|
//if( pitch==33 )
|
||||||
|
// _trkr_rpt_debug(trk,pitch,vel,trk->exp_loc_idx);
|
||||||
|
|
||||||
|
// if no match candidate was found
|
||||||
|
if( match_ni == kInvalidIdx )
|
||||||
|
{
|
||||||
|
rpt_status = "spurious";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// get the loc_id that we expected to match
|
||||||
|
exp_loc_id = trk->exp_loc_idx==kInvalidIdx ? kInvalidIdx : trk->sf->locA[ trk->exp_loc_idx ].loc_id;
|
||||||
|
|
||||||
|
// get attributes of the candidate match
|
||||||
|
match_loc_id = trk->sf->noteA[match_ni].loc_id;
|
||||||
|
unsigned match_loc_idx= trk->sf->locMapA[match_loc_id];
|
||||||
|
double match_sec = trk->sf->locA[match_loc_idx].sec;
|
||||||
|
|
||||||
|
// set d_loc_id to the diff between the matched loc and the expected match loc
|
||||||
|
if( exp_loc_id != kInvalidIdx )
|
||||||
|
{
|
||||||
|
d_loc_id = (int)match_loc_id - (int)exp_loc_id;
|
||||||
|
d_loc_id_valid_fl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is the first match since a reset then record this as the first match
|
||||||
|
// TODO: what if this point gets rejected
|
||||||
|
if( trk->beg_score_sec == INVALID_SEC )
|
||||||
|
{
|
||||||
|
trk->beg_score_sec = match_sec;
|
||||||
|
trk->beg_perf_sec = sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// track the score time diff. between this match and the prev score match
|
||||||
|
if( trk->prv_loc_idx!=kInvalidIdx )
|
||||||
|
{
|
||||||
|
// delta score match time between this match and prev match
|
||||||
|
double prv_score_sec = trk->sf->locA[ trk->prv_loc_idx ].sec;
|
||||||
|
d_match_score_sec = match_sec - prv_score_sec;
|
||||||
|
d_match_score_sec_valid_fl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// track the perf time diff. between this score and the prev perf match
|
||||||
|
if( trk->prv_match_perf_sec != INVALID_SEC )
|
||||||
|
{
|
||||||
|
// delta perf time between this match and prev match
|
||||||
|
d_match_perf_sec = sec - trk->prv_match_perf_sec;
|
||||||
|
d_match_perf_sec_valid_fl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there the delta score/perf match time's are valid then estimate the tempo corrected delta match time
|
||||||
|
if( d_match_score_sec_valid_fl && d_match_perf_sec_valid_fl )
|
||||||
|
{
|
||||||
|
double d_corr_match_score_sec = d_match_score_sec / trk->time_fact;
|
||||||
|
d_corr_sec = d_match_perf_sec - d_corr_match_score_sec;
|
||||||
|
d_corr_sec_valid_fl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is a high confidence match then update the score->perf time tempo correction factor
|
||||||
|
if( d_loc_id_valid_fl && 0 <= d_loc_id && d_loc_id < trk->sf->args.d_loc_stats_thresh )
|
||||||
|
{
|
||||||
|
if( sec - trk->beg_perf_sec > 0 && (match_sec - trk->beg_score_sec) > 0 )
|
||||||
|
{
|
||||||
|
trk->time_delta_sum += (match_sec - trk->beg_score_sec)/(sec - trk->beg_perf_sec);
|
||||||
|
trk->time_delta_cnt += 1;
|
||||||
|
trk->time_fact = trk->time_delta_sum / trk->time_delta_cnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the thresh flags that are violated
|
||||||
|
bool lo_time_thresh_fl = d_corr_sec_valid_fl && fabs(d_corr_sec) > trk->sf->args.d_sec_err_thresh_lo;
|
||||||
|
bool hi_time_thresh_fl = d_loc_id_valid_fl && d_loc_id>0 && d_corr_sec_valid_fl && fabs(d_corr_sec) > trk->sf->args.d_sec_err_thresh_hi;
|
||||||
|
bool lo_loc_thresh_fl = d_loc_id_valid_fl && abs(d_loc_id) > trk->sf->args.d_loc_thresh_lo;
|
||||||
|
bool hi_loc_thresh_fl = d_loc_id_valid_fl && abs(d_loc_id) > trk->sf->args.d_loc_thresh_hi;
|
||||||
|
|
||||||
|
// if this match breaks the threshold rules ....
|
||||||
|
if( (lo_time_thresh_fl && lo_loc_thresh_fl) || hi_loc_thresh_fl || hi_time_thresh_fl )
|
||||||
|
{
|
||||||
|
match_ni = kInvalidIdx; // ... then reject the match
|
||||||
|
rpt_status = "rejected";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ... the match was accepted
|
||||||
|
|
||||||
|
trk->prv_match_perf_sec = sec;
|
||||||
|
trk->prv_loc_idx = match_loc_idx;
|
||||||
|
trk->loc_match_cntA[ match_loc_idx ] += 1;
|
||||||
|
trk->note_match_cntA[ match_ni ] += 1;
|
||||||
|
|
||||||
|
matched_loc_id_ref = match_loc_id;
|
||||||
|
|
||||||
|
// notice if we arrived at the end of the score tracking range
|
||||||
|
if( match_loc_id >= trk->sf->end_loc_id )
|
||||||
|
{
|
||||||
|
trk->end_fl = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
// select the next expected location by advancing to the next location that has not yet been matched
|
||||||
|
unsigned exp_loc_idx = match_loc_idx;
|
||||||
|
while( exp_loc_idx < trk->sf->locN && trk->loc_match_cntA[exp_loc_idx] >= trk->sf->locA[exp_loc_idx].note_idxN)
|
||||||
|
++exp_loc_idx;
|
||||||
|
|
||||||
|
// if we arrived at the end of the score
|
||||||
|
if( exp_loc_idx >= trk->sf->locN )
|
||||||
|
trk->end_fl = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
exp_loc_id = trk->sf->locA[ exp_loc_idx ].loc_id;
|
||||||
|
|
||||||
|
// apply the affinity envelope at the exected location
|
||||||
|
_trkr_apply_affinity(trk,exp_loc_id);
|
||||||
|
|
||||||
|
// update the expected loc. for the next cycle
|
||||||
|
trk->exp_loc_idx = exp_loc_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( rpt_fl )
|
||||||
|
{
|
||||||
|
|
||||||
|
printf("%4i pitch:%3i ",trk->new_note_idx, pitch);
|
||||||
|
if( prv_loc_id != kInvalidIdx )
|
||||||
|
printf("LOC: prv:%4i ",prv_loc_id);
|
||||||
|
else
|
||||||
|
printf("LOC: prv: ");
|
||||||
|
|
||||||
|
if( match_loc_id != kInvalidIdx )
|
||||||
|
printf("match:%4i ",match_loc_id);
|
||||||
|
else
|
||||||
|
printf("match: ");
|
||||||
|
|
||||||
|
if( exp_loc_id != kInvalidIdx )
|
||||||
|
printf("exp:%4i ",exp_loc_id);
|
||||||
|
else
|
||||||
|
printf("exp: ");
|
||||||
|
|
||||||
|
if( d_loc_id_valid_fl )
|
||||||
|
printf(": dLoc:%4i ",d_loc_id);
|
||||||
|
else
|
||||||
|
printf(": dLoc: ");
|
||||||
|
|
||||||
|
|
||||||
|
if( d_match_score_sec_valid_fl )
|
||||||
|
printf("| Match dsec score:%6.3f ",d_match_score_sec);
|
||||||
|
else
|
||||||
|
printf("| Match dsec score: ");
|
||||||
|
|
||||||
|
if( d_match_perf_sec_valid_fl )
|
||||||
|
printf("perf:%6.3f ",d_match_perf_sec);
|
||||||
|
else
|
||||||
|
printf("perf: ");
|
||||||
|
|
||||||
|
if( d_corr_sec_valid_fl )
|
||||||
|
printf("corr:%6.3f ",d_corr_sec);
|
||||||
|
else
|
||||||
|
printf("corr: ");
|
||||||
|
|
||||||
|
//printf(" : (%f %i %f) : ",trk->time_delta_sum,trk->time_delta_cnt,trk->time_fact);
|
||||||
|
|
||||||
|
printf("%s ",rpt_status);
|
||||||
|
|
||||||
|
if( vel < 5 )
|
||||||
|
printf("vel(%i) ",vel);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trk->new_note_idx+=1;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _trkr_do_decay( trkr_t* trk )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
if( trk->search_bni != kInvalidIdx && trk->search_eni != kInvalidIdx )
|
||||||
|
{
|
||||||
|
//printf("decay: %i %i\n",trk->search_bni, trk->search_eni);
|
||||||
|
trk->decay_cnt += 1;
|
||||||
|
|
||||||
|
for(unsigned ni=trk->search_bni; ni<=trk->search_eni; ++ni)
|
||||||
|
trk->expV[ni] *= trk->sf->args.decay_coeff;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
sf_t* _handleToPtr(handle_t h)
|
||||||
|
{
|
||||||
|
return handleToPtr<handle_t,sf_t>(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _destroy( sf_t* p )
|
||||||
|
{
|
||||||
|
for(unsigned i=0; i<p->locN; ++i)
|
||||||
|
{
|
||||||
|
if( p->locA != nullptr )
|
||||||
|
mem::release(p->locA[i].note_idxA);
|
||||||
|
|
||||||
|
if( p->loc_affA != nullptr )
|
||||||
|
mem::release(p->loc_affA[i].envA);
|
||||||
|
}
|
||||||
|
|
||||||
|
_trkr_destroy(p->trk);
|
||||||
|
|
||||||
|
mem::release(p->resultA);
|
||||||
|
mem::release(p->noteA);
|
||||||
|
mem::release(p->locA);
|
||||||
|
mem::release(p->locMapA);
|
||||||
|
mem::release(p->loc_wndA);
|
||||||
|
mem::release(p->loc_affA);
|
||||||
|
mem::release(p);
|
||||||
|
return kOkRC;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _get_loc_and_note_count( sf_t* p, const perf_score::handle_t& scoreH )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
unsigned loc_id0 = kInvalidId;
|
||||||
|
|
||||||
|
p->locAllocN = 0;
|
||||||
|
p->noteAllocN = 0;
|
||||||
|
p->max_loc_id = kInvalidId;
|
||||||
|
|
||||||
|
// for each score event
|
||||||
|
for(const perf_score::event_t* e = base_event(scoreH); e != nullptr; e=e->link)
|
||||||
|
{
|
||||||
|
// if this is a note-on
|
||||||
|
if( midi::isNoteOn( e->status, e->d1 ) )
|
||||||
|
{
|
||||||
|
p->noteAllocN += 1;
|
||||||
|
|
||||||
|
// if this is a new location
|
||||||
|
if( loc_id0 == kInvalidId || loc_id0 != e->loc )
|
||||||
|
{
|
||||||
|
// if this is the max location id
|
||||||
|
if( p->max_loc_id == kInvalidId || e->loc > p->max_loc_id )
|
||||||
|
p->max_loc_id = e->loc;
|
||||||
|
|
||||||
|
p->locAllocN += 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that location id's are in increasing order
|
||||||
|
if( loc_id0 != kInvalidId && e->loc < loc_id0 )
|
||||||
|
{
|
||||||
|
rc = cwLogError(kInvalidStateRC,"The score location id's are not increasing. (%i < %i)",e->loc,loc_id0);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
loc_id0 = e->loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _alloc_fill_loc_and_note_arrays( sf_t* p, const perf_score::handle_t& scoreH )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
unsigned loc_id0 = kInvalidId;
|
||||||
|
unsigned uid = 0;
|
||||||
|
|
||||||
|
p->noteA = mem::allocZ<note_t>(p->noteAllocN);
|
||||||
|
p->locA = mem::allocZ<loc_t>(p->locAllocN);
|
||||||
|
p->locMapA = mem::allocZ<unsigned>(p->max_loc_id+1);
|
||||||
|
p->locN = 0;
|
||||||
|
p->noteN = 0;
|
||||||
|
|
||||||
|
// for each score event
|
||||||
|
for(const perf_score::event_t* e = base_event(scoreH); e != nullptr; e=e->link)
|
||||||
|
{
|
||||||
|
// if this is a note-on
|
||||||
|
if( midi::isNoteOn( e->status, e->d1 ) )
|
||||||
|
{
|
||||||
|
note_t* note = p->noteA + p->noteN;
|
||||||
|
|
||||||
|
note->uid = uid++;
|
||||||
|
note->loc_id = e->loc;
|
||||||
|
note->pitch = e->d0;
|
||||||
|
note->vel = e->d1;
|
||||||
|
|
||||||
|
p->noteN += 1;
|
||||||
|
|
||||||
|
// if this is a new location
|
||||||
|
if( loc_id0 == kInvalidId || loc_id0 != e->loc )
|
||||||
|
{
|
||||||
|
loc_t* loc = p->locA + p->locN;
|
||||||
|
|
||||||
|
loc->loc_id = e->loc;
|
||||||
|
loc->meas_num = e->meas;
|
||||||
|
loc->sec = e->sec;
|
||||||
|
|
||||||
|
// fill in the loc_id to loc_idx map
|
||||||
|
p->locMapA[ e->loc ] = p->locN;
|
||||||
|
|
||||||
|
p->locN += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
loc_id0 = e->loc;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _alloc_and_fill_loc_note_arrays( sf_t* p )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
unsigned ni = 0;
|
||||||
|
|
||||||
|
for(unsigned li=0; li<p->locN; ++li)
|
||||||
|
{
|
||||||
|
unsigned n = 0;
|
||||||
|
unsigned bni = ni;
|
||||||
|
|
||||||
|
// count the number of notes assigned to location loc[li]->loc_id
|
||||||
|
for(; ni<p->noteN && p->noteA[ni].loc_id == p->locA[li].loc_id; ++ni )
|
||||||
|
++n;
|
||||||
|
|
||||||
|
if( n == 0 )
|
||||||
|
{
|
||||||
|
rc = cwLogError(kInvalidStateRC,"No notes exist for score location '%i'.",p->locA[li].loc_id);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// allocate this loc's pitch index array
|
||||||
|
p->locA[li].note_idxA = mem::allocZ<unsigned>(n);
|
||||||
|
p->locA[li].note_idxN = n;
|
||||||
|
|
||||||
|
// fill the pitch index array
|
||||||
|
for(unsigned i=0; i<n; ++i)
|
||||||
|
{
|
||||||
|
p->locA[li].note_idxA[i] = bni + i;
|
||||||
|
|
||||||
|
assert( p->noteA[ bni+i ].loc_id == p->locA[li].loc_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _alloc_and_fill_search_wnd_array( sf_t* p, double pre_wnd_sec, double post_wnd_sec )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
p->loc_wndA = mem::allocZ<wnd_t>(p->locN);
|
||||||
|
|
||||||
|
for(unsigned li=0; li<p->locN; ++li)
|
||||||
|
{
|
||||||
|
int bli = li;
|
||||||
|
unsigned eli = li;
|
||||||
|
|
||||||
|
// look backward for window start location
|
||||||
|
while(bli-1 >= 0 && p->locA[bli-1].sec >= p->locA[li].sec - pre_wnd_sec )
|
||||||
|
bli -= 1;
|
||||||
|
|
||||||
|
// look forward for the window end location
|
||||||
|
while(eli+1<p->locN && p->locA[eli+1].sec <= p->locA[li].sec + post_wnd_sec )
|
||||||
|
eli += 1;
|
||||||
|
|
||||||
|
// verfiy that notes exist for location 'bli' (this is a redundant check - see _alloc_and_fill_note_arrays()
|
||||||
|
if( p->locA[bli].note_idxN == 0 )
|
||||||
|
{
|
||||||
|
rc = cwLogError(kInvalidStateRC,"No notes exist for score location '%i'.",p->locA[bli].loc_id);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verfiy that notes exist for location 'eli' (this is a redundant check - see _alloc_and_fill_note_arrays()
|
||||||
|
if( p->locA[eli].note_idxN == 0 )
|
||||||
|
{
|
||||||
|
rc = cwLogError(kInvalidStateRC,"No notes exist for score location '%i'.",p->locA[eli].loc_id);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// translate the begin/end locations to begin/end pitich indexes
|
||||||
|
p->loc_wndA[li].bni = p->locA[bli].note_idxA[0];
|
||||||
|
p->loc_wndA[li].eni = p->locA[eli].note_idxA[ p->locA[eli].note_idxN-1 ];
|
||||||
|
p->loc_wndA[li].loc_id = p->locA[li].loc_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float _aff_func( sf_t* p, double t0, unsigned li, double wnd_dur_sec )
|
||||||
|
{
|
||||||
|
double t1 = p->locA[li].sec;
|
||||||
|
double dt = std::max(t0,t1)-std::min(t0,t1);
|
||||||
|
assert( dt <= wnd_dur_sec );
|
||||||
|
return (wnd_dur_sec-dt)/wnd_dur_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t _alloc_and_fill_affinity_wnd_arrays( sf_t*p, double pre_aff_sec, double post_aff_sec )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
unsigned locEnvN = 0;
|
||||||
|
float* locEnvV = nullptr;
|
||||||
|
|
||||||
|
p->loc_affA = mem::allocZ<aff_t>(p->locN);
|
||||||
|
|
||||||
|
// for each location
|
||||||
|
for(unsigned li = 0; li<p->locN; ++li)
|
||||||
|
{
|
||||||
|
int bli = li;
|
||||||
|
unsigned eli = li;
|
||||||
|
double t0 = p->locA[li].sec;
|
||||||
|
unsigned bpi = kInvalidId;
|
||||||
|
unsigned epi = kInvalidId;
|
||||||
|
|
||||||
|
// look backward for window start location
|
||||||
|
while(bli-1 >= 0 && p->locA[bli-1].sec >= t0 - pre_aff_sec )
|
||||||
|
bli -= 1;
|
||||||
|
|
||||||
|
// look forward for the window end location
|
||||||
|
while(eli+1<p->locN && p->locA[eli+1].sec <= t0 + post_aff_sec )
|
||||||
|
eli += 1;
|
||||||
|
|
||||||
|
// calc the count of the loc's in the aff. env.
|
||||||
|
locEnvN = (eli-bli) + 1;
|
||||||
|
locEnvV = mem::resize<float>(locEnvV,locEnvN);
|
||||||
|
|
||||||
|
// fill in the pre-aff wnd
|
||||||
|
for(unsigned i = bli; i<li; ++i)
|
||||||
|
{
|
||||||
|
assert( bli <= (int)i && i-bli < locEnvN );
|
||||||
|
locEnvV[i-bli] = _aff_func(p,t0,i,pre_aff_sec);
|
||||||
|
}
|
||||||
|
// fill in the post-aff wnd
|
||||||
|
for(unsigned i = li; i<=eli; ++i)
|
||||||
|
{
|
||||||
|
assert( bli<=(int)i && (li-bli)+(i-li) < locEnvN );
|
||||||
|
locEnvV[(li-bli)+(i-li)] = _aff_func(p,t0,i,post_aff_sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( p->locA[eli].note_idxN > 0 );
|
||||||
|
|
||||||
|
// allocate the per-note envelope
|
||||||
|
bpi = p->locA[bli].note_idxA[0];
|
||||||
|
epi = p->locA[eli].note_idxA[ p->locA[eli].note_idxN-1 ];
|
||||||
|
|
||||||
|
assert( bpi < p->noteN && epi < p->noteN && li < p->locN );
|
||||||
|
|
||||||
|
// init. the aff. env. record
|
||||||
|
p->loc_affA[ li ].loc_id = p->locA[li].loc_id;
|
||||||
|
p->loc_affA[ li ].bni = bpi;
|
||||||
|
p->loc_affA[ li ].envN = (epi-bpi)+1;
|
||||||
|
p->loc_affA[ li ].envA = mem::allocZ<float>(p->loc_affA[ li ].envN);
|
||||||
|
|
||||||
|
// for each note that fall withing the aff. env
|
||||||
|
for(unsigned pi = bpi; pi<=epi; ++pi)
|
||||||
|
{
|
||||||
|
// get the loc idx assoc with this note
|
||||||
|
unsigned loc_idx = p->locMapA[ p->noteA[pi].loc_id ];
|
||||||
|
|
||||||
|
// calc the loc. based envA[] index
|
||||||
|
unsigned loc_env_i = loc_idx - bli;
|
||||||
|
|
||||||
|
assert( loc_env_i < locEnvN );
|
||||||
|
assert( pi-bpi < p->loc_affA[li].envN );
|
||||||
|
|
||||||
|
p->loc_affA[li].envA[pi-bpi] = locEnvV[ loc_env_i ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mem::release(locEnvV);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _report_score( sf_t* p, unsigned beg_loc_id, unsigned end_loc_id )
|
||||||
|
{
|
||||||
|
for(unsigned ni=0; ni<p->noteN; ++ni)
|
||||||
|
if(beg_loc_id <= p->noteA[ni].loc_id && p->noteA[ni].loc_id <= end_loc_id )
|
||||||
|
printf("%4i %6.2f %3i\n", p->noteA[ni].loc_id, p->locA[ p->locMapA[ p->noteA[ni].loc_id ] ].sec, p->noteA[ni].pitch );
|
||||||
|
}
|
||||||
|
|
||||||
|
void _report_affinity( sf_t* p, unsigned N=10 )
|
||||||
|
{
|
||||||
|
for(unsigned i=0; i<N; ++i)
|
||||||
|
{
|
||||||
|
const aff_t& a = p->loc_affA[i];
|
||||||
|
printf("%i %i %i [",a.loc_id,a.bni,a.envN);
|
||||||
|
for(unsigned j=0; j<a.envN; ++j)
|
||||||
|
printf("%f ",a.envA[j]);
|
||||||
|
printf("]\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::parse_args( const object_t* cfg, args_t& args )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
|
||||||
|
if((rc = cfg->getv("rpt_fl",args.rpt_fl,
|
||||||
|
"pre_affinity_sec", args.pre_affinity_sec,
|
||||||
|
"post_affinity_sec", args.post_affinity_sec,
|
||||||
|
"pre_wnd_sec", args.pre_wnd_sec,
|
||||||
|
"post_wnd_sec", args.post_wnd_sec,
|
||||||
|
"decay_coeff", args.decay_coeff,
|
||||||
|
"d_sec_err_thresh_lo", args.d_sec_err_thresh_lo,
|
||||||
|
"d_loc_thresh_lo", args.d_loc_thresh_lo,
|
||||||
|
"d_sec_err_thresh_hi", args.d_sec_err_thresh_hi,
|
||||||
|
"d_loc_thresh_hi", args.d_loc_thresh_hi,
|
||||||
|
"d_loc_stats_thresh", args.d_loc_stats_thresh)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"SF2 parse args. failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::create( handle_t& hRef, const args_t& args )
|
||||||
|
{
|
||||||
|
rc_t rc;
|
||||||
|
if((rc = destroy(hRef)) != kOkRC )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
sf_t* p = mem::allocZ<sf_t>();
|
||||||
|
|
||||||
|
if((rc = _get_loc_and_note_count( p, args.scoreH )) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
if((rc = _alloc_fill_loc_and_note_arrays( p, args.scoreH )) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
if((rc = _alloc_and_fill_loc_note_arrays( p )) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
if((rc = _alloc_and_fill_search_wnd_array( p, args.pre_wnd_sec, args.post_wnd_sec )) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
if((rc = _alloc_and_fill_affinity_wnd_arrays(p, args.pre_affinity_sec, args.post_affinity_sec )) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
p->trk = _trkr_create(p);
|
||||||
|
|
||||||
|
p->args = args;
|
||||||
|
|
||||||
|
p->resultAllocN = p->noteN*2;
|
||||||
|
p->resultA = mem::allocZ<result_t>(p->resultAllocN);
|
||||||
|
p->resultN = 0;
|
||||||
|
|
||||||
|
//_report_affinity( p );
|
||||||
|
|
||||||
|
|
||||||
|
hRef.set(p);
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
if(rc != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score follower create failed.");
|
||||||
|
_destroy(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::destroy( handle_t& hRef )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
sf_t* p = nullptr;
|
||||||
|
|
||||||
|
if( !hRef.isValid() )
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
p = _handleToPtr(hRef);
|
||||||
|
|
||||||
|
if((rc = _destroy(p)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score follow destroy failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
hRef.clear();
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::reset( handle_t h, unsigned beg_loc_id, unsigned end_loc_id )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
sf_t* p = _handleToPtr(h);
|
||||||
|
|
||||||
|
if( beg_loc_id > p->max_loc_id )
|
||||||
|
{
|
||||||
|
rc = cwLogError(kInvalidArgRC,"An invalid location id (%i) was encountered.",beg_loc_id);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
//_report_score(p,beg_loc_id,end_loc_id);
|
||||||
|
|
||||||
|
|
||||||
|
p->beg_loc_id = beg_loc_id;
|
||||||
|
p->end_loc_id = end_loc_id;
|
||||||
|
|
||||||
|
p->resultN = 0;
|
||||||
|
|
||||||
|
_trkr_reset(p->trk,beg_loc_id);
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::on_new_note( handle_t h, unsigned uid, double sec, uint8_t pitch, uint8_t vel, unsigned& loc_id )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
sf_t* p = _handleToPtr(h);
|
||||||
|
unsigned matched_loc_id = kInvalidId;
|
||||||
|
|
||||||
|
_trkr_on_new_note(p->trk,sec,pitch,vel, p->args.rpt_fl, matched_loc_id);
|
||||||
|
|
||||||
|
if( p->resultN < p->resultAllocN )
|
||||||
|
{
|
||||||
|
result_t* r = p->resultA + p->resultN;
|
||||||
|
r->perf_uid = uid;
|
||||||
|
r->perf_pitch = pitch;
|
||||||
|
r->perf_vel = vel;
|
||||||
|
r->match_loc_id = matched_loc_id;
|
||||||
|
p->resultN += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::do_exec( handle_t h )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
sf_t* p = _handleToPtr(h);
|
||||||
|
|
||||||
|
_trkr_do_decay(p->trk);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cw::score_follow_2::report_summary( handle_t h, rpt_t& rpt_ref )
|
||||||
|
{
|
||||||
|
sf_t* p = _handleToPtr(h);
|
||||||
|
|
||||||
|
rpt_ref.matchN = 0;
|
||||||
|
rpt_ref.missN = 0;
|
||||||
|
rpt_ref.spuriousN = 0;
|
||||||
|
rpt_ref.perfNoteN = 0;
|
||||||
|
|
||||||
|
for(unsigned i=0; i<p->resultN; ++i)
|
||||||
|
if( p->resultA[i].match_loc_id == kInvalidIdx )
|
||||||
|
rpt_ref.spuriousN += 1;
|
||||||
|
|
||||||
|
unsigned bli = p->locMapA[ p->beg_loc_id ];
|
||||||
|
unsigned eli = p->locMapA[ p->end_loc_id ];
|
||||||
|
|
||||||
|
unsigned bni = p->locA[ bli ].note_idxA[0];
|
||||||
|
unsigned eni = p->locA[ eli ].note_idxA[ p->locA[eli].note_idxN-1 ];
|
||||||
|
|
||||||
|
for(unsigned ni=bni; ni<=eni; ++ni)
|
||||||
|
if( p->trk->note_match_cntA[ni] == 0 )
|
||||||
|
rpt_ref.missN += 1;
|
||||||
|
else
|
||||||
|
rpt_ref.matchN += 1;
|
||||||
|
|
||||||
|
rpt_ref.perfNoteN = p->trk->new_note_idx;
|
||||||
|
|
||||||
|
if( p->args.rpt_fl )
|
||||||
|
cwLogInfo("Matched:%i Missed:%i Spurious:%i",rpt_ref.matchN,rpt_ref.missN,rpt_ref.spuriousN);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
54
cwScoreFollow2.h
Normal file
54
cwScoreFollow2.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace score_follow_2
|
||||||
|
{
|
||||||
|
typedef handle< struct sf_str > handle_t;
|
||||||
|
|
||||||
|
typedef struct args_str
|
||||||
|
{
|
||||||
|
perf_score::handle_t scoreH;
|
||||||
|
|
||||||
|
double pre_affinity_sec; // 1.0 look back affinity duration
|
||||||
|
double post_affinity_sec; // 3.0 look forward affinity duration
|
||||||
|
double pre_wnd_sec; // 2.0 look back search window
|
||||||
|
double post_wnd_sec; // 5.0 look forward search window
|
||||||
|
|
||||||
|
double decay_coeff; // 0.995affinity decay coeff
|
||||||
|
|
||||||
|
double d_sec_err_thresh_lo; // 0.4 reject if d_loc > d_loc_thresh_lod and d_time > d_time_thresh_lo
|
||||||
|
int d_loc_thresh_lo; // 3
|
||||||
|
|
||||||
|
double d_sec_err_thresh_hi; // 1.5 reject if d_loc != 0 and d_time > d_time_thresh_hi
|
||||||
|
int d_loc_thresh_hi; // 4 reject if d_loc > d_loc_thresh_hi
|
||||||
|
int d_loc_stats_thresh; // 3 reject for time stats updates if d_loc > d_loc_stats_thresh
|
||||||
|
|
||||||
|
bool rpt_fl; // set to turn on debug reporting
|
||||||
|
|
||||||
|
} args_t;
|
||||||
|
|
||||||
|
rc_t parse_args( const object_t* cfg, args_t& args );
|
||||||
|
|
||||||
|
rc_t create( handle_t& hRef, const args_t& args );
|
||||||
|
|
||||||
|
rc_t destroy( handle_t& hRef );
|
||||||
|
|
||||||
|
rc_t reset( handle_t h, unsigned beg_loc_id, unsigned end_loc_id );
|
||||||
|
|
||||||
|
rc_t on_new_note( handle_t h, unsigned uid, double sec, uint8_t pitch, uint8_t vel, unsigned& loc_id );
|
||||||
|
|
||||||
|
// Decay the affinity window and if necessary trigger a cycle of async background processing
|
||||||
|
rc_t do_exec( handle_t h );
|
||||||
|
|
||||||
|
typedef struct rpt_str
|
||||||
|
{
|
||||||
|
unsigned matchN; // count of matched notes
|
||||||
|
unsigned missN; // count of missed notes
|
||||||
|
unsigned spuriousN; // count of spurious notes
|
||||||
|
unsigned perfNoteN; // count of performed notes (count of calls to on_new_note())
|
||||||
|
} rpt_t;
|
||||||
|
|
||||||
|
void report_summary( handle_t h, rpt_t& rpt_ref );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
314
cwScoreFollow2Test.cpp
Normal file
314
cwScoreFollow2Test.cpp
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
||||||
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
||||||
|
#include "cwCommon.h"
|
||||||
|
#include "cwLog.h"
|
||||||
|
#include "cwCommonImpl.h"
|
||||||
|
#include "cwTest.h"
|
||||||
|
#include "cwMem.h"
|
||||||
|
#include "cwText.h"
|
||||||
|
#include "cwObject.h"
|
||||||
|
#include "cwFileSys.h"
|
||||||
|
|
||||||
|
#include "cwCsv.h"
|
||||||
|
#include "cwMidi.h"
|
||||||
|
|
||||||
|
#include "cwDynRefTbl.h"
|
||||||
|
#include "cwScoreParse.h"
|
||||||
|
#include "cwSfScore.h"
|
||||||
|
#include "cwPerfMeas.h"
|
||||||
|
|
||||||
|
#include "cwPianoScore.h"
|
||||||
|
|
||||||
|
#include "cwPianoScore.h"
|
||||||
|
#include "cwScoreFollow2.h"
|
||||||
|
#include "cwScoreFollow2Test.h"
|
||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace score_follow_2
|
||||||
|
{
|
||||||
|
typedef struct midi_evt_str
|
||||||
|
{
|
||||||
|
unsigned uid;
|
||||||
|
uint8_t pitch;
|
||||||
|
uint8_t vel;
|
||||||
|
unsigned sample_idx;
|
||||||
|
double sec;
|
||||||
|
} midi_evt_t;
|
||||||
|
|
||||||
|
typedef struct midi_file_str
|
||||||
|
{
|
||||||
|
midi_evt_t* evtA;
|
||||||
|
unsigned evtN;
|
||||||
|
unsigned sampleN;
|
||||||
|
} midi_file_t;
|
||||||
|
|
||||||
|
rc_t parse_midi_file( const char* midi_csv_fname, double srate, midi_file_t& mf )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
csv::handle_t csvH;
|
||||||
|
const char* titleA[] = { "UID","trk","amicro","type","ch","D0","D1","sci_pitch" };
|
||||||
|
unsigned titleN = sizeof(titleA)/sizeof(titleA[0]);
|
||||||
|
unsigned line_idx = 0;
|
||||||
|
unsigned lineN = 0;
|
||||||
|
|
||||||
|
if((rc = csv::create(csvH,midi_csv_fname,titleA,titleN)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"MIDI CSV file open failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = line_count(csvH,lineN)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"MIDI CSV line count access failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
mf.evtA = mem::allocZ<midi_evt_t>(lineN);
|
||||||
|
mf.evtN = 0;
|
||||||
|
|
||||||
|
for(; (rc = next_line(csvH)) == kOkRC; ++line_idx )
|
||||||
|
{
|
||||||
|
unsigned uid;
|
||||||
|
unsigned amicro;
|
||||||
|
const char* type = nullptr;
|
||||||
|
unsigned ch;
|
||||||
|
unsigned d0;
|
||||||
|
unsigned d1;
|
||||||
|
|
||||||
|
if((rc = getv(csvH,"UID",uid,"amicro",amicro,"type",type,"ch",ch,"D0",d0,"D1",d1)) != kOkRC )
|
||||||
|
{
|
||||||
|
cwLogError(rc,"Error reading CSV line %i.",line_idx+1);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(textIsEqual(type,"non"))
|
||||||
|
{
|
||||||
|
midi_evt_t& m = mf.evtA[ mf.evtN++ ];
|
||||||
|
|
||||||
|
assert( d0 <= 127 && d1 <= 127 );
|
||||||
|
|
||||||
|
m.pitch = (uint8_t)d0;
|
||||||
|
m.vel = (uint8_t)d1;
|
||||||
|
m.sec = amicro/1000000.0;
|
||||||
|
m.sample_idx = (unsigned)(m.sec * srate);
|
||||||
|
m.uid = uid;
|
||||||
|
|
||||||
|
mf.sampleN = m.sample_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
destroy(csvH);
|
||||||
|
|
||||||
|
if( rc == kEofRC )
|
||||||
|
rc = kOkRC;
|
||||||
|
|
||||||
|
if( rc != kOkRC )
|
||||||
|
rc = cwLogError(rc,"MIDI csv file parse failed on '%s'.",cwStringNullGuard(midi_csv_fname));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc_t run( perf_score::handle_t& scoreH,
|
||||||
|
double srate,
|
||||||
|
unsigned smp_per_cycle,
|
||||||
|
const char* midi_csv_fname,
|
||||||
|
const object_t* sf_cfg,
|
||||||
|
unsigned beg_loc_id,
|
||||||
|
unsigned end_loc_id,
|
||||||
|
score_follow_2::rpt_t& rpt_ref )
|
||||||
|
{
|
||||||
|
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
midi_file_t mf{ .evtA = nullptr, .evtN=0, .sampleN=0 };
|
||||||
|
score_follow_2::args_t sf_args{};
|
||||||
|
score_follow_2::handle_t sfH;
|
||||||
|
unsigned midi_evt_idx = 0;
|
||||||
|
|
||||||
|
// parse args
|
||||||
|
if((rc = score_follow_2::parse_args(sf_cfg,sf_args)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"SF2 parse arg. failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse MIDI file into an array
|
||||||
|
if((rc = parse_midi_file( midi_csv_fname, srate, mf )) != kOkRC )
|
||||||
|
goto errLabel;
|
||||||
|
|
||||||
|
//cwLogInfo("Following: (%i notes found) sample count:%i %s.",mf.evtN,mf.sampleN,midi_csv_fname);
|
||||||
|
|
||||||
|
sf_args.scoreH = scoreH;
|
||||||
|
|
||||||
|
// create the score-follower
|
||||||
|
if((rc = score_follow_2::create(sfH,sf_args)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score follower create failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = score_follow_2::reset(sfH,beg_loc_id,end_loc_id)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score follower reset failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each cycle
|
||||||
|
for(unsigned smp_idx=0; smp_idx < mf.sampleN; smp_idx += smp_per_cycle)
|
||||||
|
{
|
||||||
|
while( smp_idx <= mf.evtA[midi_evt_idx].sample_idx && mf.evtA[midi_evt_idx].sample_idx < smp_idx + smp_per_cycle )
|
||||||
|
{
|
||||||
|
const midi_evt_t* e = mf.evtA + midi_evt_idx++;
|
||||||
|
unsigned loc_id;
|
||||||
|
|
||||||
|
//printf("%f pitch:%i vel:%i\n",e->sec,e->pitch,e->vel);
|
||||||
|
|
||||||
|
if((rc = on_new_note( sfH, e->uid, e->sec, e->pitch, e->vel, loc_id )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"SF2 note processing failed on note UID:%i.",e->uid);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((rc = do_exec(sfH)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"SF2 exec processing failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
score_follow_2::report_summary(sfH,rpt_ref);
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
mem::release(mf.evtA);
|
||||||
|
destroy(sfH);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cw::rc_t cw::score_follow_2::test( const object_t* cfg )
|
||||||
|
{
|
||||||
|
rc_t rc = kOkRC;
|
||||||
|
double srate = 48000.0;
|
||||||
|
unsigned smp_per_cycle = 64;
|
||||||
|
unsigned limit_perf_N = -1;
|
||||||
|
const char* c_score_fname = nullptr;
|
||||||
|
char* score_fname = nullptr;
|
||||||
|
const object_t* fileL = nullptr;
|
||||||
|
const object_t* sf_cfg = nullptr;
|
||||||
|
|
||||||
|
unsigned tot_missN = 0;
|
||||||
|
unsigned tot_spuriousN = 0;
|
||||||
|
unsigned tot_passN = 0;
|
||||||
|
unsigned tot_failN = 0;
|
||||||
|
|
||||||
|
perf_score::handle_t scoreH;
|
||||||
|
|
||||||
|
if((rc = cfg->getv("srate",srate,
|
||||||
|
"smp_per_cycle",smp_per_cycle,
|
||||||
|
"limit_perf_N",limit_perf_N,
|
||||||
|
"score_fname",c_score_fname,
|
||||||
|
"fileL",fileL,
|
||||||
|
"follower",sf_cfg)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"SF2 test argument parsing failed.");
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
score_fname = filesys::expandPath( c_score_fname );
|
||||||
|
|
||||||
|
// create the score
|
||||||
|
if((rc = perf_score::create(scoreH,score_fname)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Score create failed on '%s'.",cwStringNullGuard(score_fname));
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each file
|
||||||
|
for(unsigned i=0; rc==kOkRC && i<fileL->child_count(); ++i)
|
||||||
|
{
|
||||||
|
unsigned beg_loc_id = kInvalidId;
|
||||||
|
unsigned end_loc_id = kInvalidId;
|
||||||
|
unsigned min_perf_noteN = 0;
|
||||||
|
unsigned max_spuriousN = 0;
|
||||||
|
const char* c_folder = nullptr;
|
||||||
|
char* folder = nullptr;
|
||||||
|
const object_t* takeL = nullptr;
|
||||||
|
|
||||||
|
score_follow_2::rpt_t sf_rpt;
|
||||||
|
|
||||||
|
// read the file record
|
||||||
|
if((rc = fileL->child_ele(i)->getv("beg_loc",beg_loc_id,
|
||||||
|
"end_loc",end_loc_id,
|
||||||
|
"min_perf_noteN",min_perf_noteN,
|
||||||
|
"max_spuriousN", max_spuriousN,
|
||||||
|
"folder",c_folder,
|
||||||
|
"takeL",takeL)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Parse of SF2 test case at index %i.",i);
|
||||||
|
goto errLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
folder = filesys::expandPath( c_folder );
|
||||||
|
|
||||||
|
// for each take number
|
||||||
|
for(unsigned j=0; j<takeL->child_count(); ++j)
|
||||||
|
{
|
||||||
|
char* record_folder = nullptr;
|
||||||
|
unsigned take_num;
|
||||||
|
|
||||||
|
// read the take number
|
||||||
|
if((rc = takeL->child_ele(j)->value(take_num)) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"Error parsing take number on test '%s' index '%i'.",cwStringNullGuard(c_folder),j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// form the record_### folder name
|
||||||
|
record_folder = mem::printf<char>(nullptr,"record_%i",take_num);
|
||||||
|
|
||||||
|
// form MIDI csv file name
|
||||||
|
char* midi_csv_fname = filesys::makeFn(folder,"fix_midi","csv", record_folder, nullptr );
|
||||||
|
|
||||||
|
if((rc = run(scoreH, srate, smp_per_cycle, midi_csv_fname, sf_cfg, beg_loc_id, end_loc_id, sf_rpt )) != kOkRC )
|
||||||
|
{
|
||||||
|
rc = cwLogError(rc,"SF2 test run failed on '%s'.",cwStringNullGuard(midi_csv_fname));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cwLogInfo("Notes:%i Match:%i Miss:%i Spurious:%i : %s",sf_rpt.perfNoteN,sf_rpt.matchN,sf_rpt.missN,sf_rpt.spuriousN,cwStringNullGuard(midi_csv_fname));
|
||||||
|
|
||||||
|
if( sf_rpt.perfNoteN > min_perf_noteN && sf_rpt.spuriousN < max_spuriousN )
|
||||||
|
{
|
||||||
|
tot_passN += 1;
|
||||||
|
tot_missN += sf_rpt.missN;
|
||||||
|
tot_spuriousN += sf_rpt.spuriousN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tot_failN += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//printf("%s\n",fname);
|
||||||
|
|
||||||
|
mem::release(record_folder);
|
||||||
|
mem::release(midi_csv_fname);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mem::release(folder);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cwLogInfo("Total M+S:%i missed:%i spurious:%i pass:%i fail:%i",tot_missN+tot_spuriousN,tot_missN,tot_spuriousN,tot_passN,tot_failN);
|
||||||
|
|
||||||
|
errLabel:
|
||||||
|
destroy(scoreH);
|
||||||
|
mem::release(score_fname);
|
||||||
|
return rc;
|
||||||
|
}
|
8
cwScoreFollow2Test.h
Normal file
8
cwScoreFollow2Test.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
namespace cw
|
||||||
|
{
|
||||||
|
namespace score_follow_2
|
||||||
|
{
|
||||||
|
rc_t test( const object_t* cfg );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user