From 7312e91421deb75ad9f70b0d91a59ae663638f1b Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 30 Jan 2023 19:38:11 -0500 Subject: [PATCH] cwCmInterface.h/cpp, cwScoreFollower.h/cpp, Makefile.am : Initial score_follow development --- Makefile.am | 4 +- cwCmInterface.cpp | 80 +++++++++-- cwCmInterface.h | 15 +- cwScoreFollower.cpp | 324 +++++++++++++++++++++++++++++++++++++++++--- cwScoreFollower.h | 15 +- 5 files changed, 402 insertions(+), 36 deletions(-) diff --git a/Makefile.am b/Makefile.am index 169b209..5cac028 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 libcwSRC += src/libcw/cwIoPresetSelApp.cpp src/libcw/cwPianoScore.cpp src/libcw/cwPresetSel.cpp -# libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h -# libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp +libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h +libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp endif diff --git a/cwCmInterface.cpp b/cwCmInterface.cpp index 9830499..d2cdc75 100644 --- a/cwCmInterface.cpp +++ b/cwCmInterface.cpp @@ -1,3 +1,4 @@ + #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" @@ -18,8 +19,11 @@ #include "cmScore.h" #include "cmText.h" #include "cmFileSys.h" +#include "cmProcObj.h" extern "C" { + + void _cm_print_info( void* arg, const char* text ) { cwLogInfo(text); @@ -35,11 +39,18 @@ namespace cw { namespace cm { - typedef struct cm_str - { - ::cmCtx_t ctx; - } cm_t; - + + extern "C" { + typedef struct cm_str + { + cmCtx_t ctx; + cmSymTblH_t symTblH; + cmLHeapH_t lhH; + cmProcCtx* procCtx; + + } cm_t; + } + cm_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } @@ -52,10 +63,43 @@ namespace cw cmMdReport( kIgnoreNormalMmFl ); cmMdFinalize(); + cmLHeapDestroy(&p->lhH); + cmSymTblDestroy(&p->symTblH); + cmProcCtxFree(&p->procCtx); + mem::release(p); } return kOkRC; } + + rc_t _create_proc_ctx( cm_t* p, cmCtx_t* ctx ) + { + rc_t rc = kOkRC; + + // create the linked heap + if(cmLHeapIsValid( p->lhH = cmLHeapCreate(1024,ctx)) == false) + { + rc = cwLogError(kOpFailRC,"The cm linked heap allocation failed."); + goto errLabel; + } + + // intialize the symbol table + if( cmSymTblIsValid( p->symTblH = cmSymTblCreate(cmSymTblNullHandle,1,ctx)) == false ) + { + rc = cwLogError(kOpFailRC,"The cm linked heap allocation failed."); + goto errLabel; + } + + // initialize the proc context + if( (p->procCtx = cmProcCtxAlloc(NULL,&ctx->rpt,p->lhH,p->symTblH)) == NULL ) + { + rc = cwLogError(kOpFailRC,"The cm proc context allocation failed."); + goto errLabel; + } + + errLabel: + return rc; + } } } @@ -82,6 +126,14 @@ cw::rc_t cw::cm::create( handle_t& hRef ) cmTsInitialize( &p->ctx ); + if((rc = _create_proc_ctx(p, &p->ctx )) == kOkRC ) + hRef.set(p); + else + { + _destroy(p); + rc = cwLogError(rc,"cm Interface context create failed."); + } + return rc; } @@ -102,9 +154,17 @@ cw::rc_t cw::cm::destroy( handle_t& hRef ) } -::cmCtx_t* cw::cm::context( handle_t h ) -{ - cm_t* p = _handleToPtr(h); - return &p->ctx; +extern "C" { + struct cmCtx_str* cw::cm::context( handle_t h ) + { + cm_t* p = _handleToPtr(h); + return &p->ctx; + } + + struct cmProcCtx_str* cw::cm::proc_context( handle_t h ) + { + cm_t* p = _handleToPtr(h); + return p->procCtx; + } + } - diff --git a/cwCmInterface.h b/cwCmInterface.h index 557eac5..5398642 100644 --- a/cwCmInterface.h +++ b/cwCmInterface.h @@ -1,17 +1,24 @@ #ifndef cwCmInterface_h #define cwCmInterface_h +extern "C" { + struct cmCtx_str; + struct cmProcCtx_str; +} + namespace cw { namespace cm { - extern "C" { struct cmCtx_str; } typedef handle< struct cm_str > handle_t; - rc_t create( handle_t& hRef ); - rc_t destroy( handle_t& hRef ); - ::cmCtx_t* context( handle_t h ); + rc_t create( handle_t& hRef ); + rc_t destroy( handle_t& hRef ); + + + extern "C" struct cmCtx_str* context( handle_t h ); + extern "C" struct cmProcCtx_str* proc_context( handle_t h ); } } diff --git a/cwScoreFollower.cpp b/cwScoreFollower.cpp index 92e4caa..a8496b3 100644 --- a/cwScoreFollower.cpp +++ b/cwScoreFollower.cpp @@ -5,6 +5,10 @@ #include "cwText.h" #include "cwObject.h" #include "cwMidi.h" +#include "cwFileSys.h" +#include "cwMidi.h" +#include "cwMidiFile.h" +#include "cwCmInterface.h" #include "cwScoreFollower.h" #include "cmGlobal.h" @@ -12,10 +16,18 @@ #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" namespace cw { @@ -24,11 +36,15 @@ namespace cw typedef struct score_follower_str { - unsigned search_area_locN; - unsigned key_wnd_locN; - char* score_csv_fname; - - + double srate; + unsigned search_area_locN; + unsigned key_wnd_locN; + char* score_csv_fname; + cmScH_t cmScoreH; + cmScMatcher* matcher; + unsigned* match_idA; + unsigned match_id_allocN; + unsigned match_id_curN; } score_follower_t; score_follower_t* _handleToPtr( handle_t h ) @@ -49,7 +65,7 @@ namespace cw goto errLabel; } - if((app->score_csv_fname = filesys::expandPath( score_csv_fname )) == nullptr ) + if((p->score_csv_fname = filesys::expandPath( score_csv_fname )) == nullptr ) { rc = cwLogError(kOpFailRC,"Score follower score file expansion failed."); goto errLabel; @@ -58,37 +74,106 @@ namespace cw errLabel: return rc; } + + unsigned _get_max_cw_loc_value( score_follower_t* p ) + { + unsigned cmEvtN = cmScoreEvtCount( p->cmScoreH ); + unsigned maxCwLoc = 0; + for(unsigned i=0; iscore_csv_fname); mem::release(p); + cmScMatcherFree(&p->matcher); + cmScoreFinalize(&p->cmScoreH); return kOkRC; } + + + extern "C" void _score_follower_cb( struct cmScMatcher_str* smp, void* arg, cmScMatcherResult_t* r ) + { + score_follower_t* p = (score_follower_t*)arg; + cmScoreEvt_t* cse; + + // get a pointer to the matched event + if((cse = cmScoreEvt( p->cmScoreH, r->scEvtIdx )) == nullptr ) + { + cwLogError(kInvalidStateRC,"cm Score event index (%i) reported by the score follower is invalid.",r->scEvtIdx ); + } + else + { + if( p->match_id_curN >= p->match_id_allocN ) + { + cwLogError(kInvalidStateRC,"The score follower match id array is full."); + } + else + { + // the csvEventId corresponds to the cwPianoScore location + p->match_idA[ p->match_id_curN++ ] = cse->csvEventId; + } + } + + } + + + } } -cw::rc_t cw::score_follower::create( handle_t& hRef, const object_t* cfg, double srate ) +cw::rc_t cw::score_follower::create( handle_t& hRef, const object_t* cfg, cm::handle_t cmCtxH, double srate ) { - rc_t rc = kOkRC; - score_follower_t* p = nullptr; + rc_t rc = kOkRC; + cmCtx_t* cmCtx = context(cmCtxH); + cmProcCtx* cmProcCtx = proc_context(cmCtxH); + cmSymTblH_t cmSymTblH = cmSTATIC_NULL_HANDLE; + cmScRC_t scoreRC = cmOkRC; if((rc = destroy(hRef)) != kOkRC ) return rc; - p = mem::allocZ(); + score_follower_t* p = mem::allocZ(); if((rc = _parse_cfg(p,cfg)) != kOkRC ) goto errLabel; - cmScRC_t cmScoreInitialize( cmCtx_t* ctx, cmScH_t* hp, const cmChar_t* fn, double srate, const unsigned* dynRefArray, unsigned dynRefCnt, cmScCb_t cbFunc, void* cbArg, cmSymTblH_t stH ); - - //cmRC_t cmScMatcherInit( cmScMatcher* p, double srate, cmScH_t scH, unsigned scWndN, unsigned midiWndN, cmScMatcherCb_t cbFunc, void* cbArg ); - - + if((scoreRC = cmScoreInitialize( cmCtx, &p->cmScoreH, p->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); + goto errLabel; + } + + if((p->matcher = cmScMatcherAlloc( cmProcCtx, // Program context. + nullptr, // Existing cmScMatcher to reallocate or NULL to allocate a new cmScMatcher. + srate, // System sample rate. + p->cmScoreH, // Score handle. See cmScore.h. + p->search_area_locN, // Length of the scores active search area. ** See Notes. + p->key_wnd_locN, // Length of the MIDI active note buffer. ** See Notes. + _score_follower_cb, // A cmScMatcherCb_t function to be called to notify the recipient of changes in the score matcher status. + p )) == nullptr ) // User argument to 'cbFunc'. + { + cwLogError(kOpFailRC,"The score follower allocation failed."); + goto errLabel; + } + + + p->srate = srate; + p->match_id_allocN = 128; + p->match_idA = mem::allocZ(p->match_id_allocN); + hRef.set(p); + errLabel: if( rc != kOkRC ) { @@ -119,12 +204,217 @@ cw::rc_t cw::score_follower::destroy( handle_t& hRef ) cw::rc_t cw::score_follower::reset( handle_t h, unsigned loc ) { rc_t rc = kOkRC; + //score_follower_t* p = _handleToPtr(h); + + + // cmRC_t cmScMatcherReset( cmScMatcher* p, unsigned scLocIdx ); + return rc; } -cw::rc_t cw::score_follower::exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned status, uint8_t d0, uint8_t d1, unsigned* scLocIdxPtr ) +cw::rc_t cw::score_follower::exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned status, uint8_t d0, uint8_t d1, bool& newMatchFlRef ) { - rc_t rc = kOkRC; + rc_t rc = kOkRC; + cmRC_t cmRC = cmOkRC; + score_follower_t* p = _handleToPtr(h); + unsigned scLocIdx = cmInvalidIdx; + unsigned pre_match_id_curN = p->match_id_curN; + + newMatchFlRef = false; + + if((cmRC = cmScMatcherExec( p->matcher, smpIdx, muid, status, d0, d1, &scLocIdx )) != cmOkRC ) + { + switch( cmRC ) + { + case cmOkRC: + newMatchFlRef = p->match_id_curN != pre_match_id_curN; + break; + + case cmEofRC: + rc = cwLogInfo("Score match complete."); + break; + + case cmInvalidArgRC: + rc = cwLogError(kInvalidStateRC,"Score follower state is invalid."); + break; + + case cmSubSysFailRC: + rc = cwLogError(kOpFailRC,"The score follower failed during a resync attempt."); + break; + + default: + rc = cwLogError(kOpFailRC,"The score follower failed with an unknown error. cmRC:%i",cmRC); + } + + } + + return rc; } + + +unsigned* cw::score_follower::current_match_id_array( handle_t h, unsigned& cur_match_id_array_cnt_ref ) +{ + score_follower_t* p = _handleToPtr(h); + cur_match_id_array_cnt_ref = p->match_id_curN; + return p->match_idA; +} + +void cw::score_follower::clear_match_id_array( handle_t h ) +{ + score_follower_t* p = _handleToPtr(h); + p->match_id_curN = 0; +} + + +namespace cw { + namespace score_follower { + + typedef struct test_str + { + char* midi_fname; + char* out_dir; + double srate; + const object_t* cfg; + midi::file::handle_t mfH; + + } test_t; + + + rc_t _test_destroy( test_t* p ) + { + rc_t rc = kOkRC; + mem::release(p->midi_fname); + mem::release(p->out_dir); + + midi::file::close(p->mfH); + + return rc; + } + + rc_t _test_parse_cfg( test_t* p, const object_t* cfg ) + { + rc_t rc; + const char* midi_fname = nullptr; + const char* out_dir = nullptr; + + // read the test cfg. + if((rc = cfg->getv("midi_fname", midi_fname, + "srate", p->srate, + "cfg", p->cfg, + "out_dir", out_dir )) != 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 ) + { + rc = cwLogError(kOpFailRC,"The output directory path expansion failed."); + goto errLabel; + } + + // create the output directory + if( !filesys::isDir(p->out_dir)) + { + if((rc = filesys::makeDir(p->out_dir)) != kOkRC ) + { + cwLogError(kOpFailRC,"The output directory '%s' could not be created.", cwStringNullGuard(p->out_dir)); + goto errLabel; + } + } + + errLabel: + if(rc != kOkRC ) + { + _test_destroy(p); + rc = cwLogError(rc,"Score follower test parse cfg. failed."); + } + + return rc; + } + + + } +} + +cw::rc_t cw::score_follower::test( const object_t* cfg ) +{ + rc_t rc = kOkRC; + + test_t t = {0}; + + cm::handle_t cmCtxH; + handle_t sfH; + unsigned msgN = 0; + const midi::file::trackMsg_t** msgA = nullptr; + + // parse the test cfg + if((rc = _test_parse_cfg( &t, cfg )) != kOkRC ) + goto errLabel; + + // create a cm context record + if((rc = cm::create(cmCtxH)) != kOkRC ) + goto errLabel; + + // create the score follower + if((rc = create( sfH, t.cfg, cmCtxH, t.srate )) != kOkRC ) + goto errLabel; + + // 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)); + goto errLabel; + } + + // get a pointer to a time sorted list of MIDI messages in the file + if(((msgN = msgCount( t.mfH )) == 0) || ((msgA = midi::file::msgArray( t.mfH )) == nullptr) ) + { + rc = cwLogError(rc,"MIDI file msg array is empty or corrupt."); + goto errLabel; + } + + for(unsigned i=0; iamicro/1e6); + bool newMatchFl = false; + if((rc = exec(sfH, 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; + unsigned* matchIdA = current_match_id_array(sfH, matchIdN ); + + for(unsigned i=0; i handle_t; - rc_t create( handle_t& hRef, const object_t* cfg, double srate ); + rc_t create( handle_t& hRef, const object_t* cfg, cm::handle_t cmCtxH, double srate ); rc_t destroy( handle_t& hRef ); rc_t reset( handle_t h, unsigned loc ); - - rc_t exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned status, uint8_t d0, uint8_t d1, unsigned* scLocIdxPtr ); + + // If 'new_match_fl_ref' is returned as true then there are new match id's in the current_match_id_array[] + rc_t exec( handle_t h, unsigned smpIdx, unsigned muid, unsigned status, uint8_t d0, uint8_t d1, bool& new_match_fl_ref ); + + // Get a pointer to the event id's associated with the latest set of matches. + unsigned* current_match_id_array( handle_t h, unsigned& cur_match_id_array_cnt_ref ); + + // Clear the match id array. This should be done to empty the current_match_id_array() + void clear_match_id_array( handle_t h ); + + rc_t test( const object_t* cfg ); } }