#include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.h" #include "cwMem.h" #include "cwText.h" #include "cwObject.h" #include "cwVectOps.h" #include "cwMtx.h" #include "cwDspTypes.h" // srate_t, sample_t, coeff_t, ... #include "cwTime.h" #include "cwMidiDecls.h" #include "cwMidi.h" #include "cwMidiFile.h" #include "cwFlowDecl.h" #include "cwFlow.h" #include "cwFlowValue.h" #include "cwFlowTypes.h" #include "cwFlowNet.h" #include "cwFlowProc.h" #include "cwDynRefTbl.h" #include "cwScoreParse.h" #include "cwSfScore.h" #include "cwSfTrack.h" #include "cwPerfMeas.h" #include "cwScoreFollowerPerf.h" #include "cwScoreFollower.h" #include "cwPianoScore.h" #include "cwFlowPerf.h" namespace cw { namespace flow { //------------------------------------------------------------------------------------------------------------------ // // Score Player // namespace score_player { enum { kScoreFNamePId, kVelTblFnamePId, kVelTblLabelPId, kDoneFlPId, kOutPId, kLocPId }; typedef struct { unsigned sample_idx; unsigned loc; unsigned meas; unsigned d1; // inital d1 value before velocity mapping was applied midi::ch_msg_t* midi; // index of associated msg in chMsgA } msg_t; typedef struct vel_tbl_str { unsigned* tblA; unsigned tblN; char* label; struct vel_tbl_str* link; } vel_tbl_t; typedef struct { unsigned msgAllocN; unsigned msgN; msg_t* msgA; // msgA[ msgN ] midi::ch_msg_t* chMsgA; // chMsgA[ msgN ] vel_tbl_t* velTblL; // List of vel. tables. vel_tbl_t* activeVelTbl; // Currently active vel. table or null if no vel. tbl is active. unsigned sample_idx; unsigned msg_idx; } inst_t; rc_t _load_score( proc_t* proc, inst_t* p, const char* score_fname ) { rc_t rc = kOkRC; perf_score::handle_t perfScoreH; const perf_score::event_t* score_evt = nullptr; char* fname = nullptr; if( score_fname == nullptr || textLength(score_fname)==0 ) { rc = cwLogError(kInvalidArgRC,"The score filename is blank."); goto errLabel; } if((fname = proc_expand_filename( proc, score_fname )) == nullptr ) { rc = cwLogError(kOpFailRC,"The score filename (%s) is invalid.",score_fname); goto errLabel; } if((rc= perf_score::create( perfScoreH, fname )) != kOkRC ) { rc = cwLogError(rc,"Score create failed on '%s'.",fname); goto errLabel; } if((p->msgAllocN = perf_score::event_count(perfScoreH)) == 0 ) { rc = cwLogWarning("The score '%s' is empty.",fname); goto errLabel; } if((score_evt = perf_score::base_event(perfScoreH)) == nullptr ) { rc = cwLogError(kOpFailRC,"The score '%s' could not be accessed.",fname); goto errLabel; } p->msgA = mem::allocZ(p->msgAllocN); p->chMsgA = mem::allocZ(p->msgAllocN); for(; p->msgNmsgAllocN && score_evt !=nullptr; score_evt=score_evt->link) { if( score_evt->status != 0 ) { msg_t* m = p->msgA + p->msgN; midi::ch_msg_t* mm = p->chMsgA + p->msgN; m->sample_idx = (unsigned)(proc->ctx->sample_rate * score_evt->sec); m->loc = score_evt->loc; m->meas = score_evt->meas; m->midi = mm; time::fracSecondsToSpec( mm->timeStamp, score_evt->sec ); mm->devIdx = kInvalidIdx; mm->portIdx= kInvalidIdx; mm->uid = score_evt->uid; mm->ch = score_evt->status & 0x0f; mm->status = score_evt->status & 0xf0; mm->d0 = score_evt->d0; mm->d1 = score_evt->d1; m->d1 = score_evt->d1; p->msgN += 1; } } errLabel: if( rc != kOkRC ) rc = cwLogError(rc,"Score load failed on '%s'.",cwStringNullGuard(fname)); perf_score::destroy(perfScoreH); mem::release(fname); return rc; } rc_t _load_vel_table_file( proc_t* proc, inst_t* p, const char* vel_tbl_fname ) { rc_t rc = kOkRC; object_t* cfg = nullptr; const object_t* tblL = nullptr; unsigned tblN = 0; char* fname = nullptr; if( vel_tbl_fname == nullptr || textLength(vel_tbl_fname)==0 ) { rc = cwLogError(kInvalidArgRC,"The velocity table filename is blank."); goto errLabel; } if((fname = proc_expand_filename( proc, vel_tbl_fname )) == nullptr ) { rc = cwLogError(kOpFailRC,"The velocity table filename (%s) is invalid.",vel_tbl_fname); goto errLabel; } if((rc = objectFromFile(fname,cfg)) != kOkRC ) { rc = cwLogError(rc,"Velocity table file parse failed."); goto errLabel; } if((rc = cfg->getv("tables",tblL)) != kOkRC ) { rc = cwLogError(rc,"Velocity table file has no 'tables' field."); goto errLabel; } tblN = tblL->child_count(); for(unsigned i=0; ichild_ele(i); const object_t* velListCfg = nullptr; vel_tbl_t* vt = nullptr; const char* label = nullptr; if((rc = tbl->getv("table",velListCfg, "name",label)) != kOkRC ) { rc = cwLogError(rc,"Velocity table at index %i failed.",i); goto errLabel; } vt = mem::allocZ(); vt->link = p->velTblL; p->velTblL = vt; vt->tblN = velListCfg->child_count(); vt->label = mem::duplStr(label); // if the table is empty if( vt->tblN == 0 ) { rc = cwLogError(rc,"The velocity table named '%s' appears to be blank.",cwStringNullGuard(label)); continue; } vt->tblA = mem::allocZ(vt->tblN); for(unsigned j=0; jtblN; ++j) { const object_t* intCfg; if((intCfg = velListCfg->child_ele(j)) == nullptr ) { rc = cwLogError(rc,"Access to the integer value at index %i failed on vel. table '%s'.",j,cwStringNullGuard(label)); goto errLabel; } if((rc = intCfg->value(vt->tblA[j])) != kOkRC ) { rc = cwLogError(rc,"Parse failed on integer value at index %i in vel. table '%s'.",j,cwStringNullGuard(label)); goto errLabel; } } } errLabel: if( rc != kOkRC ) rc = cwLogError(rc,"Score velocity table file load failed on '%s'.",cwStringNullGuard(vel_tbl_fname)); if( cfg != nullptr ) cfg->free(); mem::release(fname); return rc; } rc_t _activate_vel_table( proc_t* proc, inst_t* p, const char* vel_tbl_label ) { for(vel_tbl_t* vt = p->velTblL; vt!=nullptr; vt=vt->link) if( textIsEqual(vt->label,vel_tbl_label)) { p->activeVelTbl = vt; return kOkRC; } cwLogWarning("The requested velocity table '%s' was not found on the score instance '%s:%i'.",vel_tbl_label,proc->label, proc->label_sfx_id); return kOkRC; } rc_t _apply_vel_table( inst_t* p ) { rc_t rc = kOkRC; if( p->activeVelTbl == nullptr ) { cwLogWarning("A velocity table has not been selected."); goto errLabel; } for(unsigned i=0; imsgN; ++i) { midi::ch_msg_t* m = p->msgA[i].midi; if( midi::isNoteOn(m->status,m->d1) ) { if( p->msgA[i].d1 >= p->activeVelTbl->tblN ) { rc = cwLogError(kInvalidArgRC,"The pre-mapped velocity value %i is outside of the range (%i) of the velocity table '%s'.",p->msgA[i].d1,p->activeVelTbl->tblN,cwStringNullGuard(p->activeVelTbl->label)); goto errLabel; } m->d1 = p->activeVelTbl->tblA[ p->msgA[i].d1 ]; } } errLabel: return rc; } rc_t _create( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; const char* score_fname = nullptr; const char* vel_tbl_fname = nullptr; const char* vel_tbl_label = nullptr; if((rc = var_register_and_get(proc,kAnyChIdx, kScoreFNamePId, "fname", kBaseSfxId, score_fname, kVelTblFnamePId, "vel_tbl_fname", kBaseSfxId, vel_tbl_fname, kVelTblLabelPId, "vel_tbl_label", kBaseSfxId, vel_tbl_label)) != kOkRC ) { goto errLabel; } if((rc = var_register(proc,kAnyChIdx, kLocPId,"loc", kBaseSfxId, kDoneFlPId,"done_fl", kBaseSfxId )) != kOkRC ) { goto errLabel; } // load the score if((rc = _load_score( proc, p, score_fname )) != kOkRC ) { goto errLabel; } // load p->velTblL from the vel table file if((rc = _load_vel_table_file( proc, p, vel_tbl_fname )) != kOkRC ) { goto errLabel; } // activate the selected velocity table if((rc = _activate_vel_table( proc, p, vel_tbl_label )) != kOkRC ) { goto errLabel; } // apply the selected velocity table if( p->activeVelTbl != nullptr ) { if((rc = _apply_vel_table( p )) != kOkRC ) { goto errLabel; } } // create one output MIDI buffer rc = var_register_and_set( proc, "out", kBaseSfxId, kOutPId, kAnyChIdx, nullptr, 0 ); errLabel: return rc; } rc_t _destroy( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; for(vel_tbl_t* vt=p->velTblL; vt!=nullptr; vt=vt->link) { vel_tbl_t* vt0 = vt->link; mem::release(vt->tblA); mem::release(vt); vt = vt0; } return rc; } rc_t _value( proc_t* proc, inst_t* p, variable_t* var ) { rc_t rc = kOkRC; return rc; } rc_t _exec( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; mbuf_t* mbuf = nullptr; bool done_fl = false; p->sample_idx += proc->ctx->framesPerCycle; // get the output variable if((rc = var_get(proc,kOutPId,kAnyChIdx,mbuf)) != kOkRC ) rc = cwLogError(kInvalidStateRC,"The MIDI file instance '%s' does not have a valid MIDI output buffer.",proc->label); else { mbuf->msgA = nullptr; mbuf->msgN = 0; while( p->msg_idx < p->msgN && p->sample_idx >= p->msgA[p->msg_idx].sample_idx ) { if( mbuf->msgA == nullptr ) { // Note we only set a pointer to the first msg in p->chMsgA[] // successive messages will be consecutively after it. mbuf->msgA = p->msgA[p->msg_idx].midi; } mbuf->msgN += 1; p->msg_idx += 1; done_fl = p->msg_idx == p->msgN; } if( done_fl ) var_set(proc, kDoneFlPId, kAnyChIdx, true ); } return rc; } rc_t _report( proc_t* proc, inst_t* p ) { return kOkRC; } class_members_t members = { .create = std_create, .destroy = std_destroy, .value = std_value, .exec = std_exec, .report = std_report }; } // score_player //------------------------------------------------------------------------------------------------------------------ // // Midi To Msg // namespace midi_to_msg { enum { kInPId, kFieldsPId, kBufCntPId, kOutPId }; typedef struct { recd_array_t* recd_array; unsigned allocRecdN; unsigned midiFieldIdx; } inst_t; rc_t _create_recd_array( inst_t* p, const char* fields_string ) { rc_t rc = kOkRC; const char* prefix_string = "midi "; unsigned sn = textLength(prefix_string) + textLength(fields_string); char s[ sn+1 ]; strcpy(s,prefix_string); strcat(s,fields_string); s[sn] = 0; recd_type_t* recd_type = nullptr; if((rc = recd_type_create( recd_type, nullptr, s )) != kOkRC ) { rc = cwLogError(kOpFailRC,"recd type failed on field strings:'%s'.",cwStringNullGuard(s)); goto errLabel; } // Allocate a recd_buf large enough to hold the max. number of messages arriving on a single call to exec(). if((rc = recd_array_create( p->recd_array, recd_type, p->allocRecdN )) != kOkRC ) { rc = cwLogError(kOpFailRC,"recd_array create of size %i failed.",p->allocRecdN); goto errLabel; } errLabel: return rc; } rc_t _create( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; const char* fields_string = nullptr; if((rc = var_register( proc, kAnyChIdx, kInPId, "in", kBaseSfxId )) != kOkRC ) { goto errLabel; } if((rc = var_register_and_get( proc, kAnyChIdx, kBufCntPId, "buf_cnt", kBaseSfxId, p->allocRecdN, kFieldsPId, "fields", kBaseSfxId, fields_string )) != kOkRC ) { goto errLabel; } if((rc = _create_recd_array(p,fields_string )) != kOkRC ) { goto errLabel; } // create one output msg buffer if((rc = var_register_and_set( proc, "out", kBaseSfxId, kOutPId, kAnyChIdx, p->recd_array->type, nullptr, 0 )) != kOkRC ) { goto errLabel; } if((p->midiFieldIdx = recd_type_field_index( p->recd_array->type, "midi")) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"The 'midi' record field was not found."); goto errLabel; } errLabel: return rc; } rc_t _destroy( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; recd_array_destroy(p->recd_array); return rc; } rc_t _value( proc_t* proc, inst_t* p, variable_t* var ) { rc_t rc = kOkRC; return rc; } rc_t _exec( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; rbuf_t* out_rbuf = nullptr; mbuf_t* in_mbuf = nullptr; // get the output record buffer if((rc = var_get(proc,kOutPId,kAnyChIdx,out_rbuf)) != kOkRC ) { rc = cwLogError(kInvalidStateRC,"The output recd buf variable access failed."); goto errLabel; } // get the input MIDI buffer if((rc = var_get(proc,kInPId,kAnyChIdx,in_mbuf)) != kOkRC ) { rc = cwLogError(kInvalidStateRC,"The input MIDI buf variable access failed."); goto errLabel; } // copy a pointer to each input MIDI record to the 'midi' field of the assoc'd recd for(unsigned i=0; imsgN; ++i) { //recd_t* r = p->recd_array->recdA + i; //recd_set( p->recd_array->type, r, p->midiFieldIdx, in_mbuf->msgA + i ); } // the rbuf is just a proxy to the first 'in_mbuf->msgN' records in the p->recd_array. rbuf_setup(out_rbuf,p->recd_array->type,p->recd_array->recdA, in_mbuf->msgN ); errLabel: return rc; } rc_t _report( proc_t* proc, inst_t* p ) { return kOkRC; } class_members_t members = { .create = std_create, .destroy = std_destroy, .value = std_value, .exec = std_exec, .report = std_report }; } // midi_to_msg //------------------------------------------------------------------------------------------------------------------ // // Score Follower // namespace score_follower { enum { kInPId, kDynTblFnamePId, kFnamePId, kScoreWndCntPId, kMidiWndCntPId, kPrintFlPId, kBacktrackFlPId, kLocPId, kOutPId, }; typedef struct { cw::dyn_ref_tbl::handle_t dynRefH; cw::score_parse::handle_t scParseH; cw::sfscore::handle_t scoreH; cw::score_follower::handle_t sfH; unsigned midi_field_idx; unsigned loc_field_idx; } inst_t; rc_t _create( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; rbuf_t* in_rbuf = nullptr; const char* score_fname = nullptr; const char* dyn_tbl_fname = nullptr; bool printParseWarningsFl = true; cw::score_follower::args_t args; if((rc = var_register_and_get(proc,kAnyChIdx, kInPId, "in", kBaseSfxId, in_rbuf, kFnamePId, "fname", kBaseSfxId, score_fname, kDynTblFnamePId,"dyn_ref_fname", kBaseSfxId, dyn_tbl_fname, kScoreWndCntPId,"score_wnd", kBaseSfxId, args.scoreWndLocN, kMidiWndCntPId, "midi_wnd", kBaseSfxId, args.midiWndLocN, kPrintFlPId, "print_fl", kBaseSfxId, args.trackPrintFl, kBacktrackFlPId,"back_track_fl", kBaseSfxId, args.trackResultsBacktrackFl )) != kOkRC ) { goto errLabel; } // get the input record 'midi' field index if((p->midi_field_idx = recd_type_field_index( in_rbuf->type, "midi")) == kInvalidIdx ) { rc = cwLogError(rc,"The input record type on '%s:%i' does not have a 'midi' field.",cwStringNullGuard(proc->label),proc->label_sfx_id); goto errLabel; } // get the input record 'loc' field index if((p->loc_field_idx = recd_type_field_index( in_rbuf->type, "loc")) == kInvalidIdx ) { rc = cwLogError(rc,"The input record type on '%s:%i' does not have a 'loc' field.",cwStringNullGuard(proc->label),proc->label_sfx_id); goto errLabel; } // parse the dynamics reference array if((rc = dyn_ref_tbl::create(p->dynRefH,dyn_tbl_fname)) != kOkRC ) { rc = cwLogError(rc,"The reference dynamics array parse failed."); goto errLabel; } // parse the score if((rc = create( p->scParseH, score_fname, proc->ctx->sample_rate, p->dynRefH, printParseWarningsFl )) != kOkRC ) { rc = cwLogError(rc,"Score parse failed."); goto errLabel; } // create the SF score if((rc = create( p->scoreH, p->scParseH, printParseWarningsFl)) != kOkRC ) { rc = cwLogError(rc,"SF Score create failed."); goto errLabel; } args.enableFl = true; args.scoreH = p->scoreH; // create the score follower if((rc = create( p->sfH, args )) != kOkRC ) { rc = cwLogError(rc,"Score follower create failed."); goto errLabel; } errLabel: return rc; } rc_t _destroy( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; destroy(p->sfH); destroy(p->scoreH); destroy(p->scParseH); destroy(p->dynRefH); return rc; } rc_t _value( proc_t* proc, inst_t* p, variable_t* var ) { rc_t rc = kOkRC; return rc; } rc_t _exec( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; unsigned sample_idx = proc->ctx->cycleIndex * proc->ctx->framesPerCycle; double sec = ((double)sample_idx) / proc->ctx->sample_rate; rbuf_t* rbuf = nullptr; unsigned result_recd_idx = kInvalidIdx; if((rc = var_get(proc,kInPId,kAnyChIdx,rbuf)) == kOkRC) goto errLabel; // for each incoming record for(unsigned i=0; irecdN; ++i) { bool match_fl= false; midi::ch_msg_t* m = nullptr; if((rc = recd_get( rbuf->type, rbuf->recdA+i, p->midi_field_idx, m)) != kOkRC ) { rc = cwLogError(rc,"The 'midi' field read failed."); goto errLabel; } if((rc = exec( p->sfH, sec, sample_idx, m->uid, m->status, m->d0,m->d1, match_fl )) != kOkRC ) { rc = cwLogError(rc,"Score follower exec failed."); goto errLabel; } if( match_fl ) result_recd_idx = i; } if( result_recd_idx != kInvalidIdx ) { unsigned resultIdxN = 0; const unsigned* resultIdxA = current_result_index_array(p->sfH, resultIdxN ); const sftrack::result_t* resultA = cw::score_follower::track_result(p->sfH); for(unsigned i=0; iscoreH, r->scEvtIdx ); // store the performance data in the score set_perf( p->scoreH, r->scEvtIdx, r->sec, r->pitch, r->vel, r->cost ); if( i+1 == resultIdxN ) { //recd_set( rbuf->type, rbuf->recdA + result_recd_idx, p->loc_field_idx, e->oLocId ); var_set( proc, kLocPId, kAnyChIdx, e->oLocId ); } } var_set( proc, kOutPId, kAnyChIdx, rbuf ); } errLabel: return rc; } rc_t _report( proc_t* proc, inst_t* p ) { return kOkRC; } class_members_t members = { .create = std_create, .destroy = std_destroy, .value = std_value, .exec = std_exec, .report = std_report }; } // score_follower } // flow } //cw