diff --git a/Makefile.am b/Makefile.am index eb04920..028097d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,8 +67,8 @@ libcwSRC += src/libcw/cwIoMidiRecordPlay.cpp src/libcw/cwIoAudioRecordPlay.cpp libcwHDR += src/libcw/cwIoPresetSelApp.h src/libcw/cwPianoScore.h src/libcw/cwPresetSel.h src/libcw/cwVelTableTuner.h libcwSRC += src/libcw/cwIoPresetSelApp.cpp src/libcw/cwPianoScore.cpp src/libcw/cwPresetSel.cpp src/libcw/cwVelTableTuner.cpp -libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScore.h -libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScore.cpp +libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScoreFollow.h +libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScoreFollow.cpp endif diff --git a/cwSvgScoreFollow.cpp b/cwSvgScoreFollow.cpp new file mode 100644 index 0000000..4b51493 --- /dev/null +++ b/cwSvgScoreFollow.cpp @@ -0,0 +1,515 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwObject.h" +#include "cwText.h" +#include "cwTime.h" +#include "cwMidi.h" +#include "cwPianoScore.h" +#include "cwSvg.h" +#include "cwCmInterface.h" + +#include "cmGlobal.h" +#include "cmFloatTypes.h" +#include "cmRpt.h" +#include "cmErr.h" +#include "cmCtx.h" +#include "cmMem.h" +#include "cmSymTbl.h" +#include "cmLinkedHeap.h" +#include "cmTime.h" +#include "cmMidi.h" +#include "cmSymTbl.h" +#include "cmMidiFile.h" +#include "cmAudioFile.h" +#include "cmScore.h" +#include "cmTimeLine.h" +#include "cmProcObj.h" +#include "cmProc4.h" + +#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 + + +namespace cw +{ + namespace score_follower + { + struct ssf_ref_note_str; + + typedef struct ssf_ref_str + { + bool locFl; // true = u.loc false=u.id + unsigned left; + unsigned top; + unsigned col_bottom; + + struct ssf_ref_note_str* refNoteL; // ref_note's linked to this ref + + union { + cmScoreLoc_t* loc; + unsigned id; // bar/section id + } u; + } ssf_ref_t; + + typedef struct ssf_ref_note_str + { + ssf_ref_t* ref; + cmScoreEvt_t* evt; + unsigned top; + struct ssf_ref_note_str* link; + } ssf_ref_note_t; + + typedef struct ssf_perf_note_str + { + unsigned left; + unsigned top; + ssf_ref_note_t* refNote; + const ssf_note_on_t* msg; + + 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; + + ssf_ref_note_t* refNoteA; + unsigned refNoteAllocN; + unsigned refNoteN; + + ssf_perf_note_t* perfA; + unsigned perfAllocN; + unsigned perfN; + + } ssf_t; + + + rc_t _write_rect( ssf_t* p, unsigned left, unsigned top, const char* label, const char* classLabel ) + { + rc_t rc = kOkRC; + 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; + } + + 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); + } + + rc_t _write_rect_id_pitch( ssf_t* p, unsigned left, unsigned top, const char* classLabel, unsigned id, unsigned midi_pitch ) + { + 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); + } + + rc_t _write_rect_pitch( ssf_t* p, unsigned left, unsigned top, const char* classLabel, unsigned midi_pitch ) + { + assert( midi_pitch < 128 ); + const unsigned labelCharN = 63; + char label[labelCharN+1]; + snprintf(label,labelCharN,"%s",midi::midiToSciPitch((uint8_t)midi_pitch)); + return _write_rect(p,left,top,label,classLabel); + } + + + rc_t _write_ref( ssf_t* p ) + { + rc_t rc = kOkRC; + + for(unsigned i=0; irefN; ++i) + { + ssf_ref_t* r = p->refA + i; + const char* classLabel = r->locFl ? "loc" : "bar"; + unsigned id = r->locFl ? r->u.loc->index : r->u.id; + + // write ref rect + if((rc = _write_rect_id( p, r->left, r->top, classLabel, id )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing 'loc' rect id:%i.",r->u.loc->index); + goto errLabel; + } + + // write the ref-note + for(ssf_ref_note_t* rn=r->refNoteL; rn!=nullptr; rn=rn->link) + { + if((rc = _write_rect_pitch( p, r->left, rn->top, "ref_note", rn->evt->pitch )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing 'ref_note' rect."); + goto errLabel; + } + } + } + + for(unsigned i=0; iperfN; ++i) + { + ssf_perf_note_t* pn = p->perfA + i; + if((rc = _write_rect_pitch( p, pn->left, pn->top, "perf_note", pn->msg->pitch )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing 'pref_note' rect."); + goto errLabel; + } + + } + + 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); + 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 ) + { + + ref = p->refA + p->refN; + ref->left = x_coord_ref; + ref->top = y_coord; + ref->col_bottom = y_coord + BOX_H + BORD_Y; + + p->refN += 1; + + if( p->refN == p->refAllocN ) + cwLogWarning("SVG score follower reference array cache is full."); + + x_coord_ref += (BOX_W+BORD_X); + + } + return ref; + } + + 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->u.id = e->barNumb; + } + } + + void _create_loc_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 = true; + ref->u.loc = cmScoreLoc(p->cmScH, e->locIdx ); + + cwAssert( ref->u.loc != nullptr and ref->u.loc->index == e->locIdx ); + } + } + + void _create_ref_array( ssf_t* p, unsigned& x_coord_ref, unsigned y_coord ) + { + + // create the cmScoreLoc array + unsigned cmEvtN = cmScoreEvtCount(p->cmScH); + unsigned loc_idx = kInvalidIdx; + + p->refAllocN = cmEvtN; + p->refA = mem::allocZ(p->refAllocN); + p->refN = 0; + + for(unsigned i=0; icmScH,i); + + // if this is a bar marker + if( e->type == kBarEvtScId ) + _create_bar_ref(p,e,x_coord_ref,y_coord); + + // if this is the start of a new location + if( e->locIdx != loc_idx) + { + _create_loc_ref(p,e,x_coord_ref,y_coord); + loc_idx = e->locIdx; + } + } + } + + rc_t _create_ref_note_array( ssf_t* p ) + { + rc_t rc = kOkRC; + + p->refNoteAllocN = p->refAllocN; + p->refNoteA = mem::allocZ(p->refNoteAllocN); + p->refNoteN = 0; + + for(unsigned i=0; irefN; ++i) + if( p->refA[i].locFl ) + for(unsigned j=0; jrefA[i].u.loc->evtCnt; ++j) + if( p->refA[i].u.loc->evtArray[j]->type == kNonEvtScId && p->refNoteN < p->refNoteAllocN ) + { + ssf_ref_note_t* rn = p->refNoteA + p->refNoteN + 1; + rn->ref = p->refA + i; + rn->evt = p->refA[i].u.loc->evtArray[j]; + rn->top = rn->ref->col_bottom; + rn->link = rn->ref->refNoteL; + rn->ref->refNoteL = rn; + + rn->ref->col_bottom += BOX_H + BORD_Y; + + p->refNoteN += 1; + if( p->refNoteN >= p->refNoteAllocN ) + { + rc = cwLogError(kBufTooSmallRC,"The ref note array in the SVG score writer is too small."); + goto errLabel; + } + } + + errLabel: + return rc; + } + + ssf_ref_note_t* _find_ref_note( ssf_t* p, const cmScoreEvt_t* e ) + { + ssf_ref_note_t* rn = nullptr; + for(unsigned i=0; irefN; ++i) + { + 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 ) + { + // locate the ref note associated with e + for(rn=r->refNoteL; rn!=nullptr; rn=rn->link ) + if( rn->evt->index == e->index ) + break; + } + } + + // the reference must be found for the event + 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 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; + + pn->left = ref->left; + pn->top = ref->col_bottom; + pn->refNote = rn; + pn->msg = msg; + ref->col_bottom += BOX_H + BORD_Y; + + p->perfN += 1; + } + + 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; + p->perfA = mem::allocZ(p->perfAllocN); + p->perfN = 0; + + ssf_ref_t* r0 = nullptr; + + // for each performed MIDI note + for(unsigned muid=0; muidmatcher->ri; ++j) + { + if( p->matcher->res[j].muid == muid ) + { + assert( p->matcher->res[j].scEvtIdx != kInvalidIdx ); + + // 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 ); + assert( rn != nullptr ); + + if((rc = _setup_perf_note(p,rn->ref,rn,rn0,midiA+muid)) != kOkRC ) + goto errLabel; + + rn0 = rn; + r0 = rn->ref; + } + else + { + // this should be impossible + assert(0); + } + } + } + + // if this perf note does not have any matches + if( rn == nullptr ) + if((rc = _setup_perf_note(p,r0,nullptr,nullptr,midiA+muid)) != kOkRC ) + goto errLabel; + + } + + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"create_perf_array failed."); + + return rc; + } + + + void _create_css( ssf_t* p ) + { + install_css(p->svgH,".bar","fill",0xc0c0c0,"rgb"); + install_css(p->svgH,".loc","fill",0xe0e0e0,"rgb"); + install_css(p->svgH,".ref_note","fill",0x20B2AA,"rgb"); + install_css(p->svgH,".perf_note","fill",0xE0FFFF,"rgb"); + } + + ssf_t* _create( cmScH_t cmScH, cmScMatcher* matcher, const ssf_note_on_t* midiA, unsigned midiN ) + { + rc_t rc; + ssf_t* p = mem::allocZ(); + + unsigned x_coord = 0; + unsigned y_coord = 0; + + p->cmScH = cmScH; + p->matcher = matcher; + + // 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 ); + + _create_perf_array(p,midiA,midiN ); + + errLabel: + if(rc != kOkRC ) + _destroy(p); + + return p; + + } + + + } +} + +cw::rc_t cw::score_follower::svgScoreFollowWrite( cmScH_t cmScH, + cmScMatcher* matcher, + ssf_note_on_t* midiA, + unsigned midiN, + const char* out_fname ) +{ + rc_t rc = kOkRC; + ssf_t* p; + + if((p = _create(cmScH, matcher, midiA, midiN )) == nullptr ) + { + rc = cwLogError(rc,"Score follower SVG writer initialization failed."); + goto errLabel; + } + + if((rc = _write_ref(p)) != kOkRC ) + { + 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; + +} + + + + diff --git a/cwSvgScoreFollow.h b/cwSvgScoreFollow.h new file mode 100644 index 0000000..ef6435c --- /dev/null +++ b/cwSvgScoreFollow.h @@ -0,0 +1,20 @@ +namespace cw +{ + namespace score_follower + { + typedef struct ssf_note_on_str + { + double sec; + uint8_t pitch; + uint8_t vel; + uint8_t pad[2]; + } ssf_note_on_t; + + rc_t svgScoreFollowWrite( cmScH_t cmScH, + cmScMatcher* matcher, + ssf_note_on_t* perfA, + unsigned perfN, + const char* out_fname ); + + } +}