967 lines
28 KiB
C++
967 lines
28 KiB
C++
|
|
|
|
//| 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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|