This commit is contained in:
kevin 2025-04-27 11:34:54 -04:00
commit 7b3a79bffc
12 changed files with 1126 additions and 207 deletions

View File

@ -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", &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 }
};

File diff suppressed because it is too large Load Diff

View File

@ -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; }
}
}

View File

@ -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; i<src_mbuf->msgN; ++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<voice_t>(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<midi::ch_msg_t>(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; i<abuf->chN; ++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<inst_t>,
.destroy = std_destroy<inst_t>,
.notify = std_notify<inst_t>,
.exec = std_exec<inst_t>,
.report = std_report<inst_t>
};
}
//------------------------------------------------------------------------------------------------------------------
//
@ -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; i<mbuf->msgN; ++i)
_set_output_record(p,rbuf, mbuf->msgA + i);
}
if( done_fl )
var_set(proc, kDoneFlPId, kAnyChIdx, true );

View File

@ -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; }

View File

@ -1497,6 +1497,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 )
{
@ -1791,6 +1895,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;

View File

@ -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 );

View File

@ -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<sf_t>();
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;
}

View File

@ -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 );

View File

@ -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;

View File

@ -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." },

View File

@ -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.