From ed4bf8709d7816371524cb771117b82f7171c82a Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 16 May 2023 09:14:20 -0400 Subject: [PATCH] cwScoreFollower.h/cpp, cwSvgScoreFollow.h/cpp : Complete working version of score follower and SVG result reendering. --- cwScoreFollower.cpp | 286 ++++++++++++++++++++++---------- cwScoreFollower.h | 5 +- cwSvgScoreFollow.cpp | 376 +++++++++++++++++++++++++++++++------------ cwSvgScoreFollow.h | 4 +- 4 files changed, 481 insertions(+), 190 deletions(-) diff --git a/cwScoreFollower.cpp b/cwScoreFollower.cpp index dc9a55a..036309e 100644 --- a/cwScoreFollower.cpp +++ b/cwScoreFollower.cpp @@ -36,14 +36,12 @@ namespace cw { namespace score_follower { - - typedef struct score_follower_str { double srate; unsigned search_area_locN; unsigned key_wnd_locN; - char* score_csv_fname; + char* cm_score_csv_fname; cmCtx_t* cmCtx; cmScH_t cmScoreH; cmScMatcher* matcher; @@ -73,9 +71,9 @@ namespace cw { rc_t rc = kOkRC; - const char* score_csv_fname; + const char* cm_score_csv_fname; - if((rc = cfg->getv("score_csv_fname", score_csv_fname, + if((rc = cfg->getv("cm_score_csv_fname", cm_score_csv_fname, "search_area_locN", p->search_area_locN, "key_wnd_locN", p->key_wnd_locN )) != kOkRC ) { @@ -83,7 +81,7 @@ namespace cw goto errLabel; } - if((p->score_csv_fname = filesys::expandPath( score_csv_fname )) == nullptr ) + if((p->cm_score_csv_fname = filesys::expandPath( cm_score_csv_fname )) == nullptr ) { rc = cwLogError(kOpFailRC,"Score follower score file expansion failed."); goto errLabel; @@ -130,7 +128,8 @@ namespace cw if((rc = _get_max_cw_loc(p,p->cwLocToCmLocN)) != kOkRC ) goto errLabel; - + + p->cwLocToCmLocN += 1; // incr. by 1 because _get_max_cw_loc() returns an index but we need a count p->cwLocToCmLocA = mem::allocZ( p->cwLocToCmLocN ); cmEvtN = cmScoreEvtCount( p->cmScoreH ); @@ -187,8 +186,9 @@ namespace cw { mem::release(p->cmLocToCwLocA); mem::release(p->cwLocToCmLocA); - mem::release(p->score_csv_fname); + mem::release(p->cm_score_csv_fname); mem::release(p->perfA); + mem::release(p->match_idA); cmScMatcherFree(&p->matcher); cmScoreFinalize(&p->cmScoreH); mem::release(p); @@ -251,9 +251,9 @@ cw::rc_t cw::score_follower::create( handle_t& hRef, const object_t* cfg, cm::ha goto errLabel; // create the the score follower reference score - if((scoreRC = cmScoreInitialize( p->cmCtx, &p->cmScoreH, p->score_csv_fname, srate, nullptr, 0, nullptr, nullptr, cmSymTblH )) != cmOkRC ) + if((scoreRC = cmScoreInitialize( p->cmCtx, &p->cmScoreH, p->cm_score_csv_fname, srate, nullptr, 0, nullptr, nullptr, cmSymTblH )) != cmOkRC ) { - cwLogError(kOpFailRC,"The score could not be initialized from '%s'. cmRC:%i.",p->score_csv_fname); + rc = cwLogError(kOpFailRC,"The score could not be initialized from '%s'. cmRC:%i.",p->cm_score_csv_fname); goto errLabel; } @@ -348,6 +348,7 @@ cw::rc_t cw::score_follower::reset( handle_t h, unsigned cwLocId ) } p->perf_idx = 0; + clear_match_id_array(h); } errLabel: @@ -395,13 +396,13 @@ cw::rc_t cw::score_follower::exec( handle_t h, double sec, unsigned smpIdx, uns ssf_note_on_t* pno = p->perfA + p->perf_idx; pno->sec = sec; + pno->muid = muid; pno->pitch = d0; pno->vel = d1; p->perf_idx += 1; if( p->perf_idx >= p->perfN ) cwLogWarning("The cw score follower performance cache is full."); } - return rc; @@ -421,17 +422,17 @@ void cw::score_follower::clear_match_id_array( handle_t h ) p->match_id_curN = 0; } -cw::rc_t cw::score_follower::write_svg_file( handle_t h, const char* out_fname ) +cw::rc_t cw::score_follower::write_svg_file( handle_t h, const char* out_fname, bool show_muid_fl ) { score_follower_t* p = _handleToPtr(h); - return svgScoreFollowWrite( p->cmScoreH, p->matcher, p->perfA, p->perf_idx, out_fname ); + return svgScoreFollowWrite( p->cmScoreH, p->matcher, p->perfA, p->perf_idx, out_fname, show_muid_fl ); } void cw::score_follower::score_report( handle_t h, const char* out_fname ) { score_follower_t* p = _handleToPtr(h); - cmScoreReport( p->cmCtx, p->score_csv_fname, out_fname ); + cmScoreReport( p->cmCtx, p->cm_score_csv_fname, out_fname ); } cw::rc_t cw::score_follower::midi_state_rt_report( handle_t h, const char* out_fname ) @@ -467,49 +468,47 @@ namespace cw { typedef struct test_str { - char* midi_fname; - char* out_dir; - double srate; - const object_t* cfg; - midi::file::handle_t mfH; - + double srate; + char* out_dir; + const object_t* cfg; + const char* out_svg_fname; + const char* out_midi_csv_fname; + const char* out_cm_score_rpt_fname; + const object_t* perfL; + bool pre_test_fl; // insert a dummy note prior to the first perf. note to test the 'pre' functionality of the SVG generation + bool show_muid_fl; // true=show perf. note 'muid' in SVG file, false=show sequence id } test_t; rc_t _test_destroy( test_t* p ) { rc_t rc = kOkRC; - mem::release(p->midi_fname); mem::release(p->out_dir); - - midi::file::close(p->mfH); return rc; } + rc_t _test_parse_cfg( test_t* p, const object_t* cfg ) { - rc_t rc; - const char* midi_fname = nullptr; - const char* out_dir = nullptr; - + rc_t rc = kOkRC; + const char* out_dir = nullptr; + // read the test cfg. - if((rc = cfg->getv("midi_fname", midi_fname, - "srate", p->srate, - "cfg", p->cfg, - "out_dir", out_dir )) != kOkRC ) + if((rc = cfg->getv("perfL", p->perfL, + "srate", p->srate, + "pre_test_fl", p->pre_test_fl, + "show_muid_fl", p->show_muid_fl, + "cfg", p->cfg, + "out_dir", out_dir, + "out_svg_fname",p->out_svg_fname, + "out_midi_csv_fname", p->out_midi_csv_fname, + "out_cm_score_rpt_fname", p->out_cm_score_rpt_fname)) != kOkRC ) { rc = cwLogError(rc,"Score follower test cfg. parse failed."); goto errLabel; } - // expand the MIDI filename - if((p->midi_fname = filesys::expandPath( midi_fname )) == nullptr ) - { - rc = cwLogError(kOpFailRC,"The MIDI file path expansion failed."); - goto errLabel; - } - // expand the output directory if((p->out_dir = filesys::expandPath( out_dir)) == nullptr ) { @@ -526,14 +525,164 @@ namespace cw { goto errLabel; } } + errLabel: - if(rc != kOkRC ) + return rc; + } + + + rc_t _score_follow_one_perf( test_t* p, handle_t& sfH, unsigned perf_idx ) + { + rc_t rc = kOkRC; + bool pre_test_fl= p->pre_test_fl; + bool enable_fl = true; + const char* perf_label = nullptr; + const char* midi_fname = nullptr; + char* out_dir = nullptr; + char* fname = nullptr; + unsigned start_loc = 0; + const object_t* perf = nullptr; + unsigned msgN = 0; + midi::file::handle_t mfH; + const midi::file::trackMsg_t** msgA = nullptr; + + // get the perf. record + if((perf = p->perfL->child_ele(perf_idx)) == nullptr ) { - _test_destroy(p); - rc = cwLogError(rc,"Score follower test parse cfg. failed."); + rc = cwLogError(kSyntaxErrorRC,"Error accessing the cfg record for perf. record index:%i.",perf_idx); + goto errLabel; } + // parse the performance record + if((rc = perf->getv("label",perf_label, + "enable_fl",enable_fl, + "start_loc", start_loc, + "midi_fname", midi_fname)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Error parsing cfg record for perf. record index:%i.",perf_idx); + goto errLabel; + } + + if( !enable_fl ) + goto errLabel; + + // create the output directory + if((out_dir = filesys::makeFn(p->out_dir,perf_label,nullptr,nullptr)) == nullptr ) + { + rc = cwLogError(kOpFailRC,"Directory name formation failed on '%s'.",cwStringNullGuard(out_dir)); + goto errLabel; + } + + // create the output directory + if((rc = filesys::makeDir(out_dir)) != kOkRC ) + { + rc = cwLogError(kOpFailRC,"mkdir failed on '%s'.",cwStringNullGuard(out_dir)); + goto errLabel; + } + + // expand the MIDI filename + if((fname = filesys::expandPath( midi_fname )) == nullptr ) + { + rc = cwLogError(kOpFailRC,"The MIDI file path expansion failed."); + goto errLabel; + } + + // open the midi file + if((rc = midi::file::open( mfH, fname )) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file open failed on '%s'.",cwStringNullGuard(fname)); + goto errLabel; + } + + mem::release(fname); + + // set the score follower to the start location + if((rc = reset( sfH, start_loc)) != kOkRC ) + goto errLabel; + + + // get a pointer to a time sorted list of MIDI messages in the file + if(((msgN = msgCount( mfH )) == 0) || ((msgA = midi::file::msgArray( mfH )) == nullptr) ) + { + rc = cwLogError(rc,"MIDI file msg array is empty or corrupp->"); + goto errLabel; + } + + for(unsigned i=0; iamicro/1e6; + unsigned long smpIdx = (unsigned long)(p->srate * m->amicro/1e6); + bool newMatchFl = false; + + if( pre_test_fl ) + { + // test the 'pre' ref location by adding an extra note before the first note + exec(sfH, sec, smpIdx, m->uid-1, m->status, 60, m->u.chMsgPtr->d1, newMatchFl); + pre_test_fl = false; + } + + if((rc = exec(sfH, sec, smpIdx, m->uid, m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1, newMatchFl )) != kOkRC ) + { + rc = cwLogError(rc,"score follower exec failed."); + break; + } + + if( newMatchFl ) + { + unsigned matchIdN = 0; + const unsigned* matchIdA = current_match_id_array(sfH, matchIdN ); + + for(unsigned i=0; iout_svg_fname,nullptr,nullptr)) == nullptr ) + { + cwLogError(kOpFailRC,"The output SVG filename formation failed."); + goto errLabel; + } + + // write the score following result SVG + if((rc = write_svg_file(sfH,fname,p->show_muid_fl)) != kOkRC ) + { + rc = cwLogError(rc,"SVG report file create failed."); + goto errLabel; + } + + mem::release(fname); + + + // create the MIDI file as a CSV + if((fname = filesys::makeFn(out_dir,p->out_midi_csv_fname,nullptr,nullptr)) == nullptr ) + { + cwLogError(kOpFailRC,"The output MIDI CSV filename formation failed."); + goto errLabel; + } + + // convert the MIDI file to a CSV + if((rc = midi::file::genCsvFile( filename(mfH), fname, false)) != kOkRC ) + { + cwLogError(rc,"MIDI file to CSV failed on '%s'.",cwStringNullGuard(filename(mfH))); + } + + mem::release(fname); + + errLabel: + mem::release(out_dir); + mem::release(fname); + close(mfH); + return rc; } @@ -549,9 +698,8 @@ cw::rc_t cw::score_follower::test( const object_t* cfg ) cm::handle_t cmCtxH; handle_t sfH; - unsigned msgN = 0; - const midi::file::trackMsg_t** msgA = nullptr; - + char* fname = nullptr; + // parse the test cfg if((rc = _test_parse_cfg( &t, cfg )) != kOkRC ) goto errLabel; @@ -564,53 +712,23 @@ cw::rc_t cw::score_follower::test( const object_t* cfg ) if((rc = create( sfH, t.cfg, cmCtxH, t.srate )) != kOkRC ) goto errLabel; - if((rc = reset( sfH, 0)) != kOkRC ) - goto errLabel; - - // open the midi file - if((rc = midi::file::open( t.mfH, t.midi_fname )) != kOkRC ) + // create the cm score report filename + if((fname = filesys::makeFn(t.out_dir,t.out_cm_score_rpt_fname,nullptr,nullptr)) == nullptr ) { - rc = cwLogError(rc,"MIDI file open failed on '%s'.",cwStringNullGuard(t.midi_fname)); + cwLogError(kOpFailRC,"The output cm score filename formation failed."); goto errLabel; } - - // get a pointer to a time sorted list of MIDI messages in the file - if(((msgN = msgCount( t.mfH )) == 0) || ((msgA = midi::file::msgArray( t.mfH )) == nullptr) ) - { - rc = cwLogError(rc,"MIDI file msg array is empty or corrupt."); - goto errLabel; - } - - for(unsigned i=0; iamicro/1e6; - unsigned long smpIdx = (unsigned long)(t.srate * m->amicro/1e6); - bool newMatchFl = false; - if((rc = exec(sfH, sec, smpIdx, m->uid, m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1, newMatchFl )) != kOkRC ) - { - rc = cwLogError(rc,"score follower exec failed."); - goto errLabel; - } + // write the cm score report + score_report(sfH,fname); - if( newMatchFl ) - { - unsigned matchIdN = 0; - const unsigned* matchIdA = current_match_id_array(sfH, matchIdN ); - - for(unsigned i=0; ichild_count(); ++perf_idx) + _score_follow_one_perf(&t,sfH,perf_idx); + errLabel: + mem::release(fname); destroy(sfH); cm::destroy(cmCtxH); _test_destroy(&t); diff --git a/cwScoreFollower.h b/cwScoreFollower.h index 917727c..737ccf1 100644 --- a/cwScoreFollower.h +++ b/cwScoreFollower.h @@ -12,6 +12,7 @@ namespace cw rc_t destroy( handle_t& hRef ); + // Set the starting search location and calls clear_match_id_array(). rc_t reset( handle_t h, unsigned loc ); // If 'new_match_fl_ref' is returned as true then there are new match id's in the current_match_id_array[] @@ -24,7 +25,9 @@ namespace cw void clear_match_id_array( handle_t h ); // Write an SVG file containing a graphic view of the score following results since the last call to reset(). - rc_t write_svg_file( handle_t h, const char* out_fname ); + // Set show_muid_fl to true to display the 'muid' of the performed notes in the + // SVG rendering, otherwise the performed note sequence (order of arrival) id is shown. + rc_t write_svg_file( handle_t h, const char* out_fname, bool show_muid_fl=false ); // Write the score to 'out_fname'. void score_report( handle_t h, const char* out_fname ); diff --git a/cwSvgScoreFollow.cpp b/cwSvgScoreFollow.cpp index 4b51493..d5cf20d 100644 --- a/cwSvgScoreFollow.cpp +++ b/cwSvgScoreFollow.cpp @@ -38,63 +38,71 @@ #define ROW_0_Y 0 +#define PRE_REF_IDX 0 namespace cw { namespace score_follower { struct ssf_ref_note_str; - + + enum { + kLocTId, + kIdTId, + kPreTId + }; + + // bar/section or location anchor + // (one for each top row of UI elements) typedef struct ssf_ref_str { - bool locFl; // true = u.loc false=u.id - unsigned left; - unsigned top; - unsigned col_bottom; + unsigned tid; // See k??TId + unsigned left; + unsigned top; + unsigned col_bottom; struct ssf_ref_note_str* refNoteL; // ref_note's linked to this ref union { - cmScoreLoc_t* loc; - unsigned id; // bar/section id + cmScoreLoc_t* loc; // cmScore location + unsigned id; // bar/section id } u; } ssf_ref_t; + // ref. score note that performed notes should match to typedef struct ssf_ref_note_str { - ssf_ref_t* ref; - cmScoreEvt_t* evt; - unsigned top; + ssf_ref_t* ref; // loc UI component that this ref. note belongs to + cmScoreEvt_t* evt; // cmScore event assoc'd with this note + unsigned top; + unsigned cnt; // count of perf notes that matched to this ref-note + unsigned cwLocId; struct ssf_ref_note_str* link; } ssf_ref_note_t; typedef struct ssf_perf_note_str { - unsigned left; - unsigned top; - ssf_ref_note_t* refNote; - const ssf_note_on_t* msg; - + unsigned left; + unsigned top; + ssf_ref_note_t* refNote; // ref. note that this perf. note matches to + const ssf_note_on_t* msg; // MIDI info for this performed note + unsigned seqId; // performed note sequence id == index into perfA[] of this note + bool isVisibleFl; // true if this rect is visible + + // 'duplicate' perf notes occur when the same perf note is connected + // to multiple references. This can occur when the score follower backtracks. + struct ssf_perf_note_str* dupl_list; // list of 'duplicate' perf. notes. struct ssf_perf_note_str* dupl_link; } ssf_perf_note_t; - typedef struct ssf_match_str - { - - ssf_note_on_t* perf; - ssf_ref_note_t* ref; - } ssf_match_t; - - typedef struct ssf_str { svg::handle_t svgH; cmScH_t cmScH; cmScMatcher* matcher; - ssf_ref_t* refA; unsigned refAllocN; unsigned refN; @@ -106,13 +114,27 @@ namespace cw ssf_perf_note_t* perfA; unsigned perfAllocN; unsigned perfN; + + unsigned minMatchLoc; + unsigned maxMatchLoc; } ssf_t; - rc_t _write_rect( ssf_t* p, unsigned left, unsigned top, const char* label, const char* classLabel ) + rc_t _write_rect( ssf_t* p, unsigned left, unsigned top, const char* label, const char* classLabel, const char* classLabel2=nullptr, unsigned cwLocId=kInvalidId ) { rc_t rc = kOkRC; + + int n = textLength(classLabel) + textLength(classLabel2) + 2; + char buf[ n + 1 ]; + + if( classLabel2 != nullptr ) + { + snprintf(buf,n,"%s %s",classLabel,classLabel2); + buf[n] = 0; + classLabel = buf; + } + if((rc = svg::rect(p->svgH,left,top,BOX_W,BOX_H,"class",classLabel,nullptr)) != kOkRC ) { rc = cwLogError(rc,"Error writing SVG rect."); @@ -124,6 +146,18 @@ namespace cw rc = cwLogError(rc,"Error writing SVG text."); goto errLabel; } + + if( cwLocId != kInvalidId ) + { + unsigned locIdCharN = 31; + char locIdBuf[ locIdCharN + 1 ]; + snprintf(locIdBuf,locIdCharN,"%i",cwLocId); + if((rc = svg::text(p->svgH,left,top+40,locIdBuf)) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG cwLocId text."); + goto errLabel; + } + } errLabel: return rc; @@ -137,46 +171,68 @@ namespace cw return _write_rect(p,left,top,label,classLabel); } - rc_t _write_rect_id_pitch( ssf_t* p, unsigned left, unsigned top, const char* classLabel, unsigned id, unsigned midi_pitch ) + rc_t _write_rect_pitch( ssf_t* p, unsigned left, unsigned top, const char* classLabel, unsigned midi_pitch, bool noMatchFl,unsigned cnt=kInvalidCnt, unsigned cwLocId=kInvalidId) { - assert( midi_pitch < 128 ); - const unsigned labelCharN = 63; - char label[labelCharN+1]; - snprintf(label,labelCharN,"%s %5i",midi::midiToSciPitch((uint8_t)midi_pitch),id); - return _write_rect(p,left,top,label,classLabel); + const unsigned labelCharN = 63; + char label[labelCharN+1]; + const char* classLabel2 = noMatchFl ? "no_match" : nullptr; + + if( midi_pitch == midi::kInvalidMidiPitch ) + snprintf(label,labelCharN,"%s","pre"); + else + if( cnt == kInvalidCnt ) + snprintf(label,labelCharN,"%s",midi::midiToSciPitch((uint8_t)midi_pitch)); + else + { + snprintf(label,labelCharN,"%s %i",midi::midiToSciPitch((uint8_t)midi_pitch),cnt); + classLabel2 = "multi_match"; + } + + return _write_rect(p,left,top,label,classLabel,classLabel2,cwLocId); } - - rc_t _write_rect_pitch( ssf_t* p, unsigned left, unsigned top, const char* classLabel, unsigned midi_pitch ) - { - assert( midi_pitch < 128 ); - const unsigned labelCharN = 63; - char label[labelCharN+1]; - snprintf(label,labelCharN,"%s",midi::midiToSciPitch((uint8_t)midi_pitch)); - return _write_rect(p,left,top,label,classLabel); - } - - - rc_t _write_ref( ssf_t* p ) + + rc_t _write( ssf_t* p, bool show_muid_fl ) { rc_t rc = kOkRC; + + bool fl = false; + unsigned offset_x = 0; for(unsigned i=0; irefN; ++i) { - ssf_ref_t* r = p->refA + i; - const char* classLabel = r->locFl ? "loc" : "bar"; - unsigned id = r->locFl ? r->u.loc->index : r->u.id; + ssf_ref_t* r = p->refA + i; + const char* classLabel = r->tid==kLocTId ? "loc" : (r->tid==kIdTId ? "bar" : "pre"); + unsigned id = r->tid==kLocTId ? r->u.loc->index : (r->tid==kIdTId ? r->u.id : 0); + + if( fl==false && r->tid==kLocTId && r->u.loc->index == p->minMatchLoc ) + { + fl = true; + offset_x = r->left - (BOX_W + BORD_X) ; + } + + if( fl && r->tid==kLocTId && r->u.loc->index > p->maxMatchLoc ) + fl = false; + + if( !fl && r->tid != kPreTId ) + continue; + + if( r->left < offset_x && r->tid != kPreTId ) + continue; // write ref rect - if((rc = _write_rect_id( p, r->left, r->top, classLabel, id )) != kOkRC ) + if((rc = _write_rect_id( p, r->left-offset_x, r->top, classLabel, id )) != kOkRC ) { rc = cwLogError(rc,"Error writing 'loc' rect id:%i.",r->u.loc->index); goto errLabel; } // write the ref-note - for(ssf_ref_note_t* rn=r->refNoteL; rn!=nullptr; rn=rn->link) + for(ssf_ref_note_t* rn=r->refNoteL; rn!=nullptr; rn = rn->link) { - if((rc = _write_rect_pitch( p, r->left, rn->top, "ref_note", rn->evt->pitch )) != kOkRC ) + unsigned pitch = rn->evt == nullptr ? midi::kInvalidMidiPitch : rn->evt->pitch; + unsigned cnt = rn->cnt > 1 ? rn->cnt : kInvalidCnt; + bool noMatchFl = rn->cnt == 0; + if((rc = _write_rect_pitch( p, r->left-offset_x, rn->top, "ref_note", pitch, noMatchFl, cnt, rn->cwLocId )) != kOkRC ) { rc = cwLogError(rc,"Error writing 'ref_note' rect."); goto errLabel; @@ -184,15 +240,45 @@ namespace cw } } + // write the pref-notes for(unsigned i=0; iperfN; ++i) { ssf_perf_note_t* pn = p->perfA + i; - if((rc = _write_rect_pitch( p, pn->left, pn->top, "perf_note", pn->msg->pitch )) != kOkRC ) + + bool isPreFl = pn->refNote !=nullptr && pn->refNote->ref->tid == kPreTId; + bool noMatchFl = pn->refNote==nullptr || isPreFl; + + // Rectangles whose 'left' coord is less than 'offset' occur prior to p->minMatchLoc + // (but the 'pre' column is always shown) + if( pn->left >= offset_x || isPreFl ) { + pn->isVisibleFl = true; + unsigned left = isPreFl ? pn->left : pn->left-offset_x; + unsigned perf_id = show_muid_fl ? pn->msg->muid : pn->seqId; + + if((rc = _write_rect_pitch( p, left, pn->top, "perf_note", pn->msg->pitch, noMatchFl, kInvalidCnt, perf_id )) != kOkRC ) + { rc = cwLogError(rc,"Error writing 'pref_note' rect."); goto errLabel; + } + } + } + + for(unsigned i=0; iperfN; ++i) + { + ssf_perf_note_t* pn = p->perfA + i; + if( pn->isVisibleFl) + { + ssf_perf_note_t* pn0 = pn; + for(ssf_perf_note_t* pn1=pn->dupl_list; pn1!=nullptr; pn1=pn1->dupl_link) + { + if( pn1->isVisibleFl ) + { + line(p->svgH,pn0->left-offset_x,pn0->top,pn1->left-offset_x,pn0->top); + pn0 = pn1; + } + } } - } errLabel: @@ -210,6 +296,7 @@ namespace cw { mem::release(p->refA); mem::release(p->refNoteA); + mem::release(p->perfA); mem::release(p); } @@ -220,11 +307,10 @@ namespace cw { ssf_ref_t* ref = nullptr; if( p->refN < p->refAllocN ) - { - - ref = p->refA + p->refN; - ref->left = x_coord_ref; - ref->top = y_coord; + { + ref = p->refA + p->refN; + ref->left = x_coord_ref; + ref->top = y_coord; ref->col_bottom = y_coord + BOX_H + BORD_Y; p->refN += 1; @@ -238,12 +324,21 @@ namespace cw return ref; } + void _create_pre_ref( ssf_t* p, unsigned& x_coord_ref, unsigned y_coord ) + { + ssf_ref_t* ref; + if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr ) + { + ref->tid = kPreTId; + } + } + void _create_bar_ref( ssf_t* p, const cmScoreEvt_t* e, unsigned& x_coord_ref, unsigned y_coord ) { ssf_ref_t* ref; if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr ) { - ref->locFl = false; + ref->tid = kIdTId; ref->u.id = e->barNumb; } } @@ -253,7 +348,7 @@ namespace cw ssf_ref_t* ref; if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr ) { - ref->locFl = true; + ref->tid = kLocTId; ref->u.loc = cmScoreLoc(p->cmScH, e->locIdx ); cwAssert( ref->u.loc != nullptr and ref->u.loc->index == e->locIdx ); @@ -267,9 +362,11 @@ namespace cw unsigned cmEvtN = cmScoreEvtCount(p->cmScH); unsigned loc_idx = kInvalidIdx; - p->refAllocN = cmEvtN; + p->refAllocN = cmEvtN + 1; p->refA = mem::allocZ(p->refAllocN); p->refN = 0; + + _create_pre_ref(p, x_coord_ref, y_coord ); for(unsigned i=0; ilocIdx; } - } + } + + // reset the min and max loc index + p->minMatchLoc = loc_idx == kInvalidIdx ? 0 : loc_idx; + p->maxMatchLoc = 0; + } + + rc_t _attach_ref_note( ssf_t* p, unsigned ref_idx, cmScoreEvt_t* evt ) + { + rc_t rc = kOkRC; + ssf_ref_note_t* rn = p->refNoteA + p->refNoteN + 1; + + rn->ref = p->refA + ref_idx; + rn->evt = evt; + rn->top = rn->ref->col_bottom; + rn->cnt = 0; + rn->cwLocId = evt==nullptr ? 0 : evt->csvEventId; + rn->link = rn->ref->refNoteL; + + rn->ref->refNoteL = rn; + rn->ref->col_bottom += BOX_H + BORD_Y; + + p->refNoteN += 1; + if( p->refNoteN >= p->refNoteAllocN ) + { + rc = cwLogError(kBufTooSmallRC,"The ref note array in the SVG score writer is too small."); + goto errLabel; + } + + errLabel: + return rc; + } rc_t _create_ref_note_array( ssf_t* p ) { rc_t rc = kOkRC; - p->refNoteAllocN = p->refAllocN; + p->refNoteAllocN = p->refAllocN + 1; p->refNoteA = mem::allocZ(p->refNoteAllocN); p->refNoteN = 0; - for(unsigned i=0; irefN; ++i) - if( p->refA[i].locFl ) - for(unsigned j=0; jrefA[i].u.loc->evtCnt; ++j) - if( p->refA[i].u.loc->evtArray[j]->type == kNonEvtScId && p->refNoteN < p->refNoteAllocN ) + // attach the 'pre' ref note + _attach_ref_note(p,PRE_REF_IDX,nullptr); + + for(unsigned ri=PRE_REF_IDX+1; rirefN; ++ri) + if( p->refA[ri].tid == kLocTId ) + for(unsigned j=0; jrefA[ri].u.loc->evtCnt; ++j) + if( p->refA[ri].u.loc->evtArray[j]->type == kNonEvtScId && p->refNoteN < p->refNoteAllocN ) { - ssf_ref_note_t* rn = p->refNoteA + p->refNoteN + 1; - rn->ref = p->refA + i; - rn->evt = p->refA[i].u.loc->evtArray[j]; - rn->top = rn->ref->col_bottom; - rn->link = rn->ref->refNoteL; - rn->ref->refNoteL = rn; - - rn->ref->col_bottom += BOX_H + BORD_Y; - - p->refNoteN += 1; - if( p->refNoteN >= p->refNoteAllocN ) - { - rc = cwLogError(kBufTooSmallRC,"The ref note array in the SVG score writer is too small."); + if((rc = _attach_ref_note(p,ri,p->refA[ri].u.loc->evtArray[j])) != kOkRC ) goto errLabel; - } } errLabel: @@ -329,7 +447,7 @@ namespace cw { ssf_ref_t* r = p->refA + i; // if this is the ref record for the target location - if( r->locFl and r->u.loc->index == e->locIdx ) + if( r->tid == kLocTId and r->u.loc->index == e->locIdx ) { // locate the ref note associated with e for(rn=r->refNoteL; rn!=nullptr; rn=rn->link ) @@ -337,13 +455,15 @@ namespace cw break; } } + + // rn SHOULD NEVER BE null BUT THIS TEST FAILS FOR PRE-PERF NOTES: SEE:**** + - // the reference must be found for the event - assert( rn != nullptr ); + //assert( rn != nullptr ); return rn; } - rc_t _setup_perf_note( ssf_t* p, ssf_ref_t* ref, ssf_ref_note_t* rn, ssf_ref_note_t* dupl_rn, const ssf_note_on_t* msg ) + rc_t _setup_perf_note( ssf_t* p, ssf_ref_t* ref, ssf_ref_note_t* rn, ssf_ref_note_t* dupl_rn, const ssf_note_on_t* msg, ssf_perf_note_t*& pnRef ) { rc_t rc = kOkRC; @@ -355,13 +475,22 @@ namespace cw { ssf_perf_note_t* pn = p->perfA + p->perfN; + if( ref->tid==kPreTId) + rn = ref->refNoteL; + pn->left = ref->left; pn->top = ref->col_bottom; pn->refNote = rn; pn->msg = msg; + pn->seqId = p->perfN; ref->col_bottom += BOX_H + BORD_Y; p->perfN += 1; + + if( rn != nullptr ) + rn->cnt += 1; + + pnRef = pn; } return rc; @@ -372,36 +501,68 @@ namespace cw rc_t rc = kOkRC; p->perfAllocN = midiN*2; - p->perfA = mem::allocZ(p->perfAllocN); - p->perfN = 0; + p->perfA = mem::allocZ(p->perfAllocN); + p->perfN = 0; - ssf_ref_t* r0 = nullptr; + ssf_ref_t* r0 = p->refA + PRE_REF_IDX; // for each performed MIDI note for(unsigned muid=0; muidres[] result array for(unsigned j=0; jmatcher->ri; ++j) { + + // if this matcher result record matches the perf'd MIDI note ... if( p->matcher->res[j].muid == muid ) { - assert( p->matcher->res[j].scEvtIdx != kInvalidIdx ); + // this result record matches this perf'd note - but it was not matched + if( p->matcher->res[j].scEvtIdx == kInvalidIdx ) + continue; - // locate the score evt assoc'd with this 'match' record + + // **** PRE-PERF NOTES SHOULD NEVER HAVE A MATCH - BUT FOR SOME + // REASON THE scEvtIdx on the PRE is 0 instead of kInvalidIdx + // AND THEREFORE WE CAN END UP HERE EVEN THOUGH PRE PERF + // RECORDS - BY DEFINITION - CANNOT HAVE A VALID MATCH + + // ... locate the score evt assoc'd with this 'match' record if((e = cmScoreEvt( p->cmScH, p->matcher->res[j].scEvtIdx )) != nullptr ) { - rn = _find_ref_note( p, e ); + if((rn = _find_ref_note( p, e )) == nullptr ) + break; + assert( rn != nullptr ); - - if((rc = _setup_perf_note(p,rn->ref,rn,rn0,midiA+muid)) != kOkRC ) + + ssf_perf_note_t* pn; + + if((rc = _setup_perf_note(p,rn->ref,rn,rn0,midiA+muid,pn)) != kOkRC ) goto errLabel; rn0 = rn; r0 = rn->ref; + + // maintain a list of pointers to duplicate performance notes + if( pn0 == nullptr ) + pn0 = pn; + else + { + pn->dupl_link = pn0; + pn0->dupl_list = pn; + } + + if( e->locIdx < p->minMatchLoc ) + p->minMatchLoc = e->locIdx; + + if( e->locIdx > p->maxMatchLoc ) + p->maxMatchLoc = e->locIdx; + } else { @@ -413,11 +574,13 @@ namespace cw // if this perf note does not have any matches if( rn == nullptr ) - if((rc = _setup_perf_note(p,r0,nullptr,nullptr,midiA+muid)) != kOkRC ) - goto errLabel; - + { + assert( r0 != nullptr ); + ssf_perf_note_t* dummy = nullptr; + if((rc = _setup_perf_note(p,r0,nullptr,nullptr,midiA+muid,dummy)) != kOkRC ) + goto errLabel; + } } - errLabel: if( rc != kOkRC ) @@ -429,10 +592,13 @@ namespace cw void _create_css( ssf_t* p ) { - install_css(p->svgH,".bar","fill",0xc0c0c0,"rgb"); + install_css(p->svgH,".pre","fill",0xb0b0b0,"rgb"); + install_css(p->svgH,".bar","fill",0xa0a0a0,"rgb"); install_css(p->svgH,".loc","fill",0xe0e0e0,"rgb"); - install_css(p->svgH,".ref_note","fill",0x20B2AA,"rgb"); - install_css(p->svgH,".perf_note","fill",0xE0FFFF,"rgb"); + install_css(p->svgH,".ref_note","fill",0x40E0D0,"rgb"); + install_css(p->svgH,".perf_note","fill",0xE0FFFF,"rgb"); + install_css(p->svgH,".no_match","stroke",0xFF0000,"rgb"); + install_css(p->svgH,".multi_match","stroke",0x0000FF,"rgb"); } ssf_t* _create( cmScH_t cmScH, cmScMatcher* matcher, const ssf_note_on_t* midiA, unsigned midiN ) @@ -461,6 +627,7 @@ namespace cw // create the reference notes _create_ref_note_array( p ); + // create the performance array _create_perf_array(p,midiA,midiN ); errLabel: @@ -476,10 +643,11 @@ namespace cw } cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH, - cmScMatcher* matcher, + cmScMatcher* matcher, ssf_note_on_t* midiA, unsigned midiN, - const char* out_fname ) + const char* out_fname, + bool show_muid_fl) { rc_t rc = kOkRC; ssf_t* p; @@ -490,7 +658,7 @@ cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH, goto errLabel; } - if((rc = _write_ref(p)) != kOkRC ) + if((rc = _write(p,show_muid_fl)) != kOkRC ) { rc = cwLogError(rc,"Error writing SSF reference row."); goto errLabel; diff --git a/cwSvgScoreFollow.h b/cwSvgScoreFollow.h index ef6435c..462717e 100644 --- a/cwSvgScoreFollow.h +++ b/cwSvgScoreFollow.h @@ -5,6 +5,7 @@ namespace cw typedef struct ssf_note_on_str { double sec; + unsigned muid; uint8_t pitch; uint8_t vel; uint8_t pad[2]; @@ -14,7 +15,8 @@ namespace cw cmScMatcher* matcher, ssf_note_on_t* perfA, unsigned perfN, - const char* out_fname ); + const char* out_fname, + bool show_muid_fl); } }