diff --git a/cwFlowPerf.cpp b/cwFlowPerf.cpp index da762ed..5a728d7 100644 --- a/cwFlowPerf.cpp +++ b/cwFlowPerf.cpp @@ -50,7 +50,9 @@ namespace cw namespace score_player { enum { - kScoreFileNamePId, + kScoreFNamePId, + kVelTblFnamePId, + kVelTblLabelPId, kDoneFlPId, kOutPId, kLocPId @@ -61,9 +63,18 @@ namespace cw 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 { @@ -71,24 +82,35 @@ namespace cw 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* fname ) + 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; + perf_score::handle_t perfScoreH; + const perf_score::event_t* score_evt = nullptr; + char* fname = nullptr; - if( fname == nullptr || textLength(fname)==0 ) + 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); @@ -110,19 +132,18 @@ namespace cw p->msgA = mem::allocZ(p->msgAllocN); p->chMsgA = mem::allocZ(p->msgAllocN); - for(unsigned i=0; imsgAllocN; ++i,++score_evt) + for(; p->msgNmsgAllocN && score_evt !=nullptr; score_evt=score_evt->link) { if( score_evt->status != 0 ) { - msg_t* m = p->msgA + p->msgN; + 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; + m->loc = score_evt->loc; + m->meas = score_evt->meas; + m->midi = mm; - time::fracSecondsToSpec( mm->timeStamp, score_evt->sec ); mm->devIdx = kInvalidIdx; @@ -132,25 +153,175 @@ namespace cw 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: - perf_score::destroy(perfScoreH); + 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,kScoreFileNamePId, "fname", kBaseSfxId, score_fname)) != kOkRC ) + 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; } @@ -162,6 +333,32 @@ namespace cw 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 ); @@ -174,6 +371,14 @@ namespace cw { 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; } @@ -203,7 +408,11 @@ namespace cw 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; @@ -213,7 +422,8 @@ namespace cw } - if( done_fl ) + + if( done_fl ) var_set(proc, kDoneFlPId, kAnyChIdx, true ); }