//| Copyright: (C) 2020-2024 Kevin Larke //| 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; ienvN; ++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; nisf->noteN; ++ni) { trk->note_match_cntA[ni] = 0; trk->expV[ni] = 0; } for(unsigned li=0; lisf->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(); trk->sf = sf; trk->note_match_cntA = mem::allocZ(sf->noteN); trk->loc_match_cntA = mem::allocZ(sf->locN); trk->expV = mem::allocZ(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, unsigned& score_vel_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; score_vel_ref = -1; 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; score_vel_ref = trk->sf->noteA[ match_ni ].vel; 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(h); } rc_t _destroy( sf_t* p ) { for(unsigned i=0; ilocN; ++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(p->noteAllocN); p->locA = mem::allocZ(p->locAllocN); p->locMapA = mem::allocZ(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; lilocN; ++li) { unsigned n = 0; unsigned bni = ni; // count the number of notes assigned to location loc[li]->loc_id for(; ninoteN && 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(n); p->locA[li].note_idxN = n; // fill the pitch index array for(unsigned i=0; ilocA[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(p->locN); for(unsigned li=0; lilocN; ++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+1locN && 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(p->locN); // for each location for(unsigned li = 0; lilocN; ++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+1locN && 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(locEnvV,locEnvN); // fill in the pre-aff wnd for(unsigned i = bli; ilocA[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(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; ninoteN; ++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; iloc_affA[i]; printf("%i %i %i [",a.loc_id,a.bni,a.envN); for(unsigned j=0; jgetv("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, perf_score::handle_t scoreH ) { rc_t rc; if((rc = destroy(hRef)) != kOkRC ) return rc; sf_t* p = mem::allocZ(); if((rc = _get_loc_and_note_count( p, scoreH )) != kOkRC ) goto errLabel; if((rc = _alloc_fill_loc_and_note_arrays( p, 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(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); cwLogInfo("SF2 reset: %i %i",beg_loc_id,end_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& matched_loc_id_ref, unsigned& score_vel_ref ) { rc_t rc = kOkRC; sf_t* p = _handleToPtr(h); _trkr_on_new_note(p->trk,sec,pitch,vel, p->args.rpt_fl, matched_loc_id_ref, score_vel_ref); 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_ref; 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; iresultN; ++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); }