cwScoreFollower.h/cpp, cwSvgScoreFollow.h/cpp : Complete working version of score follower and SVG result reendering.

This commit is contained in:
kevin 2023-05-16 09:14:20 -04:00
parent 8cb760af14
commit ed4bf8709d
4 changed files with 481 additions and 190 deletions

View File

@ -36,14 +36,12 @@ namespace cw
{ {
namespace score_follower namespace score_follower
{ {
typedef struct score_follower_str typedef struct score_follower_str
{ {
double srate; double srate;
unsigned search_area_locN; unsigned search_area_locN;
unsigned key_wnd_locN; unsigned key_wnd_locN;
char* score_csv_fname; char* cm_score_csv_fname;
cmCtx_t* cmCtx; cmCtx_t* cmCtx;
cmScH_t cmScoreH; cmScH_t cmScoreH;
cmScMatcher* matcher; cmScMatcher* matcher;
@ -73,9 +71,9 @@ namespace cw
{ {
rc_t rc = kOkRC; 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, "search_area_locN", p->search_area_locN,
"key_wnd_locN", p->key_wnd_locN )) != kOkRC ) "key_wnd_locN", p->key_wnd_locN )) != kOkRC )
{ {
@ -83,7 +81,7 @@ namespace cw
goto errLabel; 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."); rc = cwLogError(kOpFailRC,"Score follower score file expansion failed.");
goto errLabel; goto errLabel;
@ -130,7 +128,8 @@ namespace cw
if((rc = _get_max_cw_loc(p,p->cwLocToCmLocN)) != kOkRC ) if((rc = _get_max_cw_loc(p,p->cwLocToCmLocN)) != kOkRC )
goto errLabel; 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<unsigned>( p->cwLocToCmLocN ); p->cwLocToCmLocA = mem::allocZ<unsigned>( p->cwLocToCmLocN );
cmEvtN = cmScoreEvtCount( p->cmScoreH ); cmEvtN = cmScoreEvtCount( p->cmScoreH );
@ -187,8 +186,9 @@ namespace cw
{ {
mem::release(p->cmLocToCwLocA); mem::release(p->cmLocToCwLocA);
mem::release(p->cwLocToCmLocA); mem::release(p->cwLocToCmLocA);
mem::release(p->score_csv_fname); mem::release(p->cm_score_csv_fname);
mem::release(p->perfA); mem::release(p->perfA);
mem::release(p->match_idA);
cmScMatcherFree(&p->matcher); cmScMatcherFree(&p->matcher);
cmScoreFinalize(&p->cmScoreH); cmScoreFinalize(&p->cmScoreH);
mem::release(p); 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; goto errLabel;
// create the the score follower reference score // 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; goto errLabel;
} }
@ -348,6 +348,7 @@ cw::rc_t cw::score_follower::reset( handle_t h, unsigned cwLocId )
} }
p->perf_idx = 0; p->perf_idx = 0;
clear_match_id_array(h);
} }
errLabel: 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; ssf_note_on_t* pno = p->perfA + p->perf_idx;
pno->sec = sec; pno->sec = sec;
pno->muid = muid;
pno->pitch = d0; pno->pitch = d0;
pno->vel = d1; pno->vel = d1;
p->perf_idx += 1; p->perf_idx += 1;
if( p->perf_idx >= p->perfN ) if( p->perf_idx >= p->perfN )
cwLogWarning("The cw score follower performance cache is full."); cwLogWarning("The cw score follower performance cache is full.");
} }
return rc; return rc;
@ -421,17 +422,17 @@ void cw::score_follower::clear_match_id_array( handle_t h )
p->match_id_curN = 0; 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); 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 ) void cw::score_follower::score_report( handle_t h, const char* out_fname )
{ {
score_follower_t* p = _handleToPtr(h); 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 ) 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 typedef struct test_str
{ {
char* midi_fname; double srate;
char* out_dir; char* out_dir;
double srate; const object_t* cfg;
const object_t* cfg; const char* out_svg_fname;
midi::file::handle_t mfH; 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; } test_t;
rc_t _test_destroy( test_t* p ) rc_t _test_destroy( test_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
mem::release(p->midi_fname);
mem::release(p->out_dir); mem::release(p->out_dir);
midi::file::close(p->mfH);
return rc; return rc;
} }
rc_t _test_parse_cfg( test_t* p, const object_t* cfg ) rc_t _test_parse_cfg( test_t* p, const object_t* cfg )
{ {
rc_t rc; rc_t rc = kOkRC;
const char* midi_fname = nullptr; const char* out_dir = nullptr;
const char* out_dir = nullptr;
// read the test cfg. // read the test cfg.
if((rc = cfg->getv("midi_fname", midi_fname, if((rc = cfg->getv("perfL", p->perfL,
"srate", p->srate, "srate", p->srate,
"cfg", p->cfg, "pre_test_fl", p->pre_test_fl,
"out_dir", out_dir )) != kOkRC ) "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."); rc = cwLogError(rc,"Score follower test cfg. parse failed.");
goto errLabel; 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 // expand the output directory
if((p->out_dir = filesys::expandPath( out_dir)) == nullptr ) if((p->out_dir = filesys::expandPath( out_dir)) == nullptr )
{ {
@ -526,14 +525,164 @@ namespace cw {
goto errLabel; goto errLabel;
} }
} }
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(kSyntaxErrorRC,"Error accessing the cfg record for perf. record index:%i.",perf_idx);
rc = cwLogError(rc,"Score follower test parse cfg. failed."); 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; i<msgN; ++i)
{
const midi::file::trackMsg_t* m = msgA[i];
if( midi::file::isNoteOn( m ) )
{
double sec = (double)m->amicro/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; i<matchIdN; ++i)
cwLogInfo("Match:%i",matchIdA[i]);
clear_match_id_array( sfH );
}
}
}
// create the SVG output filename
if((fname = filesys::makeFn(out_dir,p->out_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; return rc;
} }
@ -549,9 +698,8 @@ cw::rc_t cw::score_follower::test( const object_t* cfg )
cm::handle_t cmCtxH; cm::handle_t cmCtxH;
handle_t sfH; handle_t sfH;
unsigned msgN = 0; char* fname = nullptr;
const midi::file::trackMsg_t** msgA = nullptr;
// parse the test cfg // parse the test cfg
if((rc = _test_parse_cfg( &t, cfg )) != kOkRC ) if((rc = _test_parse_cfg( &t, cfg )) != kOkRC )
goto errLabel; 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 ) if((rc = create( sfH, t.cfg, cmCtxH, t.srate )) != kOkRC )
goto errLabel; goto errLabel;
if((rc = reset( sfH, 0)) != kOkRC ) // create the cm score report filename
goto errLabel; if((fname = filesys::makeFn(t.out_dir,t.out_cm_score_rpt_fname,nullptr,nullptr)) == nullptr )
// open the midi file
if((rc = midi::file::open( t.mfH, t.midi_fname )) != kOkRC )
{ {
rc = cwLogError(rc,"MIDI file open failed on '%s'.",cwStringNullGuard(t.midi_fname)); cwLogError(kOpFailRC,"The output cm score filename formation failed.");
goto errLabel; 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; i<msgN; ++i)
{
const midi::file::trackMsg_t* m = msgA[i];
if( midi::file::isNoteOn( m ) )
{
double sec = (double)m->amicro/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 ) // write the cm score report
{ score_report(sfH,fname);
rc = cwLogError(rc,"score follower exec failed.");
goto errLabel;
}
if( newMatchFl ) // score follow each performance
{ for(unsigned perf_idx=0; perf_idx<t.perfL->child_count(); ++perf_idx)
unsigned matchIdN = 0; _score_follow_one_perf(&t,sfH,perf_idx);
const unsigned* matchIdA = current_match_id_array(sfH, matchIdN );
for(unsigned i=0; i<matchIdN; ++i)
cwLogInfo("Match:%i",matchIdA[i]);
clear_match_id_array( sfH );
}
}
}
errLabel: errLabel:
mem::release(fname);
destroy(sfH); destroy(sfH);
cm::destroy(cmCtxH); cm::destroy(cmCtxH);
_test_destroy(&t); _test_destroy(&t);

View File

@ -12,6 +12,7 @@ namespace cw
rc_t destroy( handle_t& hRef ); 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 ); 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[] // 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 ); 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(). // 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'. // Write the score to 'out_fname'.
void score_report( handle_t h, const char* out_fname ); void score_report( handle_t h, const char* out_fname );

View File

@ -38,63 +38,71 @@
#define ROW_0_Y 0 #define ROW_0_Y 0
#define PRE_REF_IDX 0
namespace cw namespace cw
{ {
namespace score_follower namespace score_follower
{ {
struct ssf_ref_note_str; 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 typedef struct ssf_ref_str
{ {
bool locFl; // true = u.loc false=u.id unsigned tid; // See k??TId
unsigned left; unsigned left;
unsigned top; unsigned top;
unsigned col_bottom; unsigned col_bottom;
struct ssf_ref_note_str* refNoteL; // ref_note's linked to this ref struct ssf_ref_note_str* refNoteL; // ref_note's linked to this ref
union { union {
cmScoreLoc_t* loc; cmScoreLoc_t* loc; // cmScore location
unsigned id; // bar/section id unsigned id; // bar/section id
} u; } u;
} ssf_ref_t; } ssf_ref_t;
// ref. score note that performed notes should match to
typedef struct ssf_ref_note_str typedef struct ssf_ref_note_str
{ {
ssf_ref_t* ref; ssf_ref_t* ref; // loc UI component that this ref. note belongs to
cmScoreEvt_t* evt; cmScoreEvt_t* evt; // cmScore event assoc'd with this note
unsigned top; unsigned top;
unsigned cnt; // count of perf notes that matched to this ref-note
unsigned cwLocId;
struct ssf_ref_note_str* link; struct ssf_ref_note_str* link;
} ssf_ref_note_t; } ssf_ref_note_t;
typedef struct ssf_perf_note_str typedef struct ssf_perf_note_str
{ {
unsigned left; unsigned left;
unsigned top; unsigned top;
ssf_ref_note_t* refNote; ssf_ref_note_t* refNote; // ref. note that this perf. note matches to
const ssf_note_on_t* msg; 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; struct ssf_perf_note_str* dupl_link;
} ssf_perf_note_t; } 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 typedef struct ssf_str
{ {
svg::handle_t svgH; svg::handle_t svgH;
cmScH_t cmScH; cmScH_t cmScH;
cmScMatcher* matcher; cmScMatcher* matcher;
ssf_ref_t* refA; ssf_ref_t* refA;
unsigned refAllocN; unsigned refAllocN;
unsigned refN; unsigned refN;
@ -106,13 +114,27 @@ namespace cw
ssf_perf_note_t* perfA; ssf_perf_note_t* perfA;
unsigned perfAllocN; unsigned perfAllocN;
unsigned perfN; unsigned perfN;
unsigned minMatchLoc;
unsigned maxMatchLoc;
} ssf_t; } 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; 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 ) if((rc = svg::rect(p->svgH,left,top,BOX_W,BOX_H,"class",classLabel,nullptr)) != kOkRC )
{ {
rc = cwLogError(rc,"Error writing SVG rect."); rc = cwLogError(rc,"Error writing SVG rect.");
@ -124,6 +146,18 @@ namespace cw
rc = cwLogError(rc,"Error writing SVG text."); rc = cwLogError(rc,"Error writing SVG text.");
goto errLabel; 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: errLabel:
return rc; return rc;
@ -137,46 +171,68 @@ namespace cw
return _write_rect(p,left,top,label,classLabel); 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;
const unsigned labelCharN = 63; char label[labelCharN+1];
char label[labelCharN+1]; const char* classLabel2 = noMatchFl ? "no_match" : nullptr;
snprintf(label,labelCharN,"%s %5i",midi::midiToSciPitch((uint8_t)midi_pitch),id);
return _write_rect(p,left,top,label,classLabel); 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 ) rc_t _write( ssf_t* p, bool show_muid_fl )
{
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 rc = kOkRC; rc_t rc = kOkRC;
bool fl = false;
unsigned offset_x = 0;
for(unsigned i=0; i<p->refN; ++i) for(unsigned i=0; i<p->refN; ++i)
{ {
ssf_ref_t* r = p->refA + i; ssf_ref_t* r = p->refA + i;
const char* classLabel = r->locFl ? "loc" : "bar"; const char* classLabel = r->tid==kLocTId ? "loc" : (r->tid==kIdTId ? "bar" : "pre");
unsigned id = r->locFl ? r->u.loc->index : r->u.id; 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 // 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); rc = cwLogError(rc,"Error writing 'loc' rect id:%i.",r->u.loc->index);
goto errLabel; goto errLabel;
} }
// write the ref-note // 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."); rc = cwLogError(rc,"Error writing 'ref_note' rect.");
goto errLabel; goto errLabel;
@ -184,15 +240,45 @@ namespace cw
} }
} }
// write the pref-notes
for(unsigned i=0; i<p->perfN; ++i) for(unsigned i=0; i<p->perfN; ++i)
{ {
ssf_perf_note_t* pn = p->perfA + 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."); rc = cwLogError(rc,"Error writing 'pref_note' rect.");
goto errLabel; goto errLabel;
}
}
}
for(unsigned i=0; i<p->perfN; ++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: errLabel:
@ -210,6 +296,7 @@ namespace cw
{ {
mem::release(p->refA); mem::release(p->refA);
mem::release(p->refNoteA); mem::release(p->refNoteA);
mem::release(p->perfA);
mem::release(p); mem::release(p);
} }
@ -220,11 +307,10 @@ namespace cw
{ {
ssf_ref_t* ref = nullptr; ssf_ref_t* ref = nullptr;
if( p->refN < p->refAllocN ) if( p->refN < p->refAllocN )
{ {
ref = p->refA + p->refN;
ref = p->refA + p->refN; ref->left = x_coord_ref;
ref->left = x_coord_ref; ref->top = y_coord;
ref->top = y_coord;
ref->col_bottom = y_coord + BOX_H + BORD_Y; ref->col_bottom = y_coord + BOX_H + BORD_Y;
p->refN += 1; p->refN += 1;
@ -238,12 +324,21 @@ namespace cw
return ref; 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 ) void _create_bar_ref( ssf_t* p, const cmScoreEvt_t* e, unsigned& x_coord_ref, unsigned y_coord )
{ {
ssf_ref_t* ref; ssf_ref_t* ref;
if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr ) if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr )
{ {
ref->locFl = false; ref->tid = kIdTId;
ref->u.id = e->barNumb; ref->u.id = e->barNumb;
} }
} }
@ -253,7 +348,7 @@ namespace cw
ssf_ref_t* ref; ssf_ref_t* ref;
if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr ) 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 ); ref->u.loc = cmScoreLoc(p->cmScH, e->locIdx );
cwAssert( ref->u.loc != nullptr and ref->u.loc->index == 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 cmEvtN = cmScoreEvtCount(p->cmScH);
unsigned loc_idx = kInvalidIdx; unsigned loc_idx = kInvalidIdx;
p->refAllocN = cmEvtN; p->refAllocN = cmEvtN + 1;
p->refA = mem::allocZ<ssf_ref_t>(p->refAllocN); p->refA = mem::allocZ<ssf_ref_t>(p->refAllocN);
p->refN = 0; p->refN = 0;
_create_pre_ref(p, x_coord_ref, y_coord );
for(unsigned i=0; i<cmEvtN; ++i) for(unsigned i=0; i<cmEvtN; ++i)
{ {
@ -285,37 +382,58 @@ namespace cw
_create_loc_ref(p,e,x_coord_ref,y_coord); _create_loc_ref(p,e,x_coord_ref,y_coord);
loc_idx = e->locIdx; loc_idx = e->locIdx;
} }
} }
// 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 _create_ref_note_array( ssf_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
p->refNoteAllocN = p->refAllocN; p->refNoteAllocN = p->refAllocN + 1;
p->refNoteA = mem::allocZ<ssf_ref_note_t>(p->refNoteAllocN); p->refNoteA = mem::allocZ<ssf_ref_note_t>(p->refNoteAllocN);
p->refNoteN = 0; p->refNoteN = 0;
for(unsigned i=0; i<p->refN; ++i) // attach the 'pre' ref note
if( p->refA[i].locFl ) _attach_ref_note(p,PRE_REF_IDX,nullptr);
for(unsigned j=0; j<p->refA[i].u.loc->evtCnt; ++j)
if( p->refA[i].u.loc->evtArray[j]->type == kNonEvtScId && p->refNoteN < p->refNoteAllocN ) for(unsigned ri=PRE_REF_IDX+1; ri<p->refN; ++ri)
if( p->refA[ri].tid == kLocTId )
for(unsigned j=0; j<p->refA[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; if((rc = _attach_ref_note(p,ri,p->refA[ri].u.loc->evtArray[j])) != kOkRC )
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.");
goto errLabel; goto errLabel;
}
} }
errLabel: errLabel:
@ -329,7 +447,7 @@ namespace cw
{ {
ssf_ref_t* r = p->refA + i; ssf_ref_t* r = p->refA + i;
// if this is the ref record for the target location // 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 // locate the ref note associated with e
for(rn=r->refNoteL; rn!=nullptr; rn=rn->link ) for(rn=r->refNoteL; rn!=nullptr; rn=rn->link )
@ -337,13 +455,15 @@ namespace cw
break; 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; 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; rc_t rc = kOkRC;
@ -355,13 +475,22 @@ namespace cw
{ {
ssf_perf_note_t* pn = p->perfA + p->perfN; ssf_perf_note_t* pn = p->perfA + p->perfN;
if( ref->tid==kPreTId)
rn = ref->refNoteL;
pn->left = ref->left; pn->left = ref->left;
pn->top = ref->col_bottom; pn->top = ref->col_bottom;
pn->refNote = rn; pn->refNote = rn;
pn->msg = msg; pn->msg = msg;
pn->seqId = p->perfN;
ref->col_bottom += BOX_H + BORD_Y; ref->col_bottom += BOX_H + BORD_Y;
p->perfN += 1; p->perfN += 1;
if( rn != nullptr )
rn->cnt += 1;
pnRef = pn;
} }
return rc; return rc;
@ -372,36 +501,68 @@ namespace cw
rc_t rc = kOkRC; rc_t rc = kOkRC;
p->perfAllocN = midiN*2; p->perfAllocN = midiN*2;
p->perfA = mem::allocZ<ssf_perf_note_t>(p->perfAllocN); p->perfA = mem::allocZ<ssf_perf_note_t>(p->perfAllocN);
p->perfN = 0; p->perfN = 0;
ssf_ref_t* r0 = nullptr; ssf_ref_t* r0 = p->refA + PRE_REF_IDX;
// for each performed MIDI note // for each performed MIDI note
for(unsigned muid=0; muid<midiN; ++muid) for(unsigned muid=0; muid<midiN; ++muid)
{ {
cmScoreEvt_t* e = nullptr; cmScoreEvt_t* e = nullptr;
ssf_ref_note_t* rn = nullptr; ssf_ref_note_t* rn = nullptr;
ssf_ref_note_t* rn0 = nullptr; ssf_ref_note_t* rn0 = nullptr;
ssf_perf_note_t* pn0 = nullptr;
// look for a 'match' record that refers to this perf note // look for a 'match' record that refers to this perf note
// by interating through the matcher->res[] result array
for(unsigned j=0; j<p->matcher->ri; ++j) for(unsigned j=0; j<p->matcher->ri; ++j)
{ {
// if this matcher result record matches the perf'd MIDI note ...
if( p->matcher->res[j].muid == muid ) 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 ) 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 ); 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; goto errLabel;
rn0 = rn; rn0 = rn;
r0 = rn->ref; 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 else
{ {
@ -413,11 +574,13 @@ namespace cw
// if this perf note does not have any matches // if this perf note does not have any matches
if( rn == nullptr ) 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: errLabel:
if( rc != kOkRC ) if( rc != kOkRC )
@ -429,10 +592,13 @@ namespace cw
void _create_css( ssf_t* p ) 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,".loc","fill",0xe0e0e0,"rgb");
install_css(p->svgH,".ref_note","fill",0x20B2AA,"rgb"); install_css(p->svgH,".ref_note","fill",0x40E0D0,"rgb");
install_css(p->svgH,".perf_note","fill",0xE0FFFF,"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 ) 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 the reference notes
_create_ref_note_array( p ); _create_ref_note_array( p );
// create the performance array
_create_perf_array(p,midiA,midiN ); _create_perf_array(p,midiA,midiN );
errLabel: errLabel:
@ -476,10 +643,11 @@ namespace cw
} }
cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH, cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH,
cmScMatcher* matcher, cmScMatcher* matcher,
ssf_note_on_t* midiA, ssf_note_on_t* midiA,
unsigned midiN, unsigned midiN,
const char* out_fname ) const char* out_fname,
bool show_muid_fl)
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
ssf_t* p; ssf_t* p;
@ -490,7 +658,7 @@ cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH,
goto errLabel; goto errLabel;
} }
if((rc = _write_ref(p)) != kOkRC ) if((rc = _write(p,show_muid_fl)) != kOkRC )
{ {
rc = cwLogError(rc,"Error writing SSF reference row."); rc = cwLogError(rc,"Error writing SSF reference row.");
goto errLabel; goto errLabel;

View File

@ -5,6 +5,7 @@ namespace cw
typedef struct ssf_note_on_str typedef struct ssf_note_on_str
{ {
double sec; double sec;
unsigned muid;
uint8_t pitch; uint8_t pitch;
uint8_t vel; uint8_t vel;
uint8_t pad[2]; uint8_t pad[2];
@ -14,7 +15,8 @@ namespace cw
cmScMatcher* matcher, cmScMatcher* matcher,
ssf_note_on_t* perfA, ssf_note_on_t* perfA,
unsigned perfN, unsigned perfN,
const char* out_fname ); const char* out_fname,
bool show_muid_fl);
} }
} }