cwScoreFollower.h/cpp, cwSvgScoreFollow.h/cpp : Complete working version of score follower and SVG result reendering.
This commit is contained in:
parent
8cb760af14
commit
ed4bf8709d
@ -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;
|
||||
@ -131,6 +129,7 @@ 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<unsigned>( 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,6 +396,7 @@ 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;
|
||||
@ -402,7 +404,6 @@ cw::rc_t cw::score_follower::exec( handle_t h, double sec, unsigned smpIdx, uns
|
||||
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;
|
||||
char* out_dir;
|
||||
const object_t* cfg;
|
||||
midi::file::handle_t mfH;
|
||||
|
||||
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;
|
||||
rc_t rc = kOkRC;
|
||||
const char* out_dir = nullptr;
|
||||
|
||||
// read the test cfg.
|
||||
if((rc = cfg->getv("midi_fname", midi_fname,
|
||||
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 )) != kOkRC )
|
||||
"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 )
|
||||
{
|
||||
@ -527,13 +526,163 @@ namespace cw {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
if(rc != kOkRC )
|
||||
{
|
||||
_test_destroy(p);
|
||||
rc = cwLogError(rc,"Score follower test parse cfg. failed.");
|
||||
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 )
|
||||
{
|
||||
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; 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;
|
||||
}
|
||||
|
||||
@ -549,8 +698,7 @@ 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 )
|
||||
@ -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;
|
||||
}
|
||||
// write the cm score report
|
||||
score_report(sfH,fname);
|
||||
|
||||
for(unsigned i=0; i<msgN; ++i)
|
||||
{
|
||||
const midi::file::trackMsg_t* m = msgA[i];
|
||||
// score follow each performance
|
||||
for(unsigned perf_idx=0; perf_idx<t.perfL->child_count(); ++perf_idx)
|
||||
_score_follow_one_perf(&t,sfH,perf_idx);
|
||||
|
||||
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 )
|
||||
{
|
||||
rc = cwLogError(rc,"score follower exec failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errLabel:
|
||||
mem::release(fname);
|
||||
destroy(sfH);
|
||||
cm::destroy(cmCtxH);
|
||||
_test_destroy(&t);
|
||||
|
@ -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 );
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
#define ROW_0_Y 0
|
||||
|
||||
#define PRE_REF_IDX 0
|
||||
|
||||
namespace cw
|
||||
{
|
||||
@ -45,9 +46,17 @@ namespace cw
|
||||
{
|
||||
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 tid; // See k??TId
|
||||
unsigned left;
|
||||
unsigned top;
|
||||
unsigned col_bottom;
|
||||
@ -55,16 +64,19 @@ namespace cw
|
||||
struct ssf_ref_note_str* refNoteL; // ref_note's linked to this ref
|
||||
|
||||
union {
|
||||
cmScoreLoc_t* loc;
|
||||
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;
|
||||
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;
|
||||
|
||||
@ -72,29 +84,25 @@ namespace cw
|
||||
{
|
||||
unsigned left;
|
||||
unsigned top;
|
||||
ssf_ref_note_t* refNote;
|
||||
const ssf_note_on_t* msg;
|
||||
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;
|
||||
@ -107,12 +115,26 @@ namespace cw
|
||||
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.");
|
||||
@ -125,6 +147,18 @@ namespace cw
|
||||
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,37 +171,56 @@ 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 char* classLabel2 = noMatchFl ? "no_match" : nullptr;
|
||||
|
||||
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];
|
||||
if( midi_pitch == midi::kInvalidMidiPitch )
|
||||
snprintf(label,labelCharN,"%s","pre");
|
||||
else
|
||||
if( cnt == kInvalidCnt )
|
||||
snprintf(label,labelCharN,"%s",midi::midiToSciPitch((uint8_t)midi_pitch));
|
||||
return _write_rect(p,left,top,label,classLabel);
|
||||
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_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; i<p->refN; ++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;
|
||||
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;
|
||||
@ -176,7 +229,10 @@ namespace cw
|
||||
// write the ref-note
|
||||
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; i<p->perfN; ++i)
|
||||
{
|
||||
ssf_perf_note_t* pn = p->perfA + i;
|
||||
|
||||
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; i<p->perfN; ++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 )
|
||||
if( pn->isVisibleFl)
|
||||
{
|
||||
rc = cwLogError(rc,"Error writing 'pref_note' rect.");
|
||||
goto errLabel;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -221,7 +308,6 @@ 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;
|
||||
@ -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,10 +362,12 @@ namespace cw
|
||||
unsigned cmEvtN = cmScoreEvtCount(p->cmScH);
|
||||
unsigned loc_idx = kInvalidIdx;
|
||||
|
||||
p->refAllocN = cmEvtN;
|
||||
p->refAllocN = cmEvtN + 1;
|
||||
p->refA = mem::allocZ<ssf_ref_t>(p->refAllocN);
|
||||
p->refN = 0;
|
||||
|
||||
_create_pre_ref(p, x_coord_ref, y_coord );
|
||||
|
||||
for(unsigned i=0; i<cmEvtN; ++i)
|
||||
{
|
||||
const cmScoreEvt_t* e = cmScoreEvt(p->cmScH,i);
|
||||
@ -286,28 +383,25 @@ namespace cw
|
||||
loc_idx = e->locIdx;
|
||||
}
|
||||
}
|
||||
|
||||
// reset the min and max loc index
|
||||
p->minMatchLoc = loc_idx == kInvalidIdx ? 0 : loc_idx;
|
||||
p->maxMatchLoc = 0;
|
||||
}
|
||||
|
||||
rc_t _create_ref_note_array( ssf_t* p )
|
||||
rc_t _attach_ref_note( ssf_t* p, unsigned ref_idx, cmScoreEvt_t* evt )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
p->refNoteAllocN = p->refAllocN;
|
||||
p->refNoteA = mem::allocZ<ssf_ref_note_t>(p->refNoteAllocN);
|
||||
p->refNoteN = 0;
|
||||
|
||||
for(unsigned i=0; i<p->refN; ++i)
|
||||
if( p->refA[i].locFl )
|
||||
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 )
|
||||
{
|
||||
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 = 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;
|
||||
@ -316,6 +410,30 @@ namespace cw
|
||||
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 + 1;
|
||||
p->refNoteA = mem::allocZ<ssf_ref_note_t>(p->refNoteAllocN);
|
||||
p->refNoteN = 0;
|
||||
|
||||
// attach the 'pre' ref note
|
||||
_attach_ref_note(p,PRE_REF_IDX,nullptr);
|
||||
|
||||
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 )
|
||||
{
|
||||
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 )
|
||||
@ -338,12 +456,14 @@ namespace cw
|
||||
}
|
||||
}
|
||||
|
||||
// the reference must be found for the event
|
||||
assert( rn != nullptr );
|
||||
// rn SHOULD NEVER BE null BUT THIS TEST FAILS FOR PRE-PERF NOTES: SEE:****
|
||||
|
||||
|
||||
//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;
|
||||
@ -375,7 +504,7 @@ namespace cw
|
||||
p->perfA = mem::allocZ<ssf_perf_note_t>(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; muid<midiN; ++muid)
|
||||
@ -383,25 +512,57 @@ namespace cw
|
||||
cmScoreEvt_t* e = nullptr;
|
||||
ssf_ref_note_t* rn = nullptr;
|
||||
ssf_ref_note_t* rn0 = nullptr;
|
||||
ssf_perf_note_t* pn0 = nullptr;
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
||||
// 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 )
|
||||
{
|
||||
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,".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:
|
||||
@ -479,7 +646,8 @@ cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH,
|
||||
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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user