#include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" #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" #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" namespace cw { namespace score_follower { typedef struct score_follower_str { 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 ) { return handleToPtr(h); } rc_t _parse_cfg( score_follower_t* p, const object_t* cfg ) { rc_t rc = kOkRC; const char* score_csv_fname; if((rc = cfg->getv("score_csv_fname", score_csv_fname, "search_area_locN", p->search_area_locN, "key_wnd_locN", p->key_wnd_locN )) != kOkRC ) { rc = cwLogError(kInvalidArgRC, "Score follower argument parsing failed."); goto errLabel; } if((p->score_csv_fname = filesys::expandPath( score_csv_fname )) == nullptr ) { rc = cwLogError(kOpFailRC,"Score follower score file expansion failed."); goto errLabel; } errLabel: return rc; } unsigned _get_max_cw_loc_value( score_follower_t* p ) { // Note: cmScoreEvt_t.csvEventId is the app score 'loc' value. 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, cm::handle_t cmCtxH, double srate ) { 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; score_follower_t* p = mem::allocZ(); if((rc = _parse_cfg(p,cfg)) != kOkRC ) goto errLabel; 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 ) { _destroy(p); cwLogError(rc,"Score follower create failed."); } return rc; } cw::rc_t cw::score_follower::destroy( handle_t& hRef ) { rc_t rc = kOkRC; score_follower_t* p = nullptr; if( !hRef.isValid() ) return rc; p = _handleToPtr(hRef); if((rc = _destroy(p)) != kOkRC ) return rc; hRef.clear(); return rc; } 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, bool& newMatchFlRef ) { 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