From fb8c99e0c4cf50859897a22b2a5f987b313d4095 Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 12 Sep 2023 18:03:51 -0400 Subject: [PATCH] cwSfTrack.h/cpp : Added kBacktrackResultsFl and kPrintFl. Added documentation. Made the sftrack_str private. --- cwSfTrack.cpp | 146 ++++++++++++++++++++++++++++++++++---------------- cwSfTrack.h | 97 +++++++++++++++++---------------- 2 files changed, 153 insertions(+), 90 deletions(-) diff --git a/cwSfTrack.cpp b/cwSfTrack.cpp index 2791ef9..47abbf8 100644 --- a/cwSfTrack.cpp +++ b/cwSfTrack.cpp @@ -17,6 +17,33 @@ namespace cw { namespace sftrack { + typedef struct sftrack_str + { + callback_func_t cbFunc; + void* cbArg; + sfmatch::handle_t matchH; + unsigned mn; // size of midiBuf[] + sfmatch::midi_t* midiBuf; // midiBuf[mn] + + result_t* res; // res[rn] + unsigned rn; // length of res[] (set to 2*score event count) + unsigned ri; // next avail res[] recd. + + double s_opt; // + unsigned missCnt; // current count of consecutive trailing non-matches + unsigned ili; // index into loc[] to start scan following reset + unsigned eli; // index into loc[] of the last positive match. + unsigned mni; // current count of MIDI events since the last call to cmScMatcherReset() + unsigned mbi; // index of oldest MIDI event in midiBuf[]; stays at 0 when the buffer is full. + unsigned begSyncLocIdx; // start of score window, in mp->loc[], of best match in previous scan + unsigned initHopCnt; // max window hops during the initial (when the MIDI buffer fills for first time) sync scan + unsigned stepCnt; // count of forward/backward score loc's to examine for a match during cmScMatcherStep(). + unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a scan. + unsigned scanCnt; // current count of times a resync-scan was executed during cmScMatcherStep() + + unsigned flags; + } sftrack_t; + sftrack_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } @@ -76,7 +103,7 @@ namespace cw return rc; } - bool _input_midi( sftrack_t* p, unsigned smpIdx, unsigned muid, unsigned status, midi::byte_t d0, midi::byte_t d1 ) + bool _input_midi( sftrack_t* p, double sec, unsigned smpIdx, unsigned muid, unsigned status, midi::byte_t d0, midi::byte_t d1 ) { if( (status&0xf0) != midi::kNoteOnMdId) return false; @@ -90,9 +117,10 @@ namespace cw // shift the new MIDI event onto the end of the MIDI buffer memmove(p->midiBuf, p->midiBuf+1, sizeof(sfmatch::midi_t)*mi); - p->midiBuf[mi].locIdx = kInvalidIdx; + p->midiBuf[mi].oLocId = kInvalidIdx; p->midiBuf[mi].scEvtIdx = kInvalidIdx; p->midiBuf[mi].mni = p->mni++; + p->midiBuf[mi].sec = sec; p->midiBuf[mi].smpIdx = smpIdx; p->midiBuf[mi].muid = muid; p->midiBuf[mi].pitch = d0; @@ -103,64 +131,80 @@ namespace cw return true; } - void _store_result( sftrack_t* p, unsigned locIdx, unsigned scEvtIdx, unsigned flags, const sfmatch::midi_t* mp ) + void _store_result( sftrack_t* p, unsigned oLocId, unsigned scEvtIdx, unsigned flags, const sfmatch::midi_t* mp, double cost ) { // don't store missed score note results assert( mp != NULL ); - bool matchFl = cwIsFlag(flags,sfmatch::kSmMatchFl); - bool tpFl = locIdx!=kInvalidIdx && matchFl; - bool fpFl = locIdx==kInvalidIdx || matchFl==false; - result_t * rp = NULL; - unsigned i; + bool matchFl = cwIsFlag(flags,sfmatch::kSmMatchFl); + bool tpFl = oLocId!=kInvalidIdx && matchFl; + bool fpFl = oLocId==kInvalidIdx || matchFl==false; + result_t * rp = NULL; + unsigned result_idx = kInvalidIdx; result_t r; - assert( tpFl==false || (tpFl==true && locIdx != kInvalidIdx ) ); + assert( tpFl==false || (tpFl==true && oLocId != kInvalidIdx ) ); // it is possible that the same MIDI event is reported more than once // (due to step->scan back tracking) - try to find previous result records // associated with this MIDI event - for(i=0; iri; ++i) - if( p->res[i].mni == mp->mni ) - { - // if this is not the first time this note was reported and it is a true positive - if( tpFl ) + + // TODO: This process looks expensive - as the result array grows a linear + // search is done over the entire length looking for previous matches. + // - and why do we want this behavior anyway? + if( cwIsFlag(p->flags,kBacktrackResultsFl) ) + { + for(unsigned i=0; iri; ++i) + if( p->res[i].mni == mp->mni ) { - rp = p->res + i; - break; + // if this is not the first time this note was reported and it is a true positive + if( tpFl ) + { + rp = p->res + i; + result_idx = i; + break; + } + + // a match was found but this was not a true-pos so ignore it + return; } - - // a match was found but this was not a true-pos so ignore it - return; - } - + } + if( rp == NULL ) { + // if the result array is full ... if( p->ri >= p->rn ) { - rp = &r; + // then use a single record to hold the result so that we can still make the callback + rp = &r; memset(rp,0,sizeof(r)); } else { + // otherwise append select the next available record to receive the result rp = p->res + p->ri; + result_idx = p->ri; ++p->ri; } } - rp->locIdx = locIdx; + rp->index = result_idx; + rp->oLocId = oLocId; rp->scEvtIdx = scEvtIdx; rp->mni = mp->mni; rp->muid = mp->muid; + rp->sec = mp->sec; rp->smpIdx = mp->smpIdx; rp->pitch = mp->pitch; rp->vel = mp->vel; rp->flags = flags | (tpFl ? sfmatch::kSmTruePosFl : 0) | (fpFl ? sfmatch::kSmFalsePosFl : 0); + rp->cost = cost; if( p->cbFunc != NULL ) p->cbFunc(p->cbArg,rp); } + unsigned _scan( sftrack_t* p, unsigned bli, unsigned hopCnt ) { @@ -168,7 +212,7 @@ namespace cw unsigned i_opt = kInvalidIdx; double s_opt = DBL_MAX; - rc_t rc = kOkRC; + rc_t rc = kOkRC; unsigned mmn = sfmatch::max_midi_wnd_count(p->matchH); unsigned msn = sfmatch::max_score_wnd_count(p->matchH); unsigned i; @@ -213,8 +257,8 @@ namespace cw return kInvalidIdx; - // set the locIdx field in midiBuf[], trailing miss count and - // return the latest positive-match locIdx + // set the oLocId field in midiBuf[], trailing miss count and + // return the latest positive-match oLocId p->eli = sfmatch::sync(p->matchH,i_opt,p->midiBuf,mmn,&p->missCnt); // if no positive matches were found @@ -225,7 +269,7 @@ namespace cw // record result for(const sfmatch::path_t* cp = optimal_path(p->matchH); cp!=NULL; cp=cp->next) if( cp->code != sfmatch::kSmInsIdx ) - _store_result(p, cp->locIdx, cp->scEvtIdx, cp->flags, p->midiBuf + cp->ri - 1); + _store_result(p, cp->oLocId, cp->scEvtIdx, cp->flags, p->midiBuf + cp->ri - 1,p->s_opt); } return i_opt; @@ -235,7 +279,7 @@ namespace cw rc_t _step( sftrack_t* p ) { unsigned pitch = p->midiBuf[ p->mn-1 ].pitch; - unsigned locIdx = kInvalidIdx; + unsigned oLocId = kInvalidIdx; unsigned pidx = kInvalidIdx; unsigned locN = loc_count(p->matchH); const sfmatch::loc_t* loc = loc_base(p->matchH); @@ -251,7 +295,7 @@ namespace cw // attempt to match to next location first if( (pidx = match_index(loc + p->eli + 1, pitch)) != kInvalidIdx ) { - locIdx = p->eli + 1; + oLocId = p->eli + 1; } else { @@ -261,34 +305,34 @@ namespace cw // go forward if( p->eli+i < locN && (pidx=match_index(loc + p->eli + i, pitch))!=kInvalidIdx ) { - locIdx = p->eli + i; + oLocId = p->eli + i; break; } // go backward if( p->eli >= (i-1) && (pidx=match_index(loc + p->eli - (i-1), pitch))!=kInvalidIdx ) { - locIdx = p->eli - (i-1); + oLocId = p->eli - (i-1); break; } } } - unsigned scEvtIdx = locIdx==kInvalidIdx ? kInvalidIdx : loc[locIdx].evtV[pidx].scEvtIdx; + unsigned scEvtIdx = oLocId==kInvalidIdx ? kInvalidIdx : loc[oLocId].evtV[pidx].scEvtIdx; - p->midiBuf[ p->mn-1 ].locIdx = locIdx; + p->midiBuf[ p->mn-1 ].oLocId = oLocId; p->midiBuf[ p->mn-1 ].scEvtIdx = scEvtIdx; - if( locIdx == kInvalidIdx ) + if( oLocId == kInvalidIdx ) ++p->missCnt; else { p->missCnt = 0; - p->eli = locIdx; + p->eli = oLocId; } // store the result - _store_result(p, locIdx, scEvtIdx, locIdx!=kInvalidIdx ? sfmatch::kSmMatchFl : 0, p->midiBuf + p->mn - 1); + _store_result(p, oLocId, scEvtIdx, oLocId!=kInvalidIdx ? sfmatch::kSmMatchFl : 0, p->midiBuf + p->mn - 1, cost(p->matchH) ); if( p->missCnt >= p->maxMissCnt ) { @@ -308,11 +352,11 @@ namespace cw } } - cw::rc_t cw::sftrack::create( handle_t& hRef, sfscore::handle_t scH, // Score handle. See cmScore.h. unsigned scWndN, // Length of the scores active search area. ** See Notes. unsigned midiWndN, // Length of the MIDI active note buffer. ** See Notes. + unsigned flags, callback_func_t cbFunc, // A cmScMatcherCb_t function to be called to notify the recipient of changes in the score matcher status. void* cbArg ) // User argument to 'cbFunc'. { @@ -340,7 +384,7 @@ cw::rc_t cw::sftrack::create( handle_t& hRef, p->maxMissCnt = p->stepCnt+1; p->rn = 2 * event_count(scH); p->res = mem::resize(p->res,p->rn); - p->printFl = false; + p->flags = flags; _reset(p,0); @@ -377,7 +421,7 @@ cw::rc_t cw::sftrack::reset( handle_t h, unsigned scLocIdx ) } -cw::rc_t cw::sftrack::exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned status, midi::byte_t d0, midi::byte_t d1, unsigned* scLocIdxPtr ) +cw::rc_t cw::sftrack::exec( handle_t h, double sec, unsigned smpIdx, unsigned muid, unsigned status, midi::byte_t d0, midi::byte_t d1, unsigned* scLocIdxPtr ) { sftrack_t* p = _handleToPtr(h); bool fl = p->mbi > 0; @@ -388,7 +432,7 @@ cw::rc_t cw::sftrack::exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned *scLocIdxPtr = kInvalidIdx; // update the MIDI buffer with the incoming note - if( _input_midi(p,smpIdx,muid,status,d0,d1) == false ) + if( _input_midi(p,sec,smpIdx,muid,status,d0,d1) == false ) return rc; // if the MIDI buffer transitioned to full then perform an initial scan sync. @@ -423,7 +467,7 @@ cw::rc_t cw::sftrack::exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned { const sfmatch::loc_t* loc = loc_base(p->matchH); - //printf("LOC:%i bar:%i\n",p->eli,loc[p->eli].barNumb); + // printf("LOC:%i bar:%i\n",p->eli,loc[p->eli].barNumb); *scLocIdxPtr = loc[p->eli].scLocIdx; } } @@ -456,7 +500,7 @@ namespace cw { void _test_cb_func( void* arg, result_t* rp ) { - printf("mni:%i muid:%i loc:%i scevt:%i\n",rp->mni,rp->muid,rp->locIdx,rp->scEvtIdx); + printf("mni:%i muid:%i loc:%i scevt:%i\n",rp->mni,rp->muid,rp->oLocId,rp->scEvtIdx); } } } @@ -468,10 +512,14 @@ cw::rc_t cw::sftrack::test( const object_t* cfg, sfscore::handle_t scoreH ) bool report_track_fl = false; const object_t* perf = nullptr; bool perf_enable_fl = false; + bool print_fl = false; + bool backtrack_fl = false; unsigned perf_loc_idx = 0; const char* perf_midi_fname = nullptr; unsigned maxScWndN = 10; unsigned maxMidiWndN = 7; + double srate = sample_rate(scoreH);; + unsigned flags = 0; const midi::file::trackMsg_t** midiMsgA = nullptr; unsigned midiMsgN = 0; @@ -483,6 +531,8 @@ cw::rc_t cw::sftrack::test( const object_t* cfg, sfscore::handle_t scoreH ) "maxMidiWndN", maxMidiWndN, "report_midi_file_fl",report_midi_file_fl, "report_track_fl",report_track_fl, + "print_fl",print_fl, + "backtrack_fl",backtrack_fl, "perf", perf)) != kOkRC ) { rc = cwLogError(rc,"sfscore test parse params failed."); @@ -497,8 +547,11 @@ cw::rc_t cw::sftrack::test( const object_t* cfg, sfscore::handle_t scoreH ) goto errLabel; } + flags += print_fl ? kPrintFl : 0; + flags += backtrack_fl ? kBacktrackResultsFl : 0; + // create the score tracker - if((rc = create(trackH, scoreH, maxScWndN, maxMidiWndN, _test_cb_func, nullptr )) != kOkRC ) + if((rc = create(trackH, scoreH, maxScWndN, maxMidiWndN, flags, _test_cb_func, nullptr )) != kOkRC ) { rc = cwLogError(rc,"sftrack create failed."); goto errLabel; @@ -528,16 +581,19 @@ cw::rc_t cw::sftrack::test( const object_t* cfg, sfscore::handle_t scoreH ) { unsigned scLocIdx = kInvalidIdx; const midi::file::trackMsg_t* trk = midiMsgA[i]; - unsigned smpIdx = 0; // if this is a note on message if( midi::isNoteOnStatus(trk->status) ) - if((rc = exec(trackH, smpIdx, trk->uid, trk->status, trk->u.chMsgPtr->d0, trk->u.chMsgPtr->d1, &scLocIdx)) != kOkRC ) + { + double secs = trk->amicro / 1000000.0; + unsigned smpIdx = secs / srate; + if((rc = exec(trackH, secs, smpIdx, trk->uid, trk->status, trk->u.chMsgPtr->d0, trk->u.chMsgPtr->d1, &scLocIdx)) != kOkRC ) { if( rc != kEofRC ) rc = cwLogError(rc,"tracker exec() failed."); goto errLabel; } + } } diff --git a/cwSfTrack.h b/cwSfTrack.h index 8ac323b..2d5e94a 100644 --- a/cwSfTrack.h +++ b/cwSfTrack.h @@ -7,66 +7,57 @@ namespace cw { typedef struct result_str { - unsigned locIdx; // index into cmScMatch_t.loc[] + unsigned index; // index of this record into the internal result[] array or kInvalidIdx if it is not in the array. + unsigned oLocId; // index into cwSfMatch_t.loc[] unsigned scEvtIdx; // score event index unsigned mni; // index of the performed MIDI event associated with this score location + double sec; // seconds of the performed MIDI event unsigned smpIdx; // sample time index of performed MIDI event - unsigned muid; // MIDI file event msg unique id (See cmMidiTrackMsg_t.uid) + unsigned muid; // MIDI file event msg unique id (See cwMidiFile trackMsg_t.uid) unsigned pitch; // performed pitch unsigned vel; // performed velocity - unsigned flags; // smTruePosFl | smFalsePosFl + unsigned flags; // See kSm???Fl + double cost; // match cost } result_t; struct sftrack_str; typedef void (*callback_func_t)( void* arg, result_t* rp ); - typedef struct sftrack_str - { - callback_func_t cbFunc; - void* cbArg; - sfmatch::handle_t matchH; - unsigned mn; // size of midiBuf[] - sfmatch::midi_t* midiBuf; // midiBuf[mn] - - result_t* res; // res[rn] - unsigned rn; // length of res[] (set to 2*score event count) - unsigned ri; // next avail res[] recd. - - double s_opt; // - unsigned missCnt; // current count of consecutive trailing non-matches - unsigned ili; // index into loc[] to start scan following reset - unsigned eli; // index into loc[] of the last positive match. - unsigned mni; // current count of MIDI events since the last call to cmScMatcherReset() - unsigned mbi; // index of oldest MIDI event in midiBuf[]; stays at 0 when the buffer is full. - unsigned begSyncLocIdx; // start of score window, in mp->loc[], of best match in previous scan - unsigned initHopCnt; // max window hops during the initial (when the MIDI buffer fills for first time) sync scan - unsigned stepCnt; // count of forward/backward score loc's to examine for a match during cmScMatcherStep(). - unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a scan. - unsigned scanCnt; // current count of times a resync-scan was executed during cmScMatcherStep() - - bool printFl; - } sftrack_t; - typedef handle handle_t; + + enum { + // Set this flag to overwrite results in the internal result[] array when + // the matcher backtracks. If this flag is set then the result[] array + // will have one record for each unique MIDI event (count of calls to exec()). + // If it is cleared then the result array will have a unique entry for + // each alignment and re-alignment. In this case a given performed + // event may have multiple entries in the result[] array if it is + // re-aligned. result[] records with identical 'muid' fields indicate + // that the performed event represented by the 'muid' was re-aligned.. + kBacktrackResultsFl = 0x01, + kPrintFl = 0x02 + }; rc_t create( handle_t& hRef, - sfscore::handle_t scH, // Score handle. See cmScore.h. + sfscore::handle_t scH, // Score handle. See cwSfScore.h. unsigned scWndN, // Length of the scores active search area. ** See Notes. unsigned midiWndN, // Length of the MIDI active note buffer. ** See Notes. - callback_func_t cbFunc, // A cmScMatcherCb_t function to be called to notify the recipient of changes in the score matcher status. + unsigned flags, // See k???Fl + callback_func_t cbFunc, // A function to be called to notify the recipient of changes in the score matcher status. void* cbArg ); // User argument to 'cbFunc'. // Notes: - // The cmScMatcher maintains an internal cmScMatch object which is used to attempt to find the + // The cwSfTrack maintains an internal cwSfMatch object which is used to attempt to find the // best match between the current MIDI active note buffer and the current score search area. - // 'scWndN' is used to set the cmScMatch 'locN' argument. + // 'scWndN' is used to set the cwSfMatch 'locN' argument. // 'midiWndN' sets the length of the MIDI FIFO which is used to match to the score with // each recceived MIDI note. // 'midiWndN' must be <= 'scWndN'. rc_t destroy( handle_t& hRef ); + // Set the starting position of the tracker and clear the internal 'result' array. // 'scLocIdx' is a score index as used by cmScoreLoc(scH) not into p->mp->loc[]. rc_t reset( handle_t h, unsigned scLocIdx ); @@ -76,7 +67,7 @@ namespace cw // Returns the index into p->mp->loc[] of the start of the best // match score window. The score associated // with this match is stored in s_opt. - //unsigned scan( handle_t h, unsigned bli, unsigned hopCnt ); + //unsigned _scan( handle_t h, unsigned bli, unsigned hopCnt ); // Step forward/back by p->stepCnt from p->eli. // p->eli must therefore be valid prior to calling this function. @@ -84,26 +75,42 @@ namespace cw // missed then automatically run cmScAlignScan(). // Return cmEofRC if the end of the score is encountered. // Return cmSubSysFailRC if an internal scan resync. failed. - //rc_t step( handle_t h ); + //rc_t _step( handle_t h ); - // This function calls cmScMatcherScan() and cmScMatcherStep() internally. + // This function calls _scan() and _step() internally. + // // If 'status' is not kNonMidiMdId then the function returns without changing the - // state of the object. In other words the matcher only recognizes MIDI note-on messages. + // state of the object. (i.e. the matcher only recognizes MIDI note-on messages.) + // // If the MIDI note passed by the call results in a successful match then - // p->eli will be updated to the location in p->mp->loc[] of the latest + // p->eli will be updated to the location in p->sfMatch.loc[] of the latest // match, the MIDI note in p->midiBuf[] associated with this match // will be assigned a valid locIdx and scLocIdx values, and *scLocIdxPtr // will be set with the matched scLocIdx of the match. + // // If this call does not result in a successful match *scLocIdxPtr is set - // to cmInvalidIdx. + // to kInvalidIdx. + // + // Calling exec() may result in alignment/re-alignment of both the incoming note + // and/or previous notes. (i.e. previous note that did not align may be found + // to align or previous notes that were found to align may now not align). + // The exact status of the alignment and re-alignments are held in the + // internal 'result' array. + // + // Every alignment/realignment of an event results in a call to the callback function. + // This means that multiple callbacks may occur as the result of a single + // call to exec(). + // 'muid' is the unique id associated with this MIDI event under the circumstances - // that the event came from a MIDI file. See cmMidiFile.h cmMidiTrackMsg_t.uid. + // that the event came from a MIDI file. See cwMidiFile.h trackMsg_t.uid. + // // Return: - // cmOkRC - Continue processing MIDI events. - // cmEofRC - The end of the score was encountered. - // cmInvalidArgRC - scan failed or the object was in an invalid state to attempt a match. - // cmSubSysFailRC - a scan resync failed in cmScMatcherStep(). + // cmOkRC - Continue processing MIDI events. + // cmEofRC - The end of the score was encountered. + // cmInvalidArgRC - scan failed or the object was in an invalid state to attempt a match. + // cmSubSysFailRC - a scan resync failed in cmScMatcherStep(). rc_t exec( handle_t h, + double sec, unsigned smpIdx, unsigned muid, unsigned status,