From 637e9bfd2879755cfc23d179a6d65b438a693e60 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 21 Apr 2025 09:19:51 -0400 Subject: [PATCH 1/6] cwFlowProc.cpp : Added enable flag to 'midi_out'. Added 'gate flag' output to 'poly_voice_ctl' to reflect state of voice. Added 'voice_detector'. Changes to 'midi_file': Added 'alt_csv_fname' , Added 'r_out', Added initial 'start'/'stop' implementation. --- cwFlowProc.cpp | 362 +++++++++++++++++++++++++++++++++++++++++++++---- cwFlowProc.h | 1 + 2 files changed, 337 insertions(+), 26 deletions(-) diff --git a/cwFlowProc.cpp b/cwFlowProc.cpp index 5940a3b..7d79069 100644 --- a/cwFlowProc.cpp +++ b/cwFlowProc.cpp @@ -809,7 +809,8 @@ namespace cw kBufMsgCntPId, kInPId, kRInPId, - kPrintFlPId + kPrintFlPId, + kEnableFlPId, }; typedef struct @@ -832,13 +833,14 @@ namespace cw const char* port_label = nullptr; rbuf_t* rbuf = nullptr; bool printFl = false; - + bool enableFl = false; // Register variables and get their current value if((rc = var_register_and_get( proc, kAnyChIdx, kDevLabelPId, "dev_label", kBaseSfxId, dev_label, kPortLabelPId,"port_label", kBaseSfxId, port_label, kPrintFlPId, "print_fl", kBaseSfxId, printFl, + kEnableFlPId, "enable_fl", kBaseSfxId, enableFl, kBufMsgCntPId,"buf_cnt", kBaseSfxId, p->msgN )) != kOkRC ) { goto errLabel; @@ -907,9 +909,15 @@ namespace cw return kOkRC; } - void _send_msg( inst_t* p, bool print_fl, const midi::ch_msg_t* m ) + void _send_msg( inst_t* p, bool print_fl, bool enable_fl, const midi::ch_msg_t* m ) { - p->ext_dev->u.m.sendTripleFunc( p->ext_dev, m->ch, m->status, m->d0, m->d1 ); + //if( midi::isNoteOn(m->status,m->d1) ) + // printf("mo:%i %s\n",m->d0,midi::midiToSciPitch(m->d0)); + + + if( enable_fl ) + p->ext_dev->u.m.sendTripleFunc( p->ext_dev, m->ch, m->status, m->d0, m->d1 ); + if( print_fl ) { cwLogPrint("%2i 0x%2x %3i %3i : %s %s\n",m->ch, m->status, m->d0, m->d1, cwStringNullGuard(p->ext_dev->devLabel),cwStringNullGuard(p->ext_dev->portLabel)); @@ -921,9 +929,12 @@ namespace cw rc_t rc = kOkRC; const rbuf_t* rbuf = nullptr; bool print_fl = false; + bool enable_fl = true; const mbuf_t* src_mbuf = nullptr; var_get(proc,kPrintFlPId,kAnyChIdx,print_fl); + var_get(proc,kEnableFlPId,kAnyChIdx,enable_fl); + if( p->rin_exists_fl ) { @@ -937,7 +948,7 @@ namespace cw const midi::ch_msg_t* m = nullptr; if((rc = recd_get(rbuf->type,r,p->midi_fld_idx,m)) == kOkRC ) - _send_msg(p,print_fl,m); + _send_msg(p,print_fl,enable_fl,m); else { rc = cwLogError(rc,"Record 'midi' field read failed."); @@ -957,7 +968,7 @@ namespace cw { for(unsigned i=0; imsgN; ++i) { - _send_msg(p,print_fl,src_mbuf->msgA + i); + _send_msg(p,print_fl,enable_fl,src_mbuf->msgA + i); } } } @@ -4464,6 +4475,7 @@ namespace cw typedef struct { + unsigned baseGateFlPId; unsigned baseDoneFlPId; midi_t midiA[ midi::kMidiNoteCnt ]; @@ -4496,7 +4508,12 @@ namespace cw p->voiceA[voice_idx].pitch = midi::kInvalidMidiPitch; if( p->ns_fl ) - _store_note_state( proc, p, 0, 0, 0, 0, voice_idx ); + _store_note_state( proc, p, 0, 0, 0, 0, voice_idx ); + + // set the gate signal low + //printf("pvc:%i off\n",voice_idx); + var_set(proc,p->baseGateFlPId + voice_idx,kAnyChIdx,false); + } void _reset_all_voices( proc_t* proc, inst_t* p ) @@ -4523,7 +4540,8 @@ namespace cw goto errLabel; } - p->baseDoneFlPId = kBaseOutPId + p->voiceN; + p->baseGateFlPId = kBaseOutPId + p->voiceN; + p->baseDoneFlPId = p->baseGateFlPId + p->voiceN; p->voiceMsgN = kVoiceMsgN; p->voiceA = mem::allocZ(p->voiceN); @@ -4539,8 +4557,10 @@ namespace cw if((rc = var_register_and_set( proc, "out", i, kBaseOutPId + i, kAnyChIdx, nullptr, 0 )) != kOkRC ) goto errLabel; - // create one 'done_fl' variable per voice - if((rc = var_register_and_set( proc, kAnyChIdx, p->baseDoneFlPId + i, "done_fl", i, false )) != kOkRC ) + // create one 'done_fl' and 'gate_fl' variable per voice + if((rc = var_register_and_set( proc, kAnyChIdx, + p->baseDoneFlPId + i, "done_fl", i, false, + p->baseGateFlPId + i, "gate_fl", i, false )) != kOkRC ) goto errLabel; p->voiceA[i].msgA = mem::allocZ(p->voiceMsgN); @@ -4733,6 +4753,10 @@ namespace cw if( p->ns_fl ) _store_note_state( proc, p, m->uid, midi::kNoteOnMdId, m->d0, m->d1, voice_idx ); + + // set the gate signal high + //printf("pvc:%i on\n",voice_idx); + var_set(proc,p->baseGateFlPId + voice_idx,kAnyChIdx,true); return rc; } @@ -5392,10 +5416,12 @@ namespace cw p->gain_coeff = p->kReleaseGain; } - void _on_note_off( inst_t* p ) + void _on_note_off( proc_t* proc, inst_t* p ) { p->noff_fl = true; + //printf("nof:%i %i\n",proc->label_sfx_id,p->pitch); + if( !p->sustain_fl ) _begin_note_release(p); } @@ -5473,9 +5499,9 @@ namespace cw rc_t _exec( proc_t* proc, inst_t* p ) { - rc_t rc = kOkRC; - abuf_t* abuf = nullptr; - mbuf_t* mbuf = nullptr; + rc_t rc = kOkRC; + abuf_t* abuf = nullptr; + mbuf_t* mbuf = nullptr; unsigned actualFrmN = 0; //sample_t rms = 0; @@ -5508,13 +5534,13 @@ namespace cw } else { - _on_note_off(p); + _on_note_off(proc,p); _store_note_state(proc, p, m->uid, midi::kNoteOffMdId, m->d0, 0 ); } break; case midi::kNoteOffMdId: - _on_note_off(p); + _on_note_off(proc,p); _store_note_state(proc, p, m->uid, midi::kNoteOnMdId, m->d0, 0 ); break; @@ -5569,6 +5595,155 @@ namespace cw }; } + + + //------------------------------------------------------------------------------------------------------------------ + // + // voice_detector + // + namespace voice_detector + { + enum { + kInPId, + kEnableFlPId, + kRlsThreshPId, + kDoneFlPId + + }; + + enum { + kRmsBufN = 30 + }; + + typedef struct + { + bool enable_fl; + bool above_fl; + bool done_fl; + bool rls_thresh; + unsigned delta_cnt; + sample_t rms_buf[ kRmsBufN ]; + unsigned rms_buf_idx; + unsigned rms_buf_cnt; + + } inst_t; + + sample_t _calc_rms( inst_t* p, abuf_t* abuf ) + { + p->rms_buf[ p->rms_buf_idx ] = 0; + + // store the max rms among all channels + for( unsigned i=0; ichN; ++i) + { + sample_t rms; + if((rms = vop::sum_sq(abuf->buf + (i*abuf->frameN), abuf->frameN )) > p->rms_buf[ p->rms_buf_idx ] ) + p->rms_buf[ p->rms_buf_idx ] = rms; + } + + if( ++p->rms_buf_idx >= kRmsBufN ) + p->rms_buf_idx = 0; + + if( p->rms_buf_cnt++ >= kRmsBufN ) + p->rms_buf_cnt = kRmsBufN; + + return std::sqrt( vop::mean(p->rms_buf,p->rms_buf_cnt) ); + + } + + rc_t _create( proc_t* proc, inst_t* p ) + { + rc_t rc = kOkRC; + abuf_t* abuf = nullptr; + + // register the input audio variable + if((rc = var_register_and_get(proc,kAnyChIdx, + kInPId, "in", kBaseSfxId, abuf, + kEnableFlPId, "enable_fl", kBaseSfxId, p->enable_fl, + kRlsThreshPId,"rls_thresh",kBaseSfxId, p->rls_thresh, + kDoneFlPId, "done_fl", kBaseSfxId, p->done_fl)) != kOkRC ) + { + goto errLabel; + } + + + errLabel: + return rc; + } + + rc_t _destroy( proc_t* proc, inst_t* p ) + { + rc_t rc = kOkRC; + return rc; + } + + rc_t _notify( proc_t* proc, inst_t* p, variable_t* var ) + { + rc_t rc = kOkRC; + + switch( var->vid ) + { + case kEnableFlPId: + var_get(var,p->enable_fl); + if( p->enable_fl ) + { + var_set(proc,kDoneFlPId,kAnyChIdx,false); + p->above_fl = false; + } + + printf("vd-ena:%i %i\n",proc->label_sfx_id,p->enable_fl); + break; + + case kRlsThreshPId: + var_get(var,p->rls_thresh); + break; + } + + return rc; + } + + rc_t _exec( proc_t* proc, inst_t* p ) + { + rc_t rc = kOkRC; + abuf_t* abuf = nullptr; + sample_t rms; + + if((rc = var_get(proc,kInPId,kAnyChIdx,abuf)) != kOkRC ) + goto errLabel; + + rms = _calc_rms(p,abuf); + + if( rms > p->rls_thresh ) + p->above_fl = true; + + if( p->above_fl && rms < p->rls_thresh) + p->delta_cnt += 1; + else + p->delta_cnt = 0; + + if( p->enable_fl && p->delta_cnt >= 3 ) + { + printf("vd:%i off\n",proc->label_sfx_id); + p->done_fl = true; + var_set(proc,kDoneFlPId,kAnyChIdx,true); + } + + errLabel: + return rc; + } + + rc_t _report( proc_t* proc, inst_t* p ) + { return kOkRC; } + + class_members_t members = { + .create = std_create, + .destroy = std_destroy, + .notify = std_notify, + .exec = std_exec, + .report = std_report + }; + + } + //------------------------------------------------------------------------------------------------------------------ // @@ -8056,8 +8231,12 @@ namespace cw enum { kMidiFileNamePId, kCsvFileNamePId, + kCsvFileName2PId, + kStartPId, + kStopPId, kDoneFlPId, - kOutPId + kOutPId, + kROutPId }; typedef struct msg_str @@ -8077,14 +8256,60 @@ namespace cw char* midi_fname; char* csv_fname; + char* csv_fname_2; + + bool start_trig_fl; // the start btn was clicked + bool stop_trig_fl; // the stop btn was clicked + + + recd_array_t* recd_array; // output record array for 'out'. + unsigned midi_fld_idx; // pre-computed record field indexes + } inst_t; + rc_t _alloc_recd_array( proc_t* proc, const char* var_label, unsigned sfx_id, unsigned chIdx, const recd_type_t* base, recd_array_t*& recd_array_ref ) + { + rc_t rc = kOkRC; + variable_t* var = nullptr; + + // find the record variable + if((rc = var_find( proc, var_label, sfx_id, chIdx, var )) != kOkRC ) + { + rc = cwLogError(rc,"The record variable '%s:%i' could was not found.",cwStringNullGuard(var_label),sfx_id); + goto errLabel; + } + + // verify that the variable has a record format + if( !var_has_recd_format(var) ) + { + rc = cwLogError(kInvalidArgRC,"The variable does not have a valid record format."); + goto errLabel; + } + else + { + recd_fmt_t* recd_fmt = var->varDesc->fmt.recd_fmt; + + // create the recd_array + if((rc = recd_array_create( recd_array_ref, recd_fmt->recd_type, base, recd_fmt->alloc_cnt )) != kOkRC ) + { + goto errLabel; + } + } + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"Record array create failed on the variable '%s:%i ch:%i.",cwStringNullGuard(var_label),sfx_id,chIdx); + + return rc; + + } rc_t _create( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; const char* midi_fname = nullptr; const char* csv_fname = nullptr; + const char* csv_fname_2= nullptr; const midi::file::trackMsg_t** tmA = nullptr; unsigned msgAllocN = 0; bool done_fl = false; @@ -8093,13 +8318,30 @@ namespace cw time::setZero(asecs); if((rc = var_register_and_get(proc,kAnyChIdx, - kMidiFileNamePId, "fname", kBaseSfxId, midi_fname, - kCsvFileNamePId, "csv_fname", kBaseSfxId, csv_fname, - kDoneFlPId, "done_fl", kBaseSfxId, done_fl)) != kOkRC ) + kMidiFileNamePId, "fname", kBaseSfxId, midi_fname, + kCsvFileNamePId, "csv_fname", kBaseSfxId, csv_fname, + kCsvFileName2PId, "alt_csv_fname", kBaseSfxId, csv_fname_2, + kDoneFlPId, "done_fl", kBaseSfxId, done_fl)) != kOkRC ) { goto errLabel; } + if((rc = var_register(proc,kAnyChIdx, + kStartPId, "start", kBaseSfxId, + kStopPId, "stop", kBaseSfxId )) != kOkRC ) + { + goto errLabel; + } + + + if( csv_fname_2 != nullptr && textLength(csv_fname_2)>0 ) + if((p->csv_fname_2 = proc_expand_filename(proc,csv_fname_2)) == nullptr ) + { + rc = cwLogError(kInvalidArgRC,"The MIDI CSV 2 filename could not be formed."); + goto errLabel; + } + + if( csv_fname != nullptr && textLength(csv_fname)>0 ) if((p->csv_fname = proc_expand_filename(proc,csv_fname)) == nullptr ) { @@ -8128,9 +8370,15 @@ namespace cw goto errLabel; } else - { - rc = cwLogError(kOpenFailRC,"No MIDI or CSV filename was given."); - } + if( p->csv_fname_2 != nullptr && textLength(p->csv_fname_2)>0 ) + { + if((rc = midi::file::open_csv_2(p->mfH,p->csv_fname_2)) != kOkRC ) + goto errLabel; + } + else + { + rc = cwLogError(kOpenFailRC,"No MIDI or CSV filename was given."); + } } // create one output MIDI buffer @@ -8148,12 +8396,14 @@ namespace cw { const midi::file::trackMsg_t* tm = tmA[i]; msg_t* m = p->msgA + p->msg_idx; + double secs; m->m = p->chMsgA + p->msg_idx; time::microsecondsToSpec( m->m->timeStamp, tmA[i]->amicro ); - - m->sample_idx = (unsigned)(proc->ctx->sample_rate * time::specToSeconds(m->m->timeStamp)); + + m->sample_idx = (unsigned)(proc->ctx->sample_rate * (secs = time::specToSeconds(m->m->timeStamp))); + m->m->devIdx = 0; m->m->portIdx = 0; @@ -8174,6 +8424,18 @@ namespace cw p->msgN = p->msg_idx; p->msg_idx = 0; + + // allocate the output recd array + if((rc = _alloc_recd_array( proc, "r_out", kBaseSfxId, kAnyChIdx, nullptr, p->recd_array )) != kOkRC ) + { + goto errLabel; + } + + // create one output record buffer + rc = var_register_and_set( proc, "r_out", kBaseSfxId, kROutPId, kAnyChIdx, p->recd_array->type, nullptr, 0 ); + + p->midi_fld_idx = recd_type_field_index( p->recd_array->type, "midi"); + errLabel: return rc; @@ -8182,9 +8444,10 @@ namespace cw rc_t _destroy( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; - + recd_array_destroy(p->recd_array); mem::release(p->midi_fname); mem::release(p->csv_fname); + mem::release(p->csv_fname_2); close(p->mfH); return rc; @@ -8193,6 +8456,36 @@ namespace cw rc_t _notify( proc_t* proc, inst_t* p, variable_t* var ) { rc_t rc = kOkRC; + + switch( var->vid ) + { + case kStartPId: + p->start_trig_fl = true; + break; + + case kStopPId: + p->stop_trig_fl = true; + break; + } + + return rc; + } + + rc_t _set_output_record( inst_t* p, rbuf_t* rbuf, const midi::ch_msg_t* m ) + { + rc_t rc = kOkRC; + + // if the output record array is full + if( rbuf->recdN >= p->recd_array->allocRecdN ) + { + rc = cwLogError(kBufTooSmallRC,"The internal record buffer overflowed. (buf recd count:%i).",p->recd_array->allocRecdN); + goto errLabel; + } + + recd_set( rbuf->type, nullptr, p->recd_array->recdA + rbuf->recdN, p->midi_fld_idx, (midi::ch_msg_t*)m ); + rbuf->recdN += 1; + + errLabel: return rc; } @@ -8201,6 +8494,7 @@ namespace cw rc_t rc = kOkRC; mbuf_t* mbuf = nullptr; bool done_fl = false; + rbuf_t* rbuf = nullptr; p->sample_idx += proc->ctx->framesPerCycle; @@ -8227,6 +8521,22 @@ namespace cw } + // get the output variable + if((rc = var_get(proc,kROutPId,kAnyChIdx,rbuf)) != kOkRC ) + { + rc = cwLogError(kInvalidStateRC,"The midi-in '%s' does not have a valid output record buffer.",proc->label); + } + else + { + rbuf->recdA = p->recd_array->recdA; + rbuf->recdN = 0; + + for(unsigned i=0; imsgN; ++i) + _set_output_record(p,rbuf, mbuf->msgA + i); + + } + + if( done_fl ) var_set(proc, kDoneFlPId, kAnyChIdx, true ); diff --git a/cwFlowProc.h b/cwFlowProc.h index 06c4d7a..4777c05 100644 --- a/cwFlowProc.h +++ b/cwFlowProc.h @@ -65,6 +65,7 @@ namespace cw namespace xfade_ctl { extern class_members_t members; } namespace midi_voice { extern class_members_t members; } namespace piano_voice { extern class_members_t members; } + namespace voice_detector { extern class_members_t members; } namespace poly_voice_ctl { extern class_members_t members; } namespace sample_hold { extern class_members_t members; } namespace number { extern class_members_t members; } From f1b7f1a263ebc4dd0cc01082acb40912d5538cb4 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 21 Apr 2025 09:28:19 -0400 Subject: [PATCH 2/6] cwFlowPerrf.cpp : Initial score_follower_2 implementation. 'score_player' added 'max loc' output. 'vel_table' added 'score_vel' functionality to support output from score_follower. 'gutim_ps' added manual preset selection. Added 'per location' (see locA[]) and 'dry notes per chord' options. --- cwFlow.cpp | 2 + cwFlowPerf.cpp | 705 +++++++++++++++++++++++++++++++++++++++---------- cwFlowPerf.h | 11 +- 3 files changed, 578 insertions(+), 140 deletions(-) diff --git a/cwFlow.cpp b/cwFlow.cpp index b441c02..df05844 100644 --- a/cwFlow.cpp +++ b/cwFlow.cpp @@ -62,6 +62,7 @@ namespace cw { "poly_voice_ctl", &poly_voice_ctl::members }, { "midi_voice", &midi_voice::members }, { "piano_voice", &piano_voice::members }, + { "voice_detector", &voice_detector::members }, { "sample_hold", &sample_hold::members }, { "number", &number::members }, { "reg", ®::members }, @@ -82,6 +83,7 @@ namespace cw { "preset_select", &preset_select::members }, { "gutim_ps", &gutim_ps::members }, { "score_follower", &score_follower::members }, + { "score_follower_2",&score_follower_2::members }, { nullptr, nullptr } }; diff --git a/cwFlowPerf.cpp b/cwFlowPerf.cpp index b2bff1d..bfebb32 100644 --- a/cwFlowPerf.cpp +++ b/cwFlowPerf.cpp @@ -32,6 +32,9 @@ #include "cwScoreFollowerPerf.h" #include "cwScoreFollower.h" +#include "cwPianoScore.h" +#include "cwScoreFollow2.h" + #include "cwPianoScore.h" #include "cwPresetSel.h" @@ -55,6 +58,7 @@ namespace cw kScoreFNamePId, kStoppingMsPId, kDoneFlPId, + kMaxLocPId, kOutPId, kStartPId, kStopPId, @@ -130,6 +134,8 @@ namespace cw state_t state; // idle,play,stopping unsigned stopping_ms; // max time in milliseconds to wait for all notes to end before sending all-note-off unsigned stopping_sample_idx; // 0 if not 'stopping', otherwise the max sample index after which the player will enter 'idle' state. + + unsigned maxLocId; } inst_t; @@ -178,18 +184,27 @@ namespace cw goto errLabel; } + p->maxLocId = 0; 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 ) { + bool note_on_fl = false; msg_t* m = p->msgA + p->msgN; midi::ch_msg_t* mm = p->chMsgA + p->msgN; if( score_evt->loc != kInvalidId ) + { + // verify that the score is in order by location + assert( score_evt->loc >= last_loc ); last_loc = score_evt->loc; + } + + if( last_loc > p->maxLocId ) + p->maxLocId = last_loc; m->sample_idx = (unsigned)(proc->ctx->sample_rate * score_evt->sec); m->loc = last_loc; @@ -206,9 +221,11 @@ namespace cw time::fracSecondsToSpec( mm->timeStamp, score_evt->sec ); - mm->devIdx = kInvalidIdx; - mm->portIdx= kInvalidIdx; - mm->uid = uuid++; + note_on_fl = midi::isNoteOn(score_evt->status,score_evt->d1); + + mm->devIdx = note_on_fl ? m->loc : kInvalidIdx; //BUG BUG BUG: hack to do per chord/note processing in gutim_ps + mm->portIdx= note_on_fl ? score_evt->chord_note_idx : kInvalidIdx; + mm->uid = note_on_fl ? score_evt->chord_note_cnt : kInvalidId; mm->ch = score_evt->status & 0x0f; mm->status = score_evt->status & 0xf0; mm->d0 = score_evt->d0; @@ -216,6 +233,7 @@ namespace cw m->d1 = score_evt->d1; // track the initial d1 before vel. mapping is applied + if( midi::isSustainPedal( mm->status, mm->d0 ) ) { bool down_fl = pedalStateFlags & kDampPedalDownFl; @@ -417,7 +435,8 @@ namespace cw if((rc = var_register_and_set(proc,kAnyChIdx, - kDoneFlPId,"done_fl", kBaseSfxId, false)) != kOkRC ) + kDoneFlPId,"done_fl", kBaseSfxId, false, + kMaxLocPId,"loc_cnt", kBaseSfxId, p->maxLocId+1 )) != kOkRC ) { goto errLabel; } @@ -669,6 +688,11 @@ namespace cw } bool note_on_fl = midi::isNoteOn(m->midi->status, m->midi->d1); + + //if( note_on_fl ) + //{ + // printf("sc:%i %i %i %s\n",m->meas,m->loc,m->midi->d0,midi::midiToSciPitch(m->midi->d0)); + //} // fill the output record with this msg but filter out note-on's when in stopping-state if( p->state == kPlayStateId || (p->state==kStoppingStateId && note_on_fl==false) ) @@ -745,6 +769,7 @@ namespace cw vel_tbl_t* velTblL; vel_tbl_t* activeVelTbl; unsigned i_midi_fld_idx; + unsigned i_score_vel_fld_idx; unsigned o_midi_fld_idx; recd_array_t* recd_array; // output record array @@ -953,8 +978,10 @@ namespace cw p->midiN = p->recd_array->allocRecdN; p->midiA = mem::allocZ(p->midiN); - - + + // If the velocity table is being fed by the score follower then there may be a 'score_vel' field in the input record. + p->i_score_vel_fld_idx = recd_type_field_index( rbuf->type, "score_vel"); + errLabel: return rc; } @@ -998,13 +1025,13 @@ namespace cw if((rc = var_get(proc,kOutPId,kAnyChIdx,o_rbuf)) != kOkRC ) goto errLabel; - + // for each incoming record for(unsigned i=0; irecdN; ++i) { const recd_t* i_r = i_rbuf->recdA + i; const midi::ch_msg_t* i_m = nullptr; - + // verify that there is space in the output array if( i >= p->midiN || i >= p->recd_array->allocRecdN ) { @@ -1029,15 +1056,46 @@ namespace cw // if this is a note on if( midi::isNoteOn(i_m->status,i_m->d1) ) { - // and the velocity is valid - if( i_m->d1 >= p->activeVelTbl->tblN ) + + // if the 'score_vel' was not given + if( p->i_score_vel_fld_idx == kInvalidIdx ) { + // and the velocity is valid + if( i_m->d1 >= p->activeVelTbl->tblN ) + { rc = cwLogError(kInvalidArgRC,"The pre-mapped velocity value %i is outside of the range (%i) of the velocity table '%s'.",i_m->d1,p->activeVelTbl->tblN,cwStringNullGuard(p->activeVelTbl->label)); goto errLabel; + } + + // map the velocity through the active table + o_m->d1 = p->activeVelTbl->tblA[ i_m->d1 ]; } + else // ... a 'score_vel' exists + { + unsigned score_vel = -1; - // map the velocity through the active table - o_m->d1 = p->activeVelTbl->tblA[ i_m->d1 ]; + // get the score_vel + if((rc = recd_get(i_rbuf->type,i_r,p->i_score_vel_fld_idx,score_vel)) != kOkRC ) + { + rc = cwLogError(kOpFailRC,"'score_velocity access failed in velocity table."); + goto errLabel; + } + + // if the score_vel is valid (it won't be if this note was not tracked in the score) + if( score_vel != (unsigned)-1 ) + { + // verify that the 'score_vel' is inside the range of the table + if(score_vel >= p->activeVelTbl->tblN ) + { + rc = cwLogError(kInvalidArgRC,"The pre-mapped score velocity value %i is outside of the range (%i) of the velocity table '%s'.",score_vel,p->activeVelTbl->tblN,cwStringNullGuard(p->activeVelTbl->label)); + goto errLabel; + } + + // apply the score_vel to the map + o_m->d1 = p->activeVelTbl->tblA[ score_vel ]; + } + + } //printf("%i %i %s\n",i_m->d1,o_m->d1,p->activeVelTbl->label); } @@ -1261,10 +1319,15 @@ namespace cw kInitCfgPId, kPresetMapCfgPId, kFNamePId, + kLocCntPId, kInPId, kLocPId, kResetPId, + kPriManualSelPId, + kSecManualSelPId, kPerNoteFlPId, + kPerLocFlPId, + kDryChordFlPId, kPriProbFlPId, kPriUniformFlPId, @@ -1310,6 +1373,15 @@ namespace cw kCoeffPresetValTId, } value_tid_t; + typedef struct loc_str + { + unsigned pri_preset_idx; + unsigned sec_preset_idx; + unsigned note_cnt; + unsigned note_idx; + unsigned rand; + } loc_t; + typedef struct var_cfg_str { const char* var_label; // gutim_ps var label @@ -1391,6 +1463,8 @@ namespace cw coeff_t cur_interp_dist; bool per_note_fl; + bool per_loc_fl; + bool dry_chord_fl; bool pri_prob_fl; bool pri_uniform_fl; bool pri_dry_on_play_fl; @@ -1406,9 +1480,31 @@ namespace cw bool interp_fl; bool interp_rand_fl; + list_t* manual_sel_list; + unsigned cur_manual_pri_preset_idx; + unsigned cur_manual_sec_preset_idx; + + loc_t* locA; + unsigned locN; + unsigned dry_preset_idx; } inst_t; + void _init_loc_array( inst_t* p ) + { + if( p->locN > 0 && p->locA == nullptr ) + p->locA = mem::allocZ(p->locN); + + for(unsigned i=0; ilocN; ++i) + { + p->locA[i].pri_preset_idx = kInvalidIdx; + p->locA[i].sec_preset_idx = kInvalidIdx; + p->locA[i].note_cnt = 0; + p->locA[i].note_idx = kInvalidIdx; + p->locA[i].rand = rand(); + } + } + const char* _preset_index_to_label( inst_t* p, unsigned preset_idx ) { const char* label = ""; @@ -1418,6 +1514,46 @@ namespace cw label = _presetA[ preset_idx ].ps_label; return label; } + + rc_t _create_manual_select_list( proc_t* proc, inst_t* p ) + { + rc_t rc = kOkRC; + const char* var_labelA[] = { "pri_manual_sel", "sec_manual_sel" }; + const unsigned var_labelN = sizeof(var_labelA)/sizeof(var_labelA[0]); + + p->cur_manual_pri_preset_idx = kInvalidIdx; + p->cur_manual_sec_preset_idx = kInvalidIdx; + + // create the list of values for the 'manual_sel' variable + if((rc = list_create(p->manual_sel_list, p->presetN+1 )) != kOkRC ) + goto errLabel; + + if((rc = list_append(p->manual_sel_list,"auto",kInvalidIdx)) != kOkRC ) + goto errLabel; + + for(unsigned i=0; ipresetN; ++i) + if((rc = list_append( p->manual_sel_list, _preset_index_to_label(p,i), i)) != kOkRC ) + goto errLabel; + + + for(unsigned i=0; ivalue_list = p->manual_sel_list; + } + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"The 'gutim_ps' manual selection list create failed."); + return rc; + } template< typename T > rc_t _read_class_preset_value( proc_t* proc, preset_t* preset, var_cfg_t* var_cfg, unsigned ch_idx, T& val_ref ) @@ -1547,11 +1683,11 @@ namespace cw return rc; } - rc_t _apply_preset_no_interp(proc_t* proc, inst_t* p, unsigned voice_idx) + rc_t _apply_preset_no_interp(proc_t* proc, inst_t* p, unsigned voice_idx, unsigned preset_idx) { rc_t rc = kOkRC; - if( p->cur_pri_preset_idx == kInvalidIdx || p->cur_pri_preset_idx >= p->presetN ) + if( preset_idx == kInvalidIdx || preset_idx >= p->presetN ) { rc = cwLogError(kInvalidArgRC,"The primary preset is invalid."); goto errLabel; @@ -1561,11 +1697,11 @@ namespace cw { const var_cfg_t* var_cfg = _var_cfgA + var_idx; - assert( p->cur_pri_preset_idx < p->presetN ); + assert( preset_idx < p->presetN ); for(unsigned ch_idx=0; ch_idxpresetA[ p->cur_pri_preset_idx ].varA[ var_idx ].chA + ch_idx; + const preset_value_t* v = p->presetA[ preset_idx ].varA[ var_idx ].chA + ch_idx; variable_t* varb; var_find(proc, p->base[var_cfg->var_pid] + voice_idx,ch_idx, varb); @@ -1600,17 +1736,17 @@ namespace cw } - rc_t _apply_preset_with_interp(proc_t* proc, inst_t* p, unsigned voice_idx) + rc_t _apply_preset_with_interp(proc_t* proc, inst_t* p, unsigned voice_idx, unsigned pri_preset_idx, unsigned sec_preset_idx) { rc_t rc = kOkRC; - if( p->cur_pri_preset_idx == kInvalidIdx || p->cur_pri_preset_idx >= p->presetN ) + if( pri_preset_idx == kInvalidIdx || pri_preset_idx >= p->presetN ) { rc = cwLogError(kInvalidArgRC,"The primary preset is invalid."); goto errLabel; } - if( p->cur_sec_preset_idx == kInvalidIdx || p->cur_sec_preset_idx >= p->presetN ) + if( sec_preset_idx == kInvalidIdx || sec_preset_idx >= p->presetN ) { rc = cwLogError(kInvalidArgRC,"The secondary preset is invalid."); goto errLabel; @@ -1620,13 +1756,13 @@ namespace cw { const var_cfg_t* var_cfg = _var_cfgA + var_idx; - assert( p->cur_pri_preset_idx < p->presetN ); + assert( pri_preset_idx < p->presetN ); for(unsigned ch_idx=0; ch_idxpresetA[ p->cur_pri_preset_idx ].varA[ var_idx ].chA + ch_idx; - const preset_value_t* c1 = p->presetA[ p->cur_sec_preset_idx ].varA[ var_idx ].chA + ch_idx; + const preset_value_t* c0 = p->presetA[ pri_preset_idx ].varA[ var_idx ].chA + ch_idx; + const preset_value_t* c1 = p->presetA[ sec_preset_idx ].varA[ var_idx ].chA + ch_idx; switch( var_cfg->tid ) { @@ -1696,24 +1832,27 @@ namespace cw if( p->cur_sec_preset_idx != kInvalidIdx ) _report_preset( proc, p, "Sec", p->cur_sec_preset_idx ); } - - rc_t _apply_preset( proc_t* proc, inst_t* p, const midi::ch_msg_t* m, unsigned voice_idx ) + + // apply the preset assoc'd with p->cur_pri_preset_idx and p->cur_sec_preset_idx + rc_t _apply_preset( proc_t* proc, inst_t* p, const midi::ch_msg_t* m, unsigned voice_idx, unsigned pri_preset_idx, unsigned sec_preset_idx ) { rc_t rc = kOkRC; + pri_preset_idx = p->cur_manual_pri_preset_idx == kInvalidIdx ? pri_preset_idx : p->cur_manual_pri_preset_idx; + sec_preset_idx = p->cur_manual_sec_preset_idx == kInvalidIdx ? sec_preset_idx : p->cur_manual_sec_preset_idx; - if( p->cur_frag == nullptr || p->cur_pri_preset_idx == kInvalidIdx ) + if( pri_preset_idx == kInvalidIdx ) { rc = cwLogError(kInvalidStateRC,"No current preset has been selected."); goto errLabel; } - if( p->cur_sec_preset_idx == kInvalidIdx ) + if( !p->interp_fl || sec_preset_idx == kInvalidIdx ) { - rc = _apply_preset_no_interp(proc, p, voice_idx); + rc = _apply_preset_no_interp(proc, p, voice_idx, pri_preset_idx); } else { - rc = _apply_preset_with_interp(proc, p, voice_idx); + rc = _apply_preset_with_interp(proc, p, voice_idx, pri_preset_idx, sec_preset_idx); } errLabel: @@ -1795,13 +1934,20 @@ namespace cw const object_t* cfg = nullptr; bool resetFl = false; + p->dry_preset_idx = kInvalidIdx; + if((rc = var_register_and_get(proc,kAnyChIdx, - kInitCfgPId, "cfg", kBaseSfxId, cfg, // TODO: clean up the contents of this CFG - kInPId, "in", kBaseSfxId, rbuf, - kFNamePId, "fname", kBaseSfxId, fname, - kLocPId, "loc", kBaseSfxId, loc, - kResetPId, "reset", kBaseSfxId, resetFl, - kPerNoteFlPId, "per_note_fl",kBaseSfxId, p->per_note_fl, + kInitCfgPId, "cfg", kBaseSfxId, cfg, // TODO: clean up the contents of this CFG + kInPId, "in", kBaseSfxId, rbuf, + kFNamePId, "fname", kBaseSfxId, fname, + kLocCntPId, "loc_cnt", kBaseSfxId, p->locN, + kLocPId, "loc", kBaseSfxId, loc, + kResetPId, "reset", kBaseSfxId, resetFl, + kPriManualSelPId, "pri_manual_sel", kBaseSfxId, p->cur_manual_pri_preset_idx, + kSecManualSelPId, "sec_manual_sel", kBaseSfxId, p->cur_manual_sec_preset_idx, + kPerNoteFlPId, "per_note_fl", kBaseSfxId, p->per_note_fl, + kPerLocFlPId, "per_loc_fl", kBaseSfxId, p->per_loc_fl, + kDryChordFlPId, "dry_chord_fl", kBaseSfxId, p->dry_chord_fl, kPriProbFlPId, "pri_prob_fl", kBaseSfxId, p->pri_prob_fl, kPriUniformFlPId, "pri_uniform_fl", kBaseSfxId, p->pri_uniform_fl, @@ -1841,6 +1987,8 @@ namespace cw goto errLabel; } + p->dry_preset_idx = preset_sel::dry_preset_index(p->psH); + // read in the loc->preset map file if((rc = preset_sel::read(p->psH,exp_fname)) != kOkRC ) { @@ -1854,8 +2002,9 @@ namespace cw // The location is coming from a 'record', get the location field. if((p->loc_fld_idx = recd_type_field_index( rbuf->type, "loc")) == kInvalidIdx ) { - rc = cwLogError(kInvalidArgRC,"The 'in' record does not have a 'loc' field."); - goto errLabel; + cwLogWarning("The incoming record to the 'gutim_ps' object does not have a 'loc' field. Score tracking is disabled."); + //rc = cwLogError(kInvalidArgRC,"The 'in' record does not have a 'loc' field."); + //goto errLabel; } @@ -1895,9 +2044,17 @@ namespace cw p->psPresetCnt = preset_count(p->psH); // get the count of preset class (~13) // Get the values for all the presets required by the transform parameter variables - rc = _create_and_fill_preset_array( proc, p ); + if((rc = _create_and_fill_preset_array( proc, p )) != kOkRC ) + goto errLabel; + + + // create the 'manual_sel' list based on the available preset labels + if((rc = _create_manual_select_list(proc, p )) != kOkRC ) + goto errLabel; + + // initialize locA[] + _init_loc_array(p); - errLabel: mem::release(exp_fname); return rc; @@ -1907,24 +2064,88 @@ namespace cw { rc_t rc = kOkRC; - // Custom clean-up code goes here - + list_destroy(p->manual_sel_list); + mem::release(p->locA); + return rc; } rc_t _exec_note_on( proc_t* proc, inst_t* p, const midi::ch_msg_t* m, unsigned voice_idx ) { - rc_t rc = kOkRC; - bool per_note_fl = false; - - if( var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl) != kOkRC ) + rc_t rc = kOkRC; + bool per_note_fl = false; + bool per_loc_fl = false; + bool chord_dry_fl = false; + bool apply_dry_fl = false; + bool update_preset_fl = false; + unsigned loc_idx = kInvalidIdx; + unsigned pri_preset_idx = p->cur_pri_preset_idx; + unsigned sec_preset_idx = p->cur_sec_preset_idx; + + if((rc = var_get(proc,kPerLocFlPId,kAnyChIdx,per_loc_fl)) != kOkRC) goto errLabel; - if( per_note_fl ) - if((rc = _update_cur_preset_idx( proc, p, p->cur_frag )) != kOkRC ) - goto errLabel; + // if we are selecting a new preset per location + if( per_loc_fl && p->locN > 0 ) + { + + unsigned loc = m->devIdx; + unsigned note_idx = m->portIdx; + unsigned note_cnt = m->uid; + assert( loc < p->locN ); - rc = _apply_preset( proc, p, m, voice_idx ); + // if this is the first note received for this location + if( p->locA[ loc ].note_cnt == 0 ) + { + p->locA[ loc ].note_cnt = note_cnt; + loc_idx = loc; + update_preset_fl = true; + } + else // ... select the preset based on the preset previously picked for this location + { + pri_preset_idx = p->locA[ loc ].pri_preset_idx; + sec_preset_idx = p->locA[ loc ].sec_preset_idx; + } + + p->locA[ loc ].note_idx += 1; + + + var_get(proc,kDryChordFlPId,kAnyChIdx,chord_dry_fl); + + apply_dry_fl = chord_dry_fl && (((note_idx % 2)==0) == (p->locA[ loc ].rand > RAND_MAX/2)); + + } + else + { + if((rc = var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl)) != kOkRC ) + goto errLabel; + + // if we are selecting presets per note + if( per_note_fl ) + update_preset_fl = true; + } + + // if a new preset should be selected + if( update_preset_fl ) + { + if((rc = _update_cur_preset_idx( proc, p, p->cur_frag )) != kOkRC ) + goto errLabel; + + // if this is the first note for this 'loc' then cache the selected presets + if( loc_idx != kInvalidIdx ) + { + p->locA[ loc_idx ].pri_preset_idx = p->cur_pri_preset_idx; + p->locA[ loc_idx ].sec_preset_idx = p->cur_sec_preset_idx; + } + } + + if( apply_dry_fl ) + { + pri_preset_idx = p->dry_preset_idx; + sec_preset_idx = p->dry_preset_idx; + } + + rc = _apply_preset( proc, p, m, voice_idx, pri_preset_idx, sec_preset_idx ); errLabel: return rc; @@ -1956,15 +2177,16 @@ namespace cw return rc; } - rc_t _exec_on_new_location( proc_t* proc, inst_t* p ) + rc_t _exec_on_new_fragment( proc_t* proc, inst_t* p ) { rc_t rc = kOkRC; bool per_note_fl = false; - - if( var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl) != kOkRC ) - goto errLabel; - - if( !per_note_fl ) + bool per_loc_fl = false;; + var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl); + var_get(proc,kPerLocFlPId,kAnyChIdx,per_loc_fl); + + // if we are not assigning presets per note - then select p->cur_pri/sec_preset_idx for all following notes + if( per_note_fl == false && per_loc_fl == false ) if((rc = _update_cur_preset_idx( proc, p, p->cur_frag )) != kOkRC ) goto errLabel; @@ -1979,6 +2201,10 @@ namespace cw rbuf_t* in_rbuf = nullptr; unsigned loc = kInvalidIdx; + // if score tracking is disabled + if( p->loc_fld_idx == kInvalidIdx ) + goto errLabel; + if((rc = var_get(proc,kInPId,kAnyChIdx,in_rbuf)) != kOkRC) goto errLabel; @@ -2001,13 +2227,17 @@ namespace cw { const preset_sel::frag_t* frag = nullptr; - // lookup the fragment associated with the location + // if this location is associated with a new set of preset selections ... if( preset_sel::track_loc( p->psH, loc, frag ) && frag != nullptr ) { + // p->cur_frag maintains a reference to the preset selections p->cur_frag = frag; + + cwLogInfo("LOC:%i ",loc); + //cwLogPrint("LOC:%i ",loc); + //fragment_report( p->psH, frag ); - cwLogInfo("LOC:%i",loc); - rc = _exec_on_new_location(proc,p); + rc = _exec_on_new_fragment(proc,p); } } @@ -2015,90 +2245,27 @@ namespace cw return rc; } - /* - bool _update( proc_t* proc, unsigned vid, bool& fl_ref, rc_t rc_ref) - { - rc_t rc = kOkRC; - bool fl_value = false; - bool value_changed_fl = false; - - if((rc = var_get(proc, vid, kAnyChIdx, fl_value)) != kOkRC ) - { - rc_ref = rc; - return false; - } - - value_changed_fl = (fl_value != fl_ref); - - //if( vid == kPriProbFlPId ) - // printf("%i : %i %i %i\n",vid,fl_value,fl_ref,value_changed_fl); - - fl_ref = fl_value; - - return value_changed_fl; - } - - rc_t _exec_update_state( proc_t* proc, inst_t* p ) + rc_t _update_manual_preset_index( inst_t* p, variable_t* var, unsigned& preset_idx_ref ) { rc_t rc = kOkRC; - - if( _update( proc, kPriProbFlPId, p->pri_prob_fl, rc) ) - { - var_send_to_ui_enable(proc, kPriUniformFlPId, kAnyChIdx, p->pri_prob_fl ); - var_send_to_ui_enable(proc, kPriDryOnPlayFlPId, kAnyChIdx, p->pri_prob_fl ); - var_send_to_ui_enable(proc, kPriAllowAllFlPId, kAnyChIdx, p->pri_prob_fl ); - var_send_to_ui_enable(proc, kPriDryOnSelFlPId, kAnyChIdx, p->pri_prob_fl && p->pri_allow_all_fl ); - } - + unsigned list_idx; - - if( _update( proc, kPriAllowAllFlPId, p->pri_allow_all_fl, rc ) ) - { - var_send_to_ui_enable(proc, kPriDryOnSelFlPId, kAnyChIdx, p->pri_allow_all_fl ); - } + if((rc = var_get(var,list_idx)) != kOkRC ) + goto errLabel; - _update( proc, kSecUniformFlPId, p->pri_uniform_fl, rc); - _update( proc, kPriDryOnPlayFlPId, p->pri_dry_on_play_fl, rc); - _update( proc, kPriDryOnSelFlPId, p->pri_dry_on_sel_fl, rc); + + if((rc = list_ele_value(p->manual_sel_list,list_idx,preset_idx_ref)) != kOkRC ) + goto errLabel; - if( _update( proc, kInterpFlPId, p->interp_fl, rc ) ) - { - var_send_to_ui_enable(proc, kInterpRandFlPId, kAnyChIdx, p->interp_fl ); - var_send_to_ui_enable(proc, kInterpDistPId, kAnyChIdx, p->interp_fl & (!p->interp_rand_fl) ); - - var_send_to_ui_enable(proc, kSecProbFlPId, kAnyChIdx, p->interp_fl ); - var_send_to_ui_enable(proc, kSecUniformFlPId, kAnyChIdx, p->interp_fl ); - var_send_to_ui_enable(proc, kSecDryOnPlayFlPId, kAnyChIdx, p->interp_fl ); - var_send_to_ui_enable(proc, kSecAllowAllFlPId, kAnyChIdx, p->interp_fl ); - var_send_to_ui_enable(proc, kSecDryOnSelFlPId, kAnyChIdx, p->interp_fl && p->sec_allow_all_fl ); - } - - if( _update( proc, kInterpRandFlPId, p->interp_rand_fl, rc ) ) - { - var_send_to_ui_enable(proc, kInterpDistPId, kAnyChIdx, p->interp_fl & (!p->interp_rand_fl) ); - } + errLabel: + if( rc != kOkRC ) + preset_idx_ref = kInvalidIdx; - if( _update( proc, kSecProbFlPId, p->sec_prob_fl, rc) ) - { - var_send_to_ui_enable(proc, kSecUniformFlPId, kAnyChIdx, p->sec_prob_fl ); - var_send_to_ui_enable(proc, kSecDryOnPlayFlPId, kAnyChIdx, p->sec_prob_fl ); - var_send_to_ui_enable(proc, kSecAllowAllFlPId, kAnyChIdx, p->sec_prob_fl ); - var_send_to_ui_enable(proc, kSecDryOnSelFlPId, kAnyChIdx, p->sec_prob_fl && p->sec_allow_all_fl ); - } - - if( _update( proc, kSecAllowAllFlPId, p->sec_allow_all_fl, rc ) ) - { - var_send_to_ui_enable(proc, kSecDryOnSelFlPId, kAnyChIdx, p->sec_allow_all_fl ); - } - - _update( proc, kSecUniformFlPId, p->sec_uniform_fl, rc); - _update( proc, kSecDryOnPlayFlPId, p->sec_dry_on_play_fl, rc); - _update( proc, kSecDryOnSelFlPId, p->sec_dry_on_sel_fl, rc); return rc; } - */ + rc_t _update_ui_state( proc_t* proc, inst_t* p, variable_t* var ) { @@ -2110,6 +2277,16 @@ namespace cw switch(var->vid) { + case kPriManualSelPId: + if((rc = _update_manual_preset_index(p,var,p->cur_manual_pri_preset_idx)) != kOkRC ) + rc = cwLogError(rc,"Manual primary selected preset index update failed."); + break; + + case kSecManualSelPId: + if((rc = _update_manual_preset_index(p,var,p->cur_manual_sec_preset_idx)) != kOkRC ) + rc = cwLogError(rc,"Manual secondary selected preset index update failed."); + break; + case kPriProbFlPId: var_get(var,p->pri_prob_fl); var_send_to_ui_enable(proc, kPriUniformFlPId, kAnyChIdx, p->pri_prob_fl ); @@ -2188,8 +2365,6 @@ namespace cw { rc_t rc = kOkRC; - //if((rc = _exec_update_state(proc, p )) != kOkRC ) - // goto errLabel; _update_ui_state( proc, p, var ); if( var->vid == kResetPId ) @@ -2197,6 +2372,7 @@ namespace cw if( p->psH.isValid() ) track_loc_reset( p->psH); + _init_loc_array(p); } //errLabel: @@ -2430,7 +2606,266 @@ namespace cw } // score_follower - + + //------------------------------------------------------------------------------------------------------------------ + // + // Score Follower 2 + // + namespace score_follower_2 + { + + enum + { + kInPId, + kFnamePId, + kBegLocPId, + kEndLocPId, + kResetTrigPId, + kPrintFlPId, + kOutPId, + }; + + typedef struct + { + cw::perf_score::handle_t scoreH; + cw::score_follow_2::handle_t sfH; + unsigned i_midi_field_idx; + unsigned o_midi_field_idx; + unsigned loc_field_idx; + unsigned vel_field_idx; + recd_array_t* recd_array; // output record array + + } inst_t; + + + rc_t _alloc_recd_array( proc_t* proc, const char* var_label, unsigned sfx_id, unsigned chIdx, const recd_type_t* base, recd_array_t*& recd_array_ref ) + { + rc_t rc = kOkRC; + variable_t* var = nullptr; + + // find the record variable + if((rc = var_find( proc, var_label, sfx_id, chIdx, var )) != kOkRC ) + { + rc = cwLogError(rc,"The record variable '%s:%i' could was not found.",cwStringNullGuard(var_label),sfx_id); + goto errLabel; + } + + // verify that the variable has a record format + if( !var_has_recd_format(var) ) + { + rc = cwLogError(kInvalidArgRC,"The variable does not have a valid record format."); + goto errLabel; + } + else + { + recd_fmt_t* recd_fmt = var->varDesc->fmt.recd_fmt; + + // create the recd_array + if((rc = recd_array_create( recd_array_ref, recd_fmt->recd_type, base, recd_fmt->alloc_cnt )) != kOkRC ) + { + goto errLabel; + } + } + + errLabel: + if( rc != kOkRC ) + rc = cwLogError(rc,"Record array create failed on the variable '%s:%i ch:%i.",cwStringNullGuard(var_label),sfx_id,chIdx); + + return rc; + + } + + rc_t _create( proc_t* proc, inst_t* p ) + { + rc_t rc = kOkRC; + rbuf_t* in_rbuf = nullptr; + const char* c_score_fname = nullptr; + char* score_fname = nullptr; + bool printParseWarningsFl = true; + unsigned beg_loc_id = kInvalidId; + unsigned end_loc_id = kInvalidId; + bool reset_trig_fl = false; + cw::score_follow_2::args_t sf_args = { + .pre_affinity_sec = 1.0, + .post_affinity_sec = 3.0, + .pre_wnd_sec = 2.0, + .post_wnd_sec = 5.0, + .decay_coeff = 0.995, + .d_sec_err_thresh_lo = 0.4, + .d_loc_thresh_lo = 3, + .d_sec_err_thresh_hi = 1.5, + .d_loc_thresh_hi = 4, + .d_loc_stats_thresh = 3, + .rpt_fl = true + }; + + if((rc = var_register_and_get(proc,kAnyChIdx, + kInPId, "in", kBaseSfxId, in_rbuf, + kFnamePId, "score_fname", kBaseSfxId, c_score_fname, + kBegLocPId, "b_loc", kBaseSfxId, beg_loc_id, + kEndLocPId, "e_loc", kBaseSfxId, end_loc_id, + kResetTrigPId, "reset_trigger", kBaseSfxId, reset_trig_fl, + kPrintFlPId, "print_fl", kBaseSfxId, sf_args.rpt_fl )) != kOkRC ) + { + goto errLabel; + } + if((score_fname = proc_expand_filename( proc, c_score_fname )) == nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable to expand the score filename '%s'.",cwStringNullGuard(c_score_fname)); + goto errLabel; + } + + // create the SF score + if((rc = create( p->scoreH, score_fname)) != kOkRC ) + { + rc = cwLogError(rc,"SF Score create failed."); + goto errLabel; + } + + // create the score follower + if((rc = create( p->sfH, sf_args, p->scoreH )) != kOkRC ) + { + rc = cwLogError(rc,"Score follower create failed."); + goto errLabel; + } + + if((rc = reset( p->sfH, beg_loc_id, end_loc_id )) != kOkRC ) + { + rc = cwLogError(rc,"Score follower reset failed."); + goto errLabel; + } + + // create the output recd_array using the 'in' record type as the base type + if((rc = _alloc_recd_array( proc, "out", kBaseSfxId, kAnyChIdx, in_rbuf->type, p->recd_array )) != kOkRC ) + { + goto errLabel; + } + + // create one output record buffer + rc = var_register_and_set( proc, "out", kBaseSfxId, kOutPId, kAnyChIdx, p->recd_array->type, nullptr, 0 ); + + p->i_midi_field_idx = recd_type_field_index( in_rbuf->type, "midi"); + p->o_midi_field_idx = recd_type_field_index( p->recd_array->type, "midi"); + p->loc_field_idx = recd_type_field_index( p->recd_array->type, "loc"); + p->vel_field_idx = recd_type_field_index( p->recd_array->type, "score_vel"); + + errLabel: + mem::release(score_fname); + return rc; + } + + rc_t _destroy( proc_t* proc, inst_t* p ) + { + rc_t rc = kOkRC; + + recd_array_destroy(p->recd_array); + destroy(p->sfH); + destroy(p->scoreH); + + return rc; + } + + rc_t _notify( proc_t* proc, inst_t* p, variable_t* var ) + { + rc_t rc = kOkRC; + return rc; + } + + + rc_t _set_output_record( inst_t* p, rbuf_t* rbuf, const recd_t* base, unsigned loc_id, unsigned vel ) + { + rc_t rc = kOkRC; + + recd_t* r = p->recd_array->recdA + rbuf->recdN; + + // if the output record array is full + if( rbuf->recdN >= p->recd_array->allocRecdN ) + { + rc = cwLogError(kBufTooSmallRC,"The internal record buffer overflowed. (buf recd count:%i).",p->recd_array->allocRecdN); + goto errLabel; + } + + recd_set( rbuf->type, base, r, p->loc_field_idx, loc_id ); + recd_set( rbuf->type, base, r, p->vel_field_idx, vel ); + rbuf->recdN += 1; + + errLabel: + 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; + const rbuf_t* i_rbuf = nullptr; + rbuf_t* o_rbuf = nullptr; + unsigned result_recd_idx = kInvalidIdx; + + if((rc = var_get(proc,kInPId,kAnyChIdx,i_rbuf)) != kOkRC ) + goto errLabel; + + if((rc = var_get(proc,kOutPId,kAnyChIdx,o_rbuf)) != kOkRC ) + goto errLabel; + + o_rbuf->recdA = p->recd_array->recdA; + o_rbuf->recdN = 0; + + // for each incoming record + for(unsigned i=0; irecdN; ++i) + { + midi::ch_msg_t* m = nullptr; + unsigned loc_id = kInvalidId; + unsigned score_vel = -1; + + if((rc = recd_get( i_rbuf->type, i_rbuf->recdA+i, p->i_midi_field_idx, m)) != kOkRC ) + { + rc = cwLogError(rc,"The 'midi' field read failed."); + goto errLabel; + } + + if( midi::isNoteOn( m->status, m->d1 ) ) + { + + if((rc = on_new_note( p->sfH, m->uid, sec, m->d0, m->d1, loc_id, score_vel )) != kOkRC ) + { + rc = cwLogError(rc,"Score follower note processing failed."); + goto errLabel; + } + + if( loc_id != kInvalidId ) + { + } + + + + + } + + _set_output_record( p, o_rbuf, i_rbuf->recdA+i, loc_id, score_vel ); + + } + + do_exec(p->sfH); + + errLabel: + return rc; + } + + rc_t _report( proc_t* proc, inst_t* p ) + { return kOkRC; } + + class_members_t members = { + .create = std_create, + .destroy = std_destroy, + .notify = std_notify, + .exec = std_exec, + .report = std_report + }; + + } // score_follower_2 + } // flow } //cw diff --git a/cwFlowPerf.h b/cwFlowPerf.h index c8b6d91..6107221 100644 --- a/cwFlowPerf.h +++ b/cwFlowPerf.h @@ -2,11 +2,12 @@ namespace cw { namespace flow { - namespace score_player { extern class_members_t members; } - namespace vel_table { extern class_members_t members; } - namespace preset_select { extern class_members_t members; } - namespace gutim_ps { extern class_members_t members; } - namespace score_follower { extern class_members_t members; } + namespace score_player { extern class_members_t members; } + namespace vel_table { extern class_members_t members; } + namespace preset_select { extern class_members_t members; } + namespace gutim_ps { extern class_members_t members; } + namespace score_follower { extern class_members_t members; } + namespace score_follower_2 { extern class_members_t members; } } } From 4558a2fcd76c07e662eba4eca87218e70dce27bb Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 21 Apr 2025 09:29:22 -0400 Subject: [PATCH 3/6] cwMidiFile.h/cpp : Added open_csv_2() to handle reading 'workshop' CSV files. --- cwMidiFile.cpp | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ cwMidiFile.h | 3 ++ 2 files changed, 108 insertions(+) diff --git a/cwMidiFile.cpp b/cwMidiFile.cpp index 40736b9..1308ae9 100644 --- a/cwMidiFile.cpp +++ b/cwMidiFile.cpp @@ -1436,6 +1436,110 @@ errLabel: return rc; } +cw::rc_t cw::midi::file::open_csv_2( handle_t& hRef, const char* midi_csv_fname ) +{ + rc_t rc = kOkRC; + file_t* p = nullptr; + csv::handle_t csvH; + const char* titleA[] = { "UID","trk","dtick","atick","amicro","type","ch","D0","D1" }; + unsigned titleN = sizeof(titleA)/sizeof(titleA[0]); + unsigned line_idx = 0; + unsigned lineN = 0; + unsigned trkN = 1; + unsigned trkIdx = 0; + unsigned TpQN = 1260; + unsigned BpM = 120; + + if((rc = _create(hRef)) != kOkRC ) + goto errLabel; + + if((p = _handleToPtr(hRef)) == nullptr ) + goto errLabel; + + _init( p, trkN, TpQN ); + + + if((rc = csv::create(csvH,midi_csv_fname,titleA,titleN)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI CSV file open failed."); + goto errLabel; + } + + if((rc = line_count(csvH,lineN)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI CSV line count access failed."); + goto errLabel; + } + + for(; (rc = next_line(csvH)) == kOkRC; ++line_idx ) + { + unsigned uid; + unsigned amicro; + unsigned dtick; + unsigned atick; + const char* type = nullptr; + unsigned ch; + unsigned d0; + unsigned d1; + uint8_t status = 0; + + if((rc = getv(csvH,"UID",uid,"dtick",dtick,"atick",atick,"amicro",amicro,"type",type,"ch",ch,"D0",d0,"D1",d1)) != kOkRC ) + { + cwLogError(rc,"Error reading CSV line %i.",line_idx+1); + goto errLabel; + } + + if(textIsEqual(type,"non")) + status = midi::kNoteOnMdId; + else + if(textIsEqual(type,"nof")) + status = midi::kNoteOffMdId; + else + if(textIsEqual(type,"ctl")) + status = midi::kCtlMdId; + else + if(textIsEqual(type,"tempo")) + { + // note the tempo is taken from the 'ch' column + if((rc = insertTrackTempoMsg(hRef, trkIdx, atick, ch )) != kOkRC ) + { + rc = cwLogError(rc,"BPM insert failed."); + goto errLabel; + } + + continue; + + } + else + { + rc = cwLogError(kSyntaxErrorRC,"Unknown message type:'%s'.",cwStringNullGuard(type)); + goto errLabel; + } + + + if( status != 0 ) + { + assert( ch<=15 && d0 <= 127 && d1 <= 127 ); + + if((rc = insertTrackChMsg(hRef, trkIdx, atick, ch+status, d0, d1 )) != kOkRC ) + { + rc = cwLogError(rc,"Channel msg insert failed."); + goto errLabel; + } + + } + } + +errLabel: + destroy(csvH); + + if( rc == kEofRC ) + rc = kOkRC; + + if( rc != kOkRC ) + rc = cwLogError(rc,"MIDI csv file parse failed on '%s'.",cwStringNullGuard(midi_csv_fname)); + return rc; +} cw::rc_t cw::midi::file::create( handle_t& hRef, unsigned trkN, unsigned ticksPerQN ) { @@ -1730,6 +1834,7 @@ cw::rc_t cw::midi::file::insertTrackMsg( handle_t h, unsigned trkIdx, const tra // fill the track record m->uid = p->nextUid++; m->atick = msg->atick; + m->amicro = msg->amicro; m->status = msg->status; m->metaId = msg->metaId; m->trkIdx = trkIdx; diff --git a/cwMidiFile.h b/cwMidiFile.h index 41df4c4..75bcd52 100644 --- a/cwMidiFile.h +++ b/cwMidiFile.h @@ -121,6 +121,9 @@ namespace cw // tpQN = ticks per quarter note should be given on the first line. (Defaults to 1260). // bpm = beats per minute should be given on the first line. (Defaults to 60). rc_t open_csv( handle_t& hRef, const char* csv_fname ); + + // Open MIDI CSV as generated by 'Workshop' recordings. + rc_t open_csv_2( handle_t& hRef, const char* csv_fname ); // Create an empty MIDI file object. rc_t create( handle_t& hRef, unsigned trkN, unsigned ticksPerQN ); From c292c009e93da263e35ee271bfa9d543b8a46fed Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 21 Apr 2025 09:31:05 -0400 Subject: [PATCH 4/6] cwScoreFollow2.h/cpp,cwScoreFollow2Test.cpp : on_new_note() now returns score velocity. --- cwScoreFollow2.cpp | 19 +++++++++++-------- cwScoreFollow2.h | 16 +++++++--------- cwScoreFollow2Test.cpp | 11 +++++------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cwScoreFollow2.cpp b/cwScoreFollow2.cpp index 282fc76..2ba0a8e 100644 --- a/cwScoreFollow2.cpp +++ b/cwScoreFollow2.cpp @@ -239,7 +239,7 @@ namespace cw } - rc_t _trkr_on_new_note( trkr_t* trk, double sec, unsigned pitch, unsigned vel, bool rpt_fl, unsigned& matched_loc_id_ref ) + rc_t _trkr_on_new_note( trkr_t* trk, double sec, unsigned pitch, unsigned vel, bool rpt_fl, unsigned& matched_loc_id_ref, unsigned& score_vel_ref ) { rc_t rc = kOkRC; double d_corr_sec = 0.0; @@ -257,6 +257,7 @@ namespace cw unsigned match_ni = kInvalidIdx; double match_val = 0; + score_vel_ref = -1; matched_loc_id_ref = kInvalidId; assert( trk->exp_loc_idx != kInvalidIdx && trk->exp_loc_idx < trk->sf->locN ); @@ -362,6 +363,7 @@ namespace cw trk->loc_match_cntA[ match_loc_idx ] += 1; trk->note_match_cntA[ match_ni ] += 1; + score_vel_ref = trk->sf->noteA[ match_ni ].vel; matched_loc_id_ref = match_loc_id; // notice if we arrived at the end of the score tracking range @@ -805,7 +807,7 @@ errLabel: return rc; } -cw::rc_t cw::score_follow_2::create( handle_t& hRef, const args_t& args ) +cw::rc_t cw::score_follow_2::create( handle_t& hRef, const args_t& args, perf_score::handle_t scoreH ) { rc_t rc; if((rc = destroy(hRef)) != kOkRC ) @@ -813,10 +815,10 @@ cw::rc_t cw::score_follow_2::create( handle_t& hRef, const args_t& args ) sf_t* p = mem::allocZ(); - if((rc = _get_loc_and_note_count( p, args.scoreH )) != kOkRC ) + if((rc = _get_loc_and_note_count( p, scoreH )) != kOkRC ) goto errLabel; - if((rc = _alloc_fill_loc_and_note_arrays( p, args.scoreH )) != kOkRC ) + if((rc = _alloc_fill_loc_and_note_arrays( p, scoreH )) != kOkRC ) goto errLabel; if((rc = _alloc_and_fill_loc_note_arrays( p )) != kOkRC ) @@ -894,18 +896,19 @@ cw::rc_t cw::score_follow_2::reset( handle_t h, unsigned beg_loc_id, unsigned en _trkr_reset(p->trk,beg_loc_id); + cwLogInfo("SF2 reset: %i %i",beg_loc_id,end_loc_id); + errLabel: return rc; } -cw::rc_t cw::score_follow_2::on_new_note( handle_t h, unsigned uid, double sec, uint8_t pitch, uint8_t vel, unsigned& loc_id ) +cw::rc_t cw::score_follow_2::on_new_note( handle_t h, unsigned uid, double sec, uint8_t pitch, uint8_t vel, unsigned& matched_loc_id_ref, unsigned& score_vel_ref ) { rc_t rc = kOkRC; sf_t* p = _handleToPtr(h); - unsigned matched_loc_id = kInvalidId; - _trkr_on_new_note(p->trk,sec,pitch,vel, p->args.rpt_fl, matched_loc_id); + _trkr_on_new_note(p->trk,sec,pitch,vel, p->args.rpt_fl, matched_loc_id_ref, score_vel_ref); if( p->resultN < p->resultAllocN ) { @@ -913,7 +916,7 @@ cw::rc_t cw::score_follow_2::on_new_note( handle_t h, unsigned uid, double sec, r->perf_uid = uid; r->perf_pitch = pitch; r->perf_vel = vel; - r->match_loc_id = matched_loc_id; + r->match_loc_id = matched_loc_id_ref; p->resultN += 1; } diff --git a/cwScoreFollow2.h b/cwScoreFollow2.h index e1d1e7a..74f215c 100644 --- a/cwScoreFollow2.h +++ b/cwScoreFollow2.h @@ -6,22 +6,20 @@ namespace cw typedef handle< struct sf_str > handle_t; typedef struct args_str - { - perf_score::handle_t scoreH; - + { double pre_affinity_sec; // 1.0 look back affinity duration double post_affinity_sec; // 3.0 look forward affinity duration double pre_wnd_sec; // 2.0 look back search window double post_wnd_sec; // 5.0 look forward search window - double decay_coeff; // 0.995affinity decay coeff + double decay_coeff; // 0.995 affinity decay coeff double d_sec_err_thresh_lo; // 0.4 reject if d_loc > d_loc_thresh_lod and d_time > d_time_thresh_lo - int d_loc_thresh_lo; // 3 + int d_loc_thresh_lo; // 3 double d_sec_err_thresh_hi; // 1.5 reject if d_loc != 0 and d_time > d_time_thresh_hi - int d_loc_thresh_hi; // 4 reject if d_loc > d_loc_thresh_hi - int d_loc_stats_thresh; // 3 reject for time stats updates if d_loc > d_loc_stats_thresh + int d_loc_thresh_hi; // 4 reject if d_loc > d_loc_thresh_hi + int d_loc_stats_thresh; // 3 reject for time stats updates if d_loc > d_loc_stats_thresh bool rpt_fl; // set to turn on debug reporting @@ -29,13 +27,13 @@ namespace cw rc_t parse_args( const object_t* cfg, args_t& args ); - rc_t create( handle_t& hRef, const args_t& args ); + rc_t create( handle_t& hRef, const args_t& args, perf_score::handle_t scoreH ); rc_t destroy( handle_t& hRef ); rc_t reset( handle_t h, unsigned beg_loc_id, unsigned end_loc_id ); - rc_t on_new_note( handle_t h, unsigned uid, double sec, uint8_t pitch, uint8_t vel, unsigned& loc_id ); + rc_t on_new_note( handle_t h, unsigned uid, double sec, uint8_t pitch, uint8_t vel, unsigned& loc_id_ref, unsigned& score_vel_ref ); // Decay the affinity window and if necessary trigger a cycle of async background processing rc_t do_exec( handle_t h ); diff --git a/cwScoreFollow2Test.cpp b/cwScoreFollow2Test.cpp index 6e0e318..adaa4e4 100644 --- a/cwScoreFollow2Test.cpp +++ b/cwScoreFollow2Test.cpp @@ -138,10 +138,8 @@ namespace cw //cwLogInfo("Following: (%i notes found) sample count:%i %s.",mf.evtN,mf.sampleN,midi_csv_fname); - sf_args.scoreH = scoreH; - // create the score-follower - if((rc = score_follow_2::create(sfH,sf_args)) != kOkRC ) + if((rc = score_follow_2::create(sfH,sf_args,scoreH)) != kOkRC ) { rc = cwLogError(rc,"Score follower create failed."); goto errLabel; @@ -159,11 +157,12 @@ namespace cw while( smp_idx <= mf.evtA[midi_evt_idx].sample_idx && mf.evtA[midi_evt_idx].sample_idx < smp_idx + smp_per_cycle ) { const midi_evt_t* e = mf.evtA + midi_evt_idx++; - unsigned loc_id; - + unsigned loc_id = kInvalidId; + unsigned score_vel = -1; + //printf("%f pitch:%i vel:%i\n",e->sec,e->pitch,e->vel); - if((rc = on_new_note( sfH, e->uid, e->sec, e->pitch, e->vel, loc_id )) != kOkRC ) + if((rc = on_new_note( sfH, e->uid, e->sec, e->pitch, e->vel, loc_id, score_vel )) != kOkRC ) { rc = cwLogError(rc,"SF2 note processing failed on note UID:%i.",e->uid); goto errLabel; From ec40706e2b211a11f4e0667e27683d8544c2c626 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 21 Apr 2025 09:32:37 -0400 Subject: [PATCH 5/6] flow/proc_dict.cfg : Added score_follow2 and voice_detector Many updates to support changes to other proc's. --- flow/proc_dict.cfg | 89 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/flow/proc_dict.cfg b/flow/proc_dict.cfg index a13e8dc..a3144eb 100644 --- a/flow/proc_dict.cfg +++ b/flow/proc_dict.cfg @@ -44,6 +44,7 @@ in: { type:midi, doc:"MIDI messages to send."}, rin: { type:record, fmt:{ required:["midi"]}, doc:"Record input. (must have 'midi' field)"}, print_fl: { type:bool, value:false, doc:"Print the output to the console."} + enable_fl: { type:bool, value:true, doc:"Enable the output port."} } } @@ -675,7 +676,7 @@ in: { type:uint, flags:["notify","src"], doc:"List selection index." }, list: { type:cfg, flags:["init"], value:{} doc:"List as a 'cfg' object." }, out: { type:runtime, doc:"List output value." }, - value: { type:runtime, flags:["mult"], doc:"List 'mult' output per list value." }, + value: { type:runtime, flags:["mult"], doc:"List 'mult' output per list value." }, } } @@ -735,7 +736,8 @@ in: { type:record, fmt:{ required:["midi"]} doc:"MIDI input."}, voice_cnt: { type:uint, value:3, flags:["init"], doc:"Count of voices." }, out: { type:midi, flags:["mult"], doc:"MIDI output to voices. One per voice." }, - done_fl: { type:bool, value:false, flags:["mult"], doc:"Voice available feedback triggers from voices. One per voice."}, + gate_fl: { type:bool, flags:["mult"], doc:"Output: Per voice. Goes true with note-on, false when done_fl is set." }, + done_fl: { type:bool, value:false, flags:["mult"], doc:"Input: Voice available feedback triggers from voices. One per voice."}, } }, @@ -743,11 +745,11 @@ piano_voice: { vars: { wtb_fname: { type:string, flags:["init"], doc:"Wave table bank cfg. file." }, - wtb_instr: { type:string, value:"piano", flags:["init"], doc:"Instrument label of the selected wave-table bank."}, - in: { type:midi, doc:"MIDI in" }, - out: { type:audio, doc:"Audio out" }, - done_fl: { type:bool, value:false, doc:"Triggers when voice is available."}, - gate_fl: { type:bool, value:false, doc:"True when voice is active, false when inactive." }, + wtb_instr: { type:string, value:"piano", flags:["init"], doc:"Instrument label of the selected wave-table bank."}, + in: { type:midi, doc:"MIDI in" }, + out: { type:audio, doc:"Audio out" }, + done_fl: { type:bool, value:false, doc:"Output: True when voice is available, false when active."}, + gate_fl: { type:bool, value:false, doc:"Output: True when voice is active, false when inactive." }, rls_coeff: { type:coeff, value:0.9, doc:"Release decay factor. Increase for longer decays."}, rls_thresh:{ type:coeff, value:0.01,doc:"Note off threshold. Decrease for longer decays."}, @@ -758,6 +760,15 @@ } }, + voice_detector: { + vars: { + in: { type:audio, doc:"Audio in" }, + enable_fl: { type:bool, flags:[notify], value:false, doc:"Set when this voice detector is enabled." }, + rls_thresh: { type:coeff, flags:[notify], value:0.01, doc:"Voice off threshold." }, + done_fl: { type:bool, value:true, doc:"Output: Set when the voice is inactive, cleared when it is active."} + } + }, + print: { vars: { @@ -797,10 +808,23 @@ "bpm = beats per minute should be given on the first line. (Defaults to 60)." ] vars: { - fname: { type:string, flags:["init"], value:"", doc:"MIDI file name." }, - csv_fname: { type:string, flags:["init"], value:"", doc:"MIDI CSV fname. See: midi::file open_csv()." }, - done_fl: { type:bool, value:false, doc:"Emits true on done." }, - out: { type:midi, doc:"MIDI output."} + fname: { type:string, flags:["init"], value:"", doc:"MIDI file name." }, + csv_fname: { type:string, flags:["init"], value:"", doc:"MIDI CSV fname. See: midi::file open_csv()." }, + alt_csv_fname: { type:string, flags:["init"], value:"", doc:"MIDI CSV fname. See: midi::file open_csv_2()." }, + start: { type:all, flags:["notify"], value:false, doc:"Start playback" }, + stop: { type:all, flags:["notify"], value:false, doc:"Stop playback" }, + done_fl: { type:bool, value:false, doc:"Emits true on done." }, + out: { type:midi, doc:"MIDI output."}, + r_out: { type:record, doc:"MIDI output as records.", + fmt: { + alloc_cnt:1024, + fields: { + midi: { type:m3, doc:"MIDI channel event message" }, + } + } + } + + } } @@ -838,6 +862,7 @@ b_meas: { type:uint, flags:["notify"], value:0, doc:"Score begin measure." }, e_meas: { type:uint, flags:["notify"], value:0, doc:"Score end measure." }, done_fl: { type:bool, value:false, doc:"Emits true on done." }, + loc_cnt: { type:uint, value:0, doc:"Output: Value of max 'loc'."}, out: { type:record, doc:"Score event record.", fmt: { fields: { @@ -851,7 +876,9 @@ }, vel_table: { - doc:[ "Remap MIDI velocity values."] + doc:[ "Remap MIDI velocity values." + "If the incoming record has a 'score_vel' field then this is taken as the source velocity to be mapped and the MIDI message velocity is ignored." + ] vars: { vel_tbl_fname:{ type:string, flags:["init"], value:"", doc:"Velocity table filename as create by vwVelTableTuner." }, vel_tbl_label:{ type:string, flags:["init"], value:"", doc:"Name of the active velocity table referenced by 'vel_fname'."}, @@ -879,6 +906,26 @@ } } + score_follower_2: { + doc:[ "MIDI score follower: Sets the 'loc' field of the output record according to the score location." ] + vars: { + in: { type:record, fmt:{ required:["midi"]} doc:"Input record with 'midi' and 'loc' fields." }, + score_fname: { type:string, flags:["init"], value:"", doc:"Score file with location information." }, + b_loc: { type:uint, value:0, doc:"Score begin location." }, + e_loc: { type:uint, value:0, doc:"Score end location." }, + reset_trigger { type:all, flags:["notify"], value:false, doc:"Reset the score follower." }, + print_fl: { type:bool, flags:["init"], value:false, doc:"Set to print log of score follower state." }, + out:{ type:record, doc:"Pass-through of incoming MIDI with score location and score velocity." + fmt: { + alloc_cnt:1024, // internal recd_array size + fields: { + loc: { type:uint, value:-1, doc:"Score location id or if score track failed." } , + score_vel: { type:uint, value:-1, doc:"Score mapped velocity or -1 if 'loc' is -1." } + } + } + + } + } preset_select: { doc:[ "Given a score location emit a preset label."], @@ -920,12 +967,18 @@ gutim_ps: { doc:[ "Given score location and MIDI note messages emit transform parameters."], vars: { - cfg: { type:cfg, flags:["init"], doc:"Initial preset configuration." }, - fname: { type:string, flags:["init"], value:"", doc:"Preset file name."}, - in: { type:record, fmt:{ required:["loc"] }, doc:"Input record with 'loc' field." }, - loc: { type:uint, value:0, doc:"Seek to this location." }, - reset: { type:bool, flags:["notify"], value:false, doc:"Reset to initial state."}, - per_note_fl: { type:bool, value:false, doc:"Update the selected preset on every note, otherwise update on new location values." }, + cfg: { type:cfg, flags:["init"], doc:"Initial preset configuration." }, + fname: { type:string, flags:["init"], value:"", doc:"Preset file name."}, + loc_cnt: { type:uint, flags:["init"], value:0, doc:"Count of uniq 'loc' id's in the score."}, + //in: { type:record, fmt:{ required:["loc"] }, doc:"Input record with 'loc' field." }, + in: { type:record, doc:"Input record with 'loc' field." }, + loc: { type:uint, value:0, doc:"Seek to this location." }, + reset: { type:bool, flags:["notify"], value:false, doc:"Reset to initial state."}, + pri_manual_sel: { type:uint, flags:["notify"], value:-1, doc:"Manually select the presets.", ui:{ type:list} }, + sec_manual_sel: { type:uint, flags:["notify"], value:-1, doc:"Manually select the presets.", ui:{ type:list} }, + per_note_fl: { type:bool, value:false, doc:"Change the preset on every note, otherwise change on new preset 'fragments'." }, + per_loc_fl: { type:bool, value:false, doc:"Change the preset on every score location, change according to 'per_note_fl'." }, + dry_chord_fl: { type:bool, value:false, doc:"Set to make 50% of all chord notes dry."}, pri_prob_fl: { type:bool, flags:["notify"], value: false, doc:"Select primary preset probabilstically." } pri_uniform_fl: { type:bool, flags:["notify","ui_disable"], value: false, doc:"Use a uniform probability distribution rather than an 'order' weighted distribution." }, From 4a914b232bbcf76091235e65ce419de36df9d8f4 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 21 Apr 2025 09:32:57 -0400 Subject: [PATCH 6/6] note.md : Updates --- notes.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/notes.md b/notes.md index ef29ef9..bdd6bd2 100644 --- a/notes.md +++ b/notes.md @@ -941,6 +941,10 @@ resolvable without more information. ### TODO: +- Check for duplicate field labels. Particularly when using a base record. + It's easy to declare a field named 'midi' and then inherit a record with a field named 'midi' - this is a crash bug. + + - DONE: Why doesn't the C7 on the downbeat of meas. 11 sound? (... it does but is quiet due to velocity table) - DONE: Allow setting the location of the score player. This should also reset the sampler and voice control. - DONE: The voice ctl should respond to all-notes-off message and reset each sampler channel. @@ -1415,6 +1419,11 @@ The new record effectively inherits the contents of the existing record by reference. No data is copied. For an example of this see the `vel_table` implementation. +Note that record field values may not be changed in place because +this would change the value for all other processors that +receive the record. Incoming records must therefore be +copied if they are changed. + Variable Change Notification ---------------------------- Processors are not directly notified when one of their connected variables changes.