2024-12-01 19:35:24 +00:00
|
|
|
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
2023-05-09 12:22:00 +00:00
|
|
|
#include "cwCommon.h"
|
|
|
|
#include "cwLog.h"
|
|
|
|
#include "cwCommonImpl.h"
|
2024-05-29 16:36:57 +00:00
|
|
|
#include "cwTest.h"
|
2023-05-09 12:22:00 +00:00
|
|
|
#include "cwMem.h"
|
|
|
|
#include "cwObject.h"
|
|
|
|
#include "cwText.h"
|
|
|
|
#include "cwTime.h"
|
|
|
|
#include "cwMidi.h"
|
2023-11-26 20:35:51 +00:00
|
|
|
#include "cwMidiFile.h"
|
2023-05-09 12:22:00 +00:00
|
|
|
#include "cwSvg.h"
|
2023-09-12 21:43:15 +00:00
|
|
|
#include "cwDynRefTbl.h"
|
|
|
|
#include "cwScoreParse.h"
|
|
|
|
#include "cwSfScore.h"
|
|
|
|
#include "cwSfMatch.h"
|
|
|
|
#include "cwSfTrack.h"
|
2024-02-08 16:20:09 +00:00
|
|
|
#include "cwPerfMeas.h"
|
|
|
|
#include "cwPianoScore.h"
|
2023-05-09 12:22:00 +00:00
|
|
|
|
2023-05-25 19:57:20 +00:00
|
|
|
#include "cwScoreFollowerPerf.h"
|
2023-05-09 12:22:00 +00:00
|
|
|
#include "cwScoreFollower.h"
|
|
|
|
#include "cwSvgScoreFollow.h"
|
|
|
|
|
|
|
|
#define BOX_W 50
|
|
|
|
#define BOX_H 50
|
|
|
|
#define BORD_X 2
|
|
|
|
#define BORD_Y 2
|
|
|
|
|
|
|
|
#define ROW_0_Y 0
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
#define PRE_REF_IDX 0
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
namespace cw
|
|
|
|
{
|
|
|
|
namespace score_follower
|
|
|
|
{
|
|
|
|
struct ssf_ref_note_str;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
enum {
|
|
|
|
kLocTId,
|
|
|
|
kIdTId,
|
|
|
|
kPreTId
|
|
|
|
};
|
|
|
|
|
|
|
|
// bar/section or location anchor
|
|
|
|
// (one for each top row of UI elements)
|
2023-05-09 12:22:00 +00:00
|
|
|
typedef struct ssf_ref_str
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
unsigned tid; // See k??TId
|
|
|
|
unsigned left;
|
|
|
|
unsigned top;
|
|
|
|
unsigned col_bottom;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
struct ssf_ref_note_str* refNoteL; // ref_note's linked to this ref
|
|
|
|
|
|
|
|
union {
|
2023-09-12 21:43:15 +00:00
|
|
|
const sfscore::loc_t* loc; // score location
|
|
|
|
unsigned id; // bar/section id
|
2023-05-09 12:22:00 +00:00
|
|
|
} u;
|
|
|
|
} ssf_ref_t;
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
// ref. score note that performed notes should match to
|
2023-05-09 12:22:00 +00:00
|
|
|
typedef struct ssf_ref_note_str
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
ssf_ref_t* ref; // loc UI component that this ref. note belongs to
|
2023-09-12 21:43:15 +00:00
|
|
|
const sfscore::event_t* evt; // cmScore event assoc'd with this note
|
2023-05-16 13:14:20 +00:00
|
|
|
unsigned top;
|
|
|
|
unsigned cnt; // count of perf notes that matched to this ref-note
|
|
|
|
unsigned cwLocId;
|
2023-05-09 12:22:00 +00:00
|
|
|
struct ssf_ref_note_str* link;
|
|
|
|
} ssf_ref_note_t;
|
|
|
|
|
|
|
|
typedef struct ssf_perf_note_str
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
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.
|
2023-05-09 12:22:00 +00:00
|
|
|
struct ssf_perf_note_str* dupl_link;
|
|
|
|
|
|
|
|
} ssf_perf_note_t;
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct ssf_str
|
|
|
|
{
|
2023-09-12 21:43:15 +00:00
|
|
|
svg::handle_t svgH;
|
|
|
|
sfscore::handle_t scoreH;
|
|
|
|
sftrack::handle_t trackH;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
ssf_ref_t* refA;
|
|
|
|
unsigned refAllocN;
|
|
|
|
unsigned refN;
|
|
|
|
|
|
|
|
ssf_ref_note_t* refNoteA;
|
|
|
|
unsigned refNoteAllocN;
|
|
|
|
unsigned refNoteN;
|
|
|
|
|
|
|
|
ssf_perf_note_t* perfA;
|
|
|
|
unsigned perfAllocN;
|
|
|
|
unsigned perfN;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
unsigned minMatchLoc;
|
|
|
|
unsigned maxMatchLoc;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
} ssf_t;
|
|
|
|
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
rc_t _write_rect( ssf_t* p, unsigned left, unsigned top, const char* label, const char* classLabel, const char* classLabel2=nullptr, unsigned cwLocId=kInvalidId )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-05-09 12:22:00 +00:00
|
|
|
if((rc = svg::rect(p->svgH,left,top,BOX_W,BOX_H,"class",classLabel,nullptr)) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Error writing SVG rect.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((rc = svg::text(p->svgH,left,top+20,label)) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Error writing SVG text.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
errLabel:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc_t _write_rect_id( ssf_t* p, unsigned left, unsigned top, const char* classLabel, unsigned id )
|
|
|
|
{
|
|
|
|
const unsigned labelCharN = 31;
|
|
|
|
char label[labelCharN+1];
|
|
|
|
snprintf(label,labelCharN,"%5i",id);
|
|
|
|
return _write_rect(p,left,top,label,classLabel);
|
|
|
|
}
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
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)
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
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);
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
rc_t _write( ssf_t* p, bool show_muid_fl )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
bool fl = false;
|
|
|
|
unsigned offset_x = 0;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
for(unsigned i=0; i<p->refN; ++i)
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
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;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
// write ref rect
|
2023-05-16 13:14:20 +00:00
|
|
|
if((rc = _write_rect_id( p, r->left-offset_x, r->top, classLabel, id )) != kOkRC )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Error writing 'loc' rect id:%i.",r->u.loc->index);
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write the ref-note
|
2023-05-16 13:14:20 +00:00
|
|
|
for(ssf_ref_note_t* rn=r->refNoteL; rn!=nullptr; rn = rn->link)
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
2024-03-25 14:50:11 +00:00
|
|
|
unsigned pitch = rn->evt == nullptr ? (unsigned)midi::kInvalidMidiPitch : rn->evt->pitch;
|
2023-05-16 13:14:20 +00:00
|
|
|
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 )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Error writing 'ref_note' rect.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
// write the pref-notes
|
2023-05-09 12:22:00 +00:00
|
|
|
for(unsigned i=0; i<p->perfN; ++i)
|
|
|
|
{
|
|
|
|
ssf_perf_note_t* pn = p->perfA + i;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
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 )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
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 )
|
|
|
|
{
|
2023-05-09 12:22:00 +00:00
|
|
|
rc = cwLogError(rc,"Error writing 'pref_note' rect.");
|
|
|
|
goto errLabel;
|
2023-05-16 13:14:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rc_t _destroy( ssf_t* p )
|
|
|
|
{
|
|
|
|
rc_t rc;
|
|
|
|
|
|
|
|
if((rc = svg::destroy(p->svgH)) != kOkRC )
|
|
|
|
rc = cwLogError(rc,"SVG file object destroy failed.");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mem::release(p->refA);
|
|
|
|
mem::release(p->refNoteA);
|
2023-05-16 13:14:20 +00:00
|
|
|
mem::release(p->perfA);
|
2023-05-09 12:22:00 +00:00
|
|
|
mem::release(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssf_ref_t* _create_ref( ssf_t* p, unsigned& x_coord_ref, unsigned y_coord )
|
|
|
|
{
|
|
|
|
ssf_ref_t* ref = nullptr;
|
|
|
|
if( p->refN < p->refAllocN )
|
2023-05-16 13:14:20 +00:00
|
|
|
{
|
|
|
|
ref = p->refA + p->refN;
|
|
|
|
ref->left = x_coord_ref;
|
|
|
|
ref->top = y_coord;
|
2023-05-09 12:22:00 +00:00
|
|
|
ref->col_bottom = y_coord + BOX_H + BORD_Y;
|
|
|
|
|
|
|
|
p->refN += 1;
|
|
|
|
|
|
|
|
if( p->refN == p->refAllocN )
|
|
|
|
cwLogWarning("SVG score follower reference array cache is full.");
|
|
|
|
|
|
|
|
x_coord_ref += (BOX_W+BORD_X);
|
|
|
|
|
|
|
|
}
|
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
void _create_bar_ref( ssf_t* p, const sfscore::event_t* e, unsigned& x_coord_ref, unsigned y_coord )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
ssf_ref_t* ref;
|
|
|
|
if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr )
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
ref->tid = kIdTId;
|
2023-05-09 12:22:00 +00:00
|
|
|
ref->u.id = e->barNumb;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
void _create_loc_ref( ssf_t* p, const sfscore::event_t* e, unsigned& x_coord_ref, unsigned y_coord )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
ssf_ref_t* ref;
|
|
|
|
if((ref = _create_ref(p,x_coord_ref,y_coord)) != nullptr )
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
ref->tid = kLocTId;
|
2023-09-12 21:43:15 +00:00
|
|
|
ref->u.loc = sfscore::loc_base(p->scoreH) + e->oLocId;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
cwAssert( ref->u.loc != nullptr and ref->u.loc->index == e->oLocId );
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _create_ref_array( ssf_t* p, unsigned& x_coord_ref, unsigned y_coord )
|
|
|
|
{
|
|
|
|
|
|
|
|
// create the cmScoreLoc array
|
2023-09-12 21:43:15 +00:00
|
|
|
unsigned cmEvtN = event_count(p->scoreH);
|
2023-05-09 12:22:00 +00:00
|
|
|
unsigned loc_idx = kInvalidIdx;
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
p->refAllocN = cmEvtN + 1;
|
2023-05-09 12:22:00 +00:00
|
|
|
p->refA = mem::allocZ<ssf_ref_t>(p->refAllocN);
|
|
|
|
p->refN = 0;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
_create_pre_ref(p, x_coord_ref, y_coord );
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
for(unsigned i=0; i<cmEvtN; ++i)
|
|
|
|
{
|
2023-09-12 21:43:15 +00:00
|
|
|
const sfscore::event_t* e = event(p->scoreH,i);
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
// if this is a bar marker
|
2023-09-12 21:43:15 +00:00
|
|
|
if( e->type == score_parse::kBarTId )
|
2023-05-09 12:22:00 +00:00
|
|
|
_create_bar_ref(p,e,x_coord_ref,y_coord);
|
|
|
|
|
|
|
|
// if this is the start of a new location
|
2023-09-12 21:43:15 +00:00
|
|
|
if( e->oLocId != loc_idx)
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
_create_loc_ref(p,e,x_coord_ref,y_coord);
|
2023-09-12 21:43:15 +00:00
|
|
|
loc_idx = e->oLocId;
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
2023-05-16 13:14:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// reset the min and max loc index
|
|
|
|
p->minMatchLoc = loc_idx == kInvalidIdx ? 0 : loc_idx;
|
|
|
|
p->maxMatchLoc = 0;
|
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
rc_t _attach_ref_note( ssf_t* p, unsigned ref_idx, sfscore::event_t* evt )
|
2023-05-16 13:14:20 +00:00
|
|
|
{
|
|
|
|
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;
|
2023-09-12 21:43:15 +00:00
|
|
|
rn->cwLocId = evt==nullptr ? 0 : evt->oLocId;
|
2023-05-16 13:14:20 +00:00
|
|
|
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;
|
|
|
|
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rc_t _create_ref_note_array( ssf_t* p )
|
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
p->refNoteAllocN = p->refAllocN + 1;
|
2023-05-09 12:22:00 +00:00
|
|
|
p->refNoteA = mem::allocZ<ssf_ref_note_t>(p->refNoteAllocN);
|
|
|
|
p->refNoteN = 0;
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
// attach the 'pre' ref note
|
|
|
|
_attach_ref_note(p,PRE_REF_IDX,nullptr);
|
2023-05-09 12:22:00 +00:00
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
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)
|
2023-09-12 21:43:15 +00:00
|
|
|
if( p->refA[ri].u.loc->evtArray[j]->type == score_parse::kNoteOnTId && p->refNoteN < p->refNoteAllocN )
|
2023-05-16 13:14:20 +00:00
|
|
|
{
|
|
|
|
if((rc = _attach_ref_note(p,ri,p->refA[ri].u.loc->evtArray[j])) != kOkRC )
|
2023-05-09 12:22:00 +00:00
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
ssf_ref_note_t* _find_ref_note( ssf_t* p, const sfscore::event_t* e )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
ssf_ref_note_t* rn = nullptr;
|
|
|
|
for(unsigned i=0; i<p->refN; ++i)
|
|
|
|
{
|
|
|
|
ssf_ref_t* r = p->refA + i;
|
|
|
|
// if this is the ref record for the target location
|
2023-09-12 21:43:15 +00:00
|
|
|
if( r->tid == kLocTId and r->u.loc->index == e->oLocId )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
// locate the ref note associated with e
|
|
|
|
for(rn=r->refNoteL; rn!=nullptr; rn=rn->link )
|
|
|
|
if( rn->evt->index == e->index )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
// rn SHOULD NEVER BE null BUT THIS TEST FAILS FOR PRE-PERF NOTES: SEE:****
|
|
|
|
|
2023-05-09 12:22:00 +00:00
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
//assert( rn != nullptr );
|
2023-05-09 12:22:00 +00:00
|
|
|
return rn;
|
|
|
|
}
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
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 )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
|
|
|
|
if( p->perfN >= p->perfAllocN )
|
|
|
|
{
|
|
|
|
rc = cwLogError(kBufTooSmallRC,"The perf_note array is too small.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ssf_perf_note_t* pn = p->perfA + p->perfN;
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
if( ref->tid==kPreTId)
|
|
|
|
rn = ref->refNoteL;
|
|
|
|
|
2023-05-09 12:22:00 +00:00
|
|
|
pn->left = ref->left;
|
|
|
|
pn->top = ref->col_bottom;
|
|
|
|
pn->refNote = rn;
|
|
|
|
pn->msg = msg;
|
2023-05-16 13:14:20 +00:00
|
|
|
pn->seqId = p->perfN;
|
2023-05-09 12:22:00 +00:00
|
|
|
ref->col_bottom += BOX_H + BORD_Y;
|
|
|
|
|
|
|
|
p->perfN += 1;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
if( rn != nullptr )
|
|
|
|
rn->cnt += 1;
|
|
|
|
|
|
|
|
pnRef = pn;
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc_t _create_perf_array( ssf_t* p, const ssf_note_on_t* midiA, unsigned midiN )
|
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
|
|
|
|
p->perfAllocN = midiN*2;
|
2023-05-16 13:14:20 +00:00
|
|
|
p->perfA = mem::allocZ<ssf_perf_note_t>(p->perfAllocN);
|
|
|
|
p->perfN = 0;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
ssf_ref_t* r0 = p->refA + PRE_REF_IDX;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
// for each performed MIDI note
|
|
|
|
for(unsigned muid=0; muid<midiN; ++muid)
|
|
|
|
{
|
2023-09-12 21:43:15 +00:00
|
|
|
const sfscore::event_t* e = nullptr;
|
|
|
|
ssf_ref_note_t* rn = nullptr;
|
|
|
|
ssf_ref_note_t* rn0 = nullptr;
|
|
|
|
ssf_perf_note_t* pn0 = nullptr;
|
|
|
|
unsigned resultN = result_count(p->trackH);
|
|
|
|
const sftrack::result_t* resultA = result_base(p->trackH);
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
// look for a 'match' record that refers to this perf note
|
2023-05-16 13:14:20 +00:00
|
|
|
// by interating through the matcher->res[] result array
|
2023-09-12 21:43:15 +00:00
|
|
|
for(unsigned j=0; j<resultN; ++j)
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
// if this matcher result record matches the perf'd MIDI note ...
|
2023-09-12 21:43:15 +00:00
|
|
|
if( resultA[j].muid == muid )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
// this result record matches this perf'd note - but it was not matched
|
2023-09-12 21:43:15 +00:00
|
|
|
if( resultA[j].scEvtIdx == kInvalidIdx )
|
2023-05-16 13:14:20 +00:00
|
|
|
continue;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
// **** 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
|
2023-09-12 21:43:15 +00:00
|
|
|
if((e = sfscore::event( p->scoreH, resultA[j].scEvtIdx )) != nullptr )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
if((rn = _find_ref_note( p, e )) == nullptr )
|
|
|
|
break;
|
|
|
|
|
2023-05-09 12:22:00 +00:00
|
|
|
assert( rn != nullptr );
|
2023-05-16 13:14:20 +00:00
|
|
|
|
2023-05-20 02:14:01 +00:00
|
|
|
ssf_perf_note_t* pn = nullptr;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
if((rc = _setup_perf_note(p,rn->ref,rn,rn0,midiA+muid,pn)) != kOkRC )
|
2023-05-09 12:22:00 +00:00
|
|
|
goto errLabel;
|
|
|
|
|
|
|
|
rn0 = rn;
|
|
|
|
r0 = rn->ref;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
|
|
|
// maintain a list of pointers to duplicate performance notes
|
|
|
|
if( pn0 == nullptr )
|
|
|
|
pn0 = pn;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pn->dupl_link = pn0;
|
|
|
|
pn0->dupl_list = pn;
|
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
if( e->oLocId < p->minMatchLoc )
|
|
|
|
p->minMatchLoc = e->oLocId;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
if( e->oLocId > p->maxMatchLoc )
|
|
|
|
p->maxMatchLoc = e->oLocId;
|
2023-05-16 13:14:20 +00:00
|
|
|
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// this should be impossible
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if this perf note does not have any matches
|
|
|
|
if( rn == nullptr )
|
2023-05-16 13:14:20 +00:00
|
|
|
{
|
|
|
|
assert( r0 != nullptr );
|
|
|
|
ssf_perf_note_t* dummy = nullptr;
|
|
|
|
if((rc = _setup_perf_note(p,r0,nullptr,nullptr,midiA+muid,dummy)) != kOkRC )
|
|
|
|
goto errLabel;
|
|
|
|
}
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
if( rc != kOkRC )
|
|
|
|
rc = cwLogError(rc,"create_perf_array failed.");
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _create_css( ssf_t* p )
|
|
|
|
{
|
2023-05-16 13:14:20 +00:00
|
|
|
install_css(p->svgH,".pre","fill",0xb0b0b0,"rgb");
|
|
|
|
install_css(p->svgH,".bar","fill",0xa0a0a0,"rgb");
|
2023-05-09 12:22:00 +00:00
|
|
|
install_css(p->svgH,".loc","fill",0xe0e0e0,"rgb");
|
2023-05-16 13:14:20 +00:00
|
|
|
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");
|
2023-05-09 12:22:00 +00:00
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
ssf_t* _create( sfscore::handle_t scoreH, sftrack::handle_t trackH, const ssf_note_on_t* midiA, unsigned midiN )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc_t rc;
|
|
|
|
ssf_t* p = mem::allocZ<ssf_t>();
|
|
|
|
|
|
|
|
unsigned x_coord = 0;
|
|
|
|
unsigned y_coord = 0;
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
p->scoreH = scoreH;
|
|
|
|
p->trackH = trackH;
|
2023-05-09 12:22:00 +00:00
|
|
|
|
|
|
|
// create the SVG file object
|
|
|
|
if((rc = svg::create(p->svgH)) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"SVG file object create failed.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
_create_css(p);
|
|
|
|
|
|
|
|
// create the refence row (the top row or bar and loc markers)
|
|
|
|
_create_ref_array( p, x_coord, y_coord );
|
|
|
|
|
|
|
|
// create the reference notes
|
|
|
|
_create_ref_note_array( p );
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
// create the performance array
|
2023-05-09 12:22:00 +00:00
|
|
|
_create_perf_array(p,midiA,midiN );
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
if(rc != kOkRC )
|
|
|
|
_destroy(p);
|
|
|
|
|
|
|
|
return p;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
cw::rc_t cw::score_follower::svgScoreFollowWrite( sfscore::handle_t scoreH,
|
|
|
|
sftrack::handle_t trackH,
|
2023-05-09 12:22:00 +00:00
|
|
|
ssf_note_on_t* midiA,
|
|
|
|
unsigned midiN,
|
2023-05-16 13:14:20 +00:00
|
|
|
const char* out_fname,
|
|
|
|
bool show_muid_fl)
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc_t rc = kOkRC;
|
|
|
|
ssf_t* p;
|
|
|
|
|
2023-09-12 21:43:15 +00:00
|
|
|
if((p = _create(scoreH, trackH, midiA, midiN )) == nullptr )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Score follower SVG writer initialization failed.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
2023-05-16 13:14:20 +00:00
|
|
|
if((rc = _write(p,show_muid_fl)) != kOkRC )
|
2023-05-09 12:22:00 +00:00
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Error writing SSF reference row.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((rc = write( p->svgH, out_fname, nullptr, svg::kStandAloneFl )) != kOkRC )
|
|
|
|
{
|
|
|
|
rc = cwLogError(rc,"Error creating SSF output file.");
|
|
|
|
goto errLabel;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
errLabel:
|
|
|
|
_destroy(p);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|