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

View File

@ -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,
@ -131,6 +135,8 @@ namespace cw
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;
rc_t _load_score( proc_t* proc, inst_t* p, const char* score_fname )
@ -178,6 +184,7 @@ namespace cw
goto errLabel;
}
p->maxLocId = 0;
p->msgA = mem::allocZ<msg_t>(p->msgAllocN);
p->chMsgA = mem::allocZ<midi::ch_msg_t>(p->msgAllocN);
@ -185,11 +192,19 @@ namespace cw
{
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;
}
@ -670,6 +689,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
@ -954,6 +979,8 @@ namespace cw
p->midiN = p->recd_array->allocRecdN;
p->midiA = mem::allocZ<midi::ch_msg_t>(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;
@ -1028,6 +1055,10 @@ namespace cw
// if this is a note on
if( midi::isNoteOn(i_m->status,i_m->d1) )
{
// 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 )
@ -1038,6 +1069,33 @@ namespace cw
// 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;
// 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<loc_t>(p->locN);
for(unsigned i=0; i<p->locN; ++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 = "<none>";
@ -1419,6 +1515,46 @@ namespace cw
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; i<p->presetN; ++i)
if((rc = list_append( p->manual_sel_list, _preset_index_to_label(p,i), i)) != kOkRC )
goto errLabel;
for(unsigned i=0; i<var_labelN; ++i)
{
variable_t* var = nullptr;
if((rc = var_find(proc, var_labelA[i], kBaseSfxId, kAnyChIdx, var )) != kOkRC )
{
rc = cwLogError(rc,"The '%s' variable could not be found.",var_labelA[i]);
goto errLabel;
}
var->value_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_idx<kMaxChN; ++ch_idx )
{
const preset_value_t* v = p->presetA[ 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_idx<kMaxChN; ++ch_idx )
{
const preset_value_t* c0 = p->presetA[ 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 )
{
@ -1697,23 +1833,26 @@ namespace cw
_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,
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,7 +2064,8 @@ namespace cw
{
rc_t rc = kOkRC;
// Custom clean-up code goes here
list_destroy(p->manual_sel_list);
mem::release(p->locA);
return rc;
}
@ -1916,15 +2074,78 @@ namespace cw
{
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( var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl) != kOkRC )
if((rc = var_get(proc,kPerLocFlPId,kAnyChIdx,per_loc_fl)) != 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 );
// 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;
rc = _apply_preset( proc, p, m, voice_idx );
// 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;
bool per_loc_fl = false;;
var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl);
var_get(proc,kPerLocFlPId,kAnyChIdx,per_loc_fl);
if( var_get(proc,kPerNoteFlPId,kAnyChIdx,per_note_fl) != kOkRC )
goto errLabel;
if( !per_note_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);
rc = _exec_on_new_location(proc,p);
//cwLogPrint("LOC:%i ",loc);
//fragment_report( p->psH, frag );
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 _update_manual_preset_index( inst_t* p, variable_t* var, unsigned& preset_idx_ref )
{
rc_t rc = kOkRC;
bool fl_value = false;
bool value_changed_fl = false;
unsigned list_idx;
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 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 );
}
if((rc = var_get(var,list_idx)) != kOkRC )
goto errLabel;
if( _update( proc, kPriAllowAllFlPId, p->pri_allow_all_fl, rc ) )
{
var_send_to_ui_enable(proc, kPriDryOnSelFlPId, kAnyChIdx, p->pri_allow_all_fl );
}
_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) );
errLabel:
if( rc != kOkRC )
preset_idx_ref = kInvalidIdx;
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) );
}
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:
@ -2431,6 +2607,265 @@ 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; i<i_rbuf->recdN; ++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<inst_t>,
.destroy = std_destroy<inst_t>,
.notify = std_notify<inst_t>,
.exec = std_exec<inst_t>,
.report = std_report<inst_t>
};
} // score_follower_2
} // flow
} //cw

View File

@ -7,6 +7,7 @@ namespace cw
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 )
{
//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 ];
@ -4497,6 +4509,11 @@ namespace cw
if( p->ns_fl )
_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);
@ -4734,6 +4754,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);
}
@ -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;
@ -5570,6 +5596,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>
};
}
//------------------------------------------------------------------------------------------------------------------
//
// audio_merge
@ -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;
@ -8095,11 +8320,28 @@ namespace cw
if((rc = var_register_and_get(proc,kAnyChIdx,
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 )
{
@ -8127,6 +8369,12 @@ namespace cw
if((rc = midi::file::open_csv(p->mfH,p->csv_fname)) != kOkRC )
goto errLabel;
}
else
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.");
@ -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;
@ -8175,6 +8425,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

@ -122,6 +122,9 @@ namespace cw
// 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

@ -7,8 +7,6 @@ namespace cw
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
@ -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."}
}
}
@ -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."},
}
},
@ -746,8 +748,8 @@
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." },
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: {
@ -799,8 +810,21 @@
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()." },
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."}
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."],
@ -922,10 +969,16 @@
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_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."},
per_note_fl: { type:bool, value:false, doc:"Update the selected preset on every note, otherwise update on new location values." },
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.