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 }, { "poly_voice_ctl", &poly_voice_ctl::members },
{ "midi_voice", &midi_voice::members }, { "midi_voice", &midi_voice::members },
{ "piano_voice", &piano_voice::members }, { "piano_voice", &piano_voice::members },
{ "voice_detector", &voice_detector::members },
{ "sample_hold", &sample_hold::members }, { "sample_hold", &sample_hold::members },
{ "number", &number::members }, { "number", &number::members },
{ "reg", &reg::members }, { "reg", &reg::members },
@ -82,6 +83,7 @@ namespace cw
{ "preset_select", &preset_select::members }, { "preset_select", &preset_select::members },
{ "gutim_ps", &gutim_ps::members }, { "gutim_ps", &gutim_ps::members },
{ "score_follower", &score_follower::members }, { "score_follower", &score_follower::members },
{ "score_follower_2",&score_follower_2::members },
{ nullptr, nullptr } { nullptr, nullptr }
}; };

View File

@ -32,6 +32,9 @@
#include "cwScoreFollowerPerf.h" #include "cwScoreFollowerPerf.h"
#include "cwScoreFollower.h" #include "cwScoreFollower.h"
#include "cwPianoScore.h"
#include "cwScoreFollow2.h"
#include "cwPianoScore.h" #include "cwPianoScore.h"
#include "cwPresetSel.h" #include "cwPresetSel.h"
@ -55,6 +58,7 @@ namespace cw
kScoreFNamePId, kScoreFNamePId,
kStoppingMsPId, kStoppingMsPId,
kDoneFlPId, kDoneFlPId,
kMaxLocPId,
kOutPId, kOutPId,
kStartPId, kStartPId,
kStopPId, 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_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 stopping_sample_idx; // 0 if not 'stopping', otherwise the max sample index after which the player will enter 'idle' state.
unsigned maxLocId;
} inst_t; } inst_t;
rc_t _load_score( proc_t* proc, inst_t* p, const char* score_fname ) rc_t _load_score( proc_t* proc, inst_t* p, const char* score_fname )
@ -178,6 +184,7 @@ namespace cw
goto errLabel; goto errLabel;
} }
p->maxLocId = 0;
p->msgA = mem::allocZ<msg_t>(p->msgAllocN); p->msgA = mem::allocZ<msg_t>(p->msgAllocN);
p->chMsgA = mem::allocZ<midi::ch_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 ) if( score_evt->status != 0 )
{ {
bool note_on_fl = false;
msg_t* m = p->msgA + p->msgN; msg_t* m = p->msgA + p->msgN;
midi::ch_msg_t* mm = p->chMsgA + p->msgN; midi::ch_msg_t* mm = p->chMsgA + p->msgN;
if( score_evt->loc != kInvalidId ) 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; 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->sample_idx = (unsigned)(proc->ctx->sample_rate * score_evt->sec);
m->loc = last_loc; m->loc = last_loc;
@ -206,9 +221,11 @@ namespace cw
time::fracSecondsToSpec( mm->timeStamp, score_evt->sec ); time::fracSecondsToSpec( mm->timeStamp, score_evt->sec );
mm->devIdx = kInvalidIdx; note_on_fl = midi::isNoteOn(score_evt->status,score_evt->d1);
mm->portIdx= kInvalidIdx;
mm->uid = uuid++; 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->ch = score_evt->status & 0x0f;
mm->status = score_evt->status & 0xf0; mm->status = score_evt->status & 0xf0;
mm->d0 = score_evt->d0; 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 m->d1 = score_evt->d1; // track the initial d1 before vel. mapping is applied
if( midi::isSustainPedal( mm->status, mm->d0 ) ) if( midi::isSustainPedal( mm->status, mm->d0 ) )
{ {
bool down_fl = pedalStateFlags & kDampPedalDownFl; bool down_fl = pedalStateFlags & kDampPedalDownFl;
@ -417,7 +435,8 @@ namespace cw
if((rc = var_register_and_set(proc,kAnyChIdx, 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; goto errLabel;
} }
@ -670,6 +689,11 @@ namespace cw
bool note_on_fl = midi::isNoteOn(m->midi->status, m->midi->d1); 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 // 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) ) if( p->state == kPlayStateId || (p->state==kStoppingStateId && note_on_fl==false) )
{ {
@ -745,6 +769,7 @@ namespace cw
vel_tbl_t* velTblL; vel_tbl_t* velTblL;
vel_tbl_t* activeVelTbl; vel_tbl_t* activeVelTbl;
unsigned i_midi_fld_idx; unsigned i_midi_fld_idx;
unsigned i_score_vel_fld_idx;
unsigned o_midi_fld_idx; unsigned o_midi_fld_idx;
recd_array_t* recd_array; // output record array recd_array_t* recd_array; // output record array
@ -954,6 +979,8 @@ namespace cw
p->midiN = p->recd_array->allocRecdN; p->midiN = p->recd_array->allocRecdN;
p->midiA = mem::allocZ<midi::ch_msg_t>(p->midiN); 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: errLabel:
return rc; return rc;
@ -1029,15 +1056,46 @@ namespace cw
// if this is a note on // if this is a note on
if( midi::isNoteOn(i_m->status,i_m->d1) ) 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)); 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; goto errLabel;
} }
// map the velocity through the active table // map the velocity through the active table
o_m->d1 = p->activeVelTbl->tblA[ i_m->d1 ]; 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); //printf("%i %i %s\n",i_m->d1,o_m->d1,p->activeVelTbl->label);
} }
@ -1261,10 +1319,15 @@ namespace cw
kInitCfgPId, kInitCfgPId,
kPresetMapCfgPId, kPresetMapCfgPId,
kFNamePId, kFNamePId,
kLocCntPId,
kInPId, kInPId,
kLocPId, kLocPId,
kResetPId, kResetPId,
kPriManualSelPId,
kSecManualSelPId,
kPerNoteFlPId, kPerNoteFlPId,
kPerLocFlPId,
kDryChordFlPId,
kPriProbFlPId, kPriProbFlPId,
kPriUniformFlPId, kPriUniformFlPId,
@ -1310,6 +1373,15 @@ namespace cw
kCoeffPresetValTId, kCoeffPresetValTId,
} value_tid_t; } 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 typedef struct var_cfg_str
{ {
const char* var_label; // gutim_ps var label const char* var_label; // gutim_ps var label
@ -1391,6 +1463,8 @@ namespace cw
coeff_t cur_interp_dist; coeff_t cur_interp_dist;
bool per_note_fl; bool per_note_fl;
bool per_loc_fl;
bool dry_chord_fl;
bool pri_prob_fl; bool pri_prob_fl;
bool pri_uniform_fl; bool pri_uniform_fl;
bool pri_dry_on_play_fl; bool pri_dry_on_play_fl;
@ -1406,9 +1480,31 @@ namespace cw
bool interp_fl; bool interp_fl;
bool interp_rand_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; } 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* _preset_index_to_label( inst_t* p, unsigned preset_idx )
{ {
const char* label = "<none>"; const char* label = "<none>";
@ -1419,6 +1515,46 @@ namespace cw
return 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; 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 > 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 ) 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; 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; 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."); rc = cwLogError(kInvalidArgRC,"The primary preset is invalid.");
goto errLabel; goto errLabel;
@ -1561,11 +1697,11 @@ namespace cw
{ {
const var_cfg_t* var_cfg = _var_cfgA + var_idx; 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 ) 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; variable_t* varb;
var_find(proc, p->base[var_cfg->var_pid] + voice_idx,ch_idx, 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; 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."); rc = cwLogError(kInvalidArgRC,"The primary preset is invalid.");
goto errLabel; 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."); rc = cwLogError(kInvalidArgRC,"The secondary preset is invalid.");
goto errLabel; goto errLabel;
@ -1620,13 +1756,13 @@ namespace cw
{ {
const var_cfg_t* var_cfg = _var_cfgA + var_idx; 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 ) 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* c0 = p->presetA[ 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* c1 = p->presetA[ sec_preset_idx ].varA[ var_idx ].chA + ch_idx;
switch( var_cfg->tid ) switch( var_cfg->tid )
{ {
@ -1697,23 +1833,26 @@ namespace cw
_report_preset( proc, p, "Sec", p->cur_sec_preset_idx ); _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; 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."); rc = cwLogError(kInvalidStateRC,"No current preset has been selected.");
goto errLabel; 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 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: errLabel:
@ -1795,13 +1934,20 @@ namespace cw
const object_t* cfg = nullptr; const object_t* cfg = nullptr;
bool resetFl = false; bool resetFl = false;
p->dry_preset_idx = kInvalidIdx;
if((rc = var_register_and_get(proc,kAnyChIdx, if((rc = var_register_and_get(proc,kAnyChIdx,
kInitCfgPId, "cfg", kBaseSfxId, cfg, // TODO: clean up the contents of this CFG kInitCfgPId, "cfg", kBaseSfxId, cfg, // TODO: clean up the contents of this CFG
kInPId, "in", kBaseSfxId, rbuf, kInPId, "in", kBaseSfxId, rbuf,
kFNamePId, "fname", kBaseSfxId, fname, kFNamePId, "fname", kBaseSfxId, fname,
kLocPId, "loc", kBaseSfxId, loc, kLocCntPId, "loc_cnt", kBaseSfxId, p->locN,
kResetPId, "reset", kBaseSfxId, resetFl, kLocPId, "loc", kBaseSfxId, loc,
kPerNoteFlPId, "per_note_fl",kBaseSfxId, p->per_note_fl, 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, kPriProbFlPId, "pri_prob_fl", kBaseSfxId, p->pri_prob_fl,
kPriUniformFlPId, "pri_uniform_fl", kBaseSfxId, p->pri_uniform_fl, kPriUniformFlPId, "pri_uniform_fl", kBaseSfxId, p->pri_uniform_fl,
@ -1841,6 +1987,8 @@ namespace cw
goto errLabel; goto errLabel;
} }
p->dry_preset_idx = preset_sel::dry_preset_index(p->psH);
// read in the loc->preset map file // read in the loc->preset map file
if((rc = preset_sel::read(p->psH,exp_fname)) != kOkRC ) 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. // The location is coming from a 'record', get the location field.
if((p->loc_fld_idx = recd_type_field_index( rbuf->type, "loc")) == kInvalidIdx ) 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."); cwLogWarning("The incoming record to the 'gutim_ps' object does not have a 'loc' field. Score tracking is disabled.");
goto errLabel; //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) 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 // 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: errLabel:
mem::release(exp_fname); mem::release(exp_fname);
return rc; return rc;
@ -1907,24 +2064,88 @@ namespace cw
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
// Custom clean-up code goes here list_destroy(p->manual_sel_list);
mem::release(p->locA);
return rc; return rc;
} }
rc_t _exec_note_on( proc_t* proc, inst_t* p, const midi::ch_msg_t* m, unsigned voice_idx ) rc_t _exec_note_on( proc_t* proc, inst_t* p, const midi::ch_msg_t* m, unsigned voice_idx )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
bool per_note_fl = false; 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; goto errLabel;
if( per_note_fl ) // if we are selecting a new preset per location
if((rc = _update_cur_preset_idx( proc, p, p->cur_frag )) != kOkRC ) if( per_loc_fl && p->locN > 0 )
goto errLabel; {
rc = _apply_preset( proc, p, m, voice_idx ); 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;
// 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: errLabel:
return rc; return rc;
@ -1956,15 +2177,16 @@ namespace cw
return rc; 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; rc_t rc = kOkRC;
bool per_note_fl = false; 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 ) // if we are not assigning presets per note - then select p->cur_pri/sec_preset_idx for all following notes
goto errLabel; if( per_note_fl == false && per_loc_fl == false )
if( !per_note_fl )
if((rc = _update_cur_preset_idx( proc, p, p->cur_frag )) != kOkRC ) if((rc = _update_cur_preset_idx( proc, p, p->cur_frag )) != kOkRC )
goto errLabel; goto errLabel;
@ -1979,6 +2201,10 @@ namespace cw
rbuf_t* in_rbuf = nullptr; rbuf_t* in_rbuf = nullptr;
unsigned loc = kInvalidIdx; 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) if((rc = var_get(proc,kInPId,kAnyChIdx,in_rbuf)) != kOkRC)
goto errLabel; goto errLabel;
@ -2001,13 +2227,17 @@ namespace cw
{ {
const preset_sel::frag_t* frag = nullptr; 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 ) 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; p->cur_frag = frag;
cwLogInfo("LOC:%i",loc); 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; return rc;
} }
/* rc_t _update_manual_preset_index( inst_t* p, variable_t* var, unsigned& preset_idx_ref )
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 rc = kOkRC; rc_t rc = kOkRC;
unsigned list_idx;
if( _update( proc, kPriProbFlPId, p->pri_prob_fl, rc) ) if((rc = var_get(var,list_idx)) != kOkRC )
{ goto errLabel;
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 = list_ele_value(p->manual_sel_list,list_idx,preset_idx_ref)) != kOkRC )
if( _update( proc, kPriAllowAllFlPId, p->pri_allow_all_fl, rc ) ) goto errLabel;
{
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( _update( proc, kInterpFlPId, p->interp_fl, rc ) ) errLabel:
{ if( rc != kOkRC )
var_send_to_ui_enable(proc, kInterpRandFlPId, kAnyChIdx, p->interp_fl ); preset_idx_ref = kInvalidIdx;
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) );
}
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; return rc;
} }
*/
rc_t _update_ui_state( proc_t* proc, inst_t* p, variable_t* var ) rc_t _update_ui_state( proc_t* proc, inst_t* p, variable_t* var )
{ {
@ -2110,6 +2277,16 @@ namespace cw
switch(var->vid) 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: case kPriProbFlPId:
var_get(var,p->pri_prob_fl); var_get(var,p->pri_prob_fl);
var_send_to_ui_enable(proc, kPriUniformFlPId, kAnyChIdx, 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; rc_t rc = kOkRC;
//if((rc = _exec_update_state(proc, p )) != kOkRC )
// goto errLabel;
_update_ui_state( proc, p, var ); _update_ui_state( proc, p, var );
if( var->vid == kResetPId ) if( var->vid == kResetPId )
@ -2197,6 +2372,7 @@ namespace cw
if( p->psH.isValid() ) if( p->psH.isValid() )
track_loc_reset( p->psH); track_loc_reset( p->psH);
_init_loc_array(p);
} }
//errLabel: //errLabel:
@ -2431,6 +2607,265 @@ namespace cw
} // score_follower } // 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 } // flow
} //cw } //cw

View File

@ -2,11 +2,12 @@ namespace cw
{ {
namespace flow namespace flow
{ {
namespace score_player { extern class_members_t members; } namespace score_player { extern class_members_t members; }
namespace vel_table { extern class_members_t members; } namespace vel_table { extern class_members_t members; }
namespace preset_select { extern class_members_t members; } namespace preset_select { extern class_members_t members; }
namespace gutim_ps { extern class_members_t members; } namespace gutim_ps { extern class_members_t members; }
namespace score_follower { 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, kBufMsgCntPId,
kInPId, kInPId,
kRInPId, kRInPId,
kPrintFlPId kPrintFlPId,
kEnableFlPId,
}; };
typedef struct typedef struct
@ -832,13 +833,14 @@ namespace cw
const char* port_label = nullptr; const char* port_label = nullptr;
rbuf_t* rbuf = nullptr; rbuf_t* rbuf = nullptr;
bool printFl = false; bool printFl = false;
bool enableFl = false;
// Register variables and get their current value // Register variables and get their current value
if((rc = var_register_and_get( proc, kAnyChIdx, if((rc = var_register_and_get( proc, kAnyChIdx,
kDevLabelPId, "dev_label", kBaseSfxId, dev_label, kDevLabelPId, "dev_label", kBaseSfxId, dev_label,
kPortLabelPId,"port_label", kBaseSfxId, port_label, kPortLabelPId,"port_label", kBaseSfxId, port_label,
kPrintFlPId, "print_fl", kBaseSfxId, printFl, kPrintFlPId, "print_fl", kBaseSfxId, printFl,
kEnableFlPId, "enable_fl", kBaseSfxId, enableFl,
kBufMsgCntPId,"buf_cnt", kBaseSfxId, p->msgN )) != kOkRC ) kBufMsgCntPId,"buf_cnt", kBaseSfxId, p->msgN )) != kOkRC )
{ {
goto errLabel; goto errLabel;
@ -907,9 +909,15 @@ namespace cw
return kOkRC; 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 ) 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)); 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; rc_t rc = kOkRC;
const rbuf_t* rbuf = nullptr; const rbuf_t* rbuf = nullptr;
bool print_fl = false; bool print_fl = false;
bool enable_fl = true;
const mbuf_t* src_mbuf = nullptr; const mbuf_t* src_mbuf = nullptr;
var_get(proc,kPrintFlPId,kAnyChIdx,print_fl); var_get(proc,kPrintFlPId,kAnyChIdx,print_fl);
var_get(proc,kEnableFlPId,kAnyChIdx,enable_fl);
if( p->rin_exists_fl ) if( p->rin_exists_fl )
{ {
@ -937,7 +948,7 @@ namespace cw
const midi::ch_msg_t* m = nullptr; const midi::ch_msg_t* m = nullptr;
if((rc = recd_get(rbuf->type,r,p->midi_fld_idx,m)) == kOkRC ) 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 else
{ {
rc = cwLogError(rc,"Record 'midi' field read failed."); rc = cwLogError(rc,"Record 'midi' field read failed.");
@ -957,7 +968,7 @@ namespace cw
{ {
for(unsigned i=0; i<src_mbuf->msgN; ++i) 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 typedef struct
{ {
unsigned baseGateFlPId;
unsigned baseDoneFlPId; unsigned baseDoneFlPId;
midi_t midiA[ midi::kMidiNoteCnt ]; midi_t midiA[ midi::kMidiNoteCnt ];
@ -4497,6 +4509,11 @@ namespace cw
if( p->ns_fl ) 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 ) void _reset_all_voices( proc_t* proc, inst_t* p )
@ -4523,7 +4540,8 @@ namespace cw
goto errLabel; goto errLabel;
} }
p->baseDoneFlPId = kBaseOutPId + p->voiceN; p->baseGateFlPId = kBaseOutPId + p->voiceN;
p->baseDoneFlPId = p->baseGateFlPId + p->voiceN;
p->voiceMsgN = kVoiceMsgN; p->voiceMsgN = kVoiceMsgN;
p->voiceA = mem::allocZ<voice_t>(p->voiceN); 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 ) if((rc = var_register_and_set( proc, "out", i, kBaseOutPId + i, kAnyChIdx, nullptr, 0 )) != kOkRC )
goto errLabel; goto errLabel;
// create one 'done_fl' variable per voice // 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 )) != kOkRC ) 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; goto errLabel;
p->voiceA[i].msgA = mem::allocZ<midi::ch_msg_t>(p->voiceMsgN); p->voiceA[i].msgA = mem::allocZ<midi::ch_msg_t>(p->voiceMsgN);
@ -4734,6 +4754,10 @@ namespace cw
if( p->ns_fl ) if( p->ns_fl )
_store_note_state( proc, p, m->uid, midi::kNoteOnMdId, m->d0, m->d1, voice_idx ); _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; return rc;
} }
@ -5392,10 +5416,12 @@ namespace cw
p->gain_coeff = p->kReleaseGain; 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; p->noff_fl = true;
//printf("nof:%i %i\n",proc->label_sfx_id,p->pitch);
if( !p->sustain_fl ) if( !p->sustain_fl )
_begin_note_release(p); _begin_note_release(p);
} }
@ -5473,9 +5499,9 @@ namespace cw
rc_t _exec( proc_t* proc, inst_t* p ) rc_t _exec( proc_t* proc, inst_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
abuf_t* abuf = nullptr; abuf_t* abuf = nullptr;
mbuf_t* mbuf = nullptr; mbuf_t* mbuf = nullptr;
unsigned actualFrmN = 0; unsigned actualFrmN = 0;
//sample_t rms = 0; //sample_t rms = 0;
@ -5508,13 +5534,13 @@ namespace cw
} }
else else
{ {
_on_note_off(p); _on_note_off(proc,p);
_store_note_state(proc, p, m->uid, midi::kNoteOffMdId, m->d0, 0 ); _store_note_state(proc, p, m->uid, midi::kNoteOffMdId, m->d0, 0 );
} }
break; break;
case midi::kNoteOffMdId: case midi::kNoteOffMdId:
_on_note_off(p); _on_note_off(proc,p);
_store_note_state(proc, p, m->uid, midi::kNoteOnMdId, m->d0, 0 ); _store_note_state(proc, p, m->uid, midi::kNoteOnMdId, m->d0, 0 );
break; 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 // audio_merge
@ -8056,8 +8231,12 @@ namespace cw
enum { enum {
kMidiFileNamePId, kMidiFileNamePId,
kCsvFileNamePId, kCsvFileNamePId,
kCsvFileName2PId,
kStartPId,
kStopPId,
kDoneFlPId, kDoneFlPId,
kOutPId kOutPId,
kROutPId
}; };
typedef struct msg_str typedef struct msg_str
@ -8077,14 +8256,60 @@ namespace cw
char* midi_fname; char* midi_fname;
char* csv_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; } 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 _create( proc_t* proc, inst_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
const char* midi_fname = nullptr; const char* midi_fname = nullptr;
const char* csv_fname = nullptr; const char* csv_fname = nullptr;
const char* csv_fname_2= nullptr;
const midi::file::trackMsg_t** tmA = nullptr; const midi::file::trackMsg_t** tmA = nullptr;
unsigned msgAllocN = 0; unsigned msgAllocN = 0;
bool done_fl = false; bool done_fl = false;
@ -8093,13 +8318,30 @@ namespace cw
time::setZero(asecs); time::setZero(asecs);
if((rc = var_register_and_get(proc,kAnyChIdx, if((rc = var_register_and_get(proc,kAnyChIdx,
kMidiFileNamePId, "fname", kBaseSfxId, midi_fname, kMidiFileNamePId, "fname", kBaseSfxId, midi_fname,
kCsvFileNamePId, "csv_fname", kBaseSfxId, csv_fname, kCsvFileNamePId, "csv_fname", kBaseSfxId, csv_fname,
kDoneFlPId, "done_fl", kBaseSfxId, done_fl)) != kOkRC ) kCsvFileName2PId, "alt_csv_fname", kBaseSfxId, csv_fname_2,
kDoneFlPId, "done_fl", kBaseSfxId, done_fl)) != kOkRC )
{ {
goto errLabel; 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( csv_fname != nullptr && textLength(csv_fname)>0 )
if((p->csv_fname = proc_expand_filename(proc,csv_fname)) == nullptr ) if((p->csv_fname = proc_expand_filename(proc,csv_fname)) == nullptr )
{ {
@ -8128,9 +8370,15 @@ namespace cw
goto errLabel; goto errLabel;
} }
else else
{ if( p->csv_fname_2 != nullptr && textLength(p->csv_fname_2)>0 )
rc = cwLogError(kOpenFailRC,"No MIDI or CSV filename was given."); {
} 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 // create one output MIDI buffer
@ -8148,12 +8396,14 @@ namespace cw
{ {
const midi::file::trackMsg_t* tm = tmA[i]; const midi::file::trackMsg_t* tm = tmA[i];
msg_t* m = p->msgA + p->msg_idx; msg_t* m = p->msgA + p->msg_idx;
double secs;
m->m = p->chMsgA + p->msg_idx; m->m = p->chMsgA + p->msg_idx;
time::microsecondsToSpec( m->m->timeStamp, tmA[i]->amicro ); 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->devIdx = 0;
m->m->portIdx = 0; m->m->portIdx = 0;
@ -8175,6 +8425,18 @@ namespace cw
p->msgN = p->msg_idx; p->msgN = p->msg_idx;
p->msg_idx = 0; 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: errLabel:
return rc; return rc;
} }
@ -8182,9 +8444,10 @@ namespace cw
rc_t _destroy( proc_t* proc, inst_t* p ) rc_t _destroy( proc_t* proc, inst_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
recd_array_destroy(p->recd_array);
mem::release(p->midi_fname); mem::release(p->midi_fname);
mem::release(p->csv_fname); mem::release(p->csv_fname);
mem::release(p->csv_fname_2);
close(p->mfH); close(p->mfH);
return rc; return rc;
@ -8193,6 +8456,36 @@ namespace cw
rc_t _notify( proc_t* proc, inst_t* p, variable_t* var ) rc_t _notify( proc_t* proc, inst_t* p, variable_t* var )
{ {
rc_t rc = kOkRC; 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; return rc;
} }
@ -8201,6 +8494,7 @@ namespace cw
rc_t rc = kOkRC; rc_t rc = kOkRC;
mbuf_t* mbuf = nullptr; mbuf_t* mbuf = nullptr;
bool done_fl = false; bool done_fl = false;
rbuf_t* rbuf = nullptr;
p->sample_idx += proc->ctx->framesPerCycle; 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 ) if( done_fl )
var_set(proc, kDoneFlPId, kAnyChIdx, true ); var_set(proc, kDoneFlPId, kAnyChIdx, true );

View File

@ -65,6 +65,7 @@ namespace cw
namespace xfade_ctl { extern class_members_t members; } namespace xfade_ctl { extern class_members_t members; }
namespace midi_voice { extern class_members_t members; } namespace midi_voice { extern class_members_t members; }
namespace piano_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 poly_voice_ctl { extern class_members_t members; }
namespace sample_hold { extern class_members_t members; } namespace sample_hold { extern class_members_t members; }
namespace number { extern class_members_t members; } namespace number { extern class_members_t members; }

View File

@ -1497,6 +1497,110 @@ errLabel:
return rc; 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 ) 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 // fill the track record
m->uid = p->nextUid++; m->uid = p->nextUid++;
m->atick = msg->atick; m->atick = msg->atick;
m->amicro = msg->amicro;
m->status = msg->status; m->status = msg->status;
m->metaId = msg->metaId; m->metaId = msg->metaId;
m->trkIdx = trkIdx; 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). // 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 ); 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. // Create an empty MIDI file object.
rc_t create( handle_t& hRef, unsigned trkN, unsigned ticksPerQN ); 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; rc_t rc = kOkRC;
double d_corr_sec = 0.0; double d_corr_sec = 0.0;
@ -257,6 +257,7 @@ namespace cw
unsigned match_ni = kInvalidIdx; unsigned match_ni = kInvalidIdx;
double match_val = 0; double match_val = 0;
score_vel_ref = -1;
matched_loc_id_ref = kInvalidId; matched_loc_id_ref = kInvalidId;
assert( trk->exp_loc_idx != kInvalidIdx && trk->exp_loc_idx < trk->sf->locN ); 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->loc_match_cntA[ match_loc_idx ] += 1;
trk->note_match_cntA[ match_ni ] += 1; trk->note_match_cntA[ match_ni ] += 1;
score_vel_ref = trk->sf->noteA[ match_ni ].vel;
matched_loc_id_ref = match_loc_id; matched_loc_id_ref = match_loc_id;
// notice if we arrived at the end of the score tracking range // notice if we arrived at the end of the score tracking range
@ -805,7 +807,7 @@ errLabel:
return rc; 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; rc_t rc;
if((rc = destroy(hRef)) != kOkRC ) 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>(); 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; 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; goto errLabel;
if((rc = _alloc_and_fill_loc_note_arrays( p )) != kOkRC ) 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); _trkr_reset(p->trk,beg_loc_id);
cwLogInfo("SF2 reset: %i %i",beg_loc_id,end_loc_id);
errLabel: errLabel:
return rc; 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; rc_t rc = kOkRC;
sf_t* p = _handleToPtr(h); 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 ) 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_uid = uid;
r->perf_pitch = pitch; r->perf_pitch = pitch;
r->perf_vel = vel; r->perf_vel = vel;
r->match_loc_id = matched_loc_id; r->match_loc_id = matched_loc_id_ref;
p->resultN += 1; p->resultN += 1;
} }

View File

@ -7,21 +7,19 @@ namespace cw
typedef struct args_str typedef struct args_str
{ {
perf_score::handle_t scoreH;
double pre_affinity_sec; // 1.0 look back affinity duration double pre_affinity_sec; // 1.0 look back affinity duration
double post_affinity_sec; // 3.0 look forward affinity duration double post_affinity_sec; // 3.0 look forward affinity duration
double pre_wnd_sec; // 2.0 look back search window double pre_wnd_sec; // 2.0 look back search window
double post_wnd_sec; // 5.0 look forward 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 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 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_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_stats_thresh; // 3 reject for time stats updates if d_loc > d_loc_stats_thresh
bool rpt_fl; // set to turn on debug reporting 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 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 destroy( handle_t& hRef );
rc_t reset( handle_t h, unsigned beg_loc_id, unsigned end_loc_id ); 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 // Decay the affinity window and if necessary trigger a cycle of async background processing
rc_t do_exec( handle_t h ); 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); //cwLogInfo("Following: (%i notes found) sample count:%i %s.",mf.evtN,mf.sampleN,midi_csv_fname);
sf_args.scoreH = scoreH;
// create the score-follower // 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."); rc = cwLogError(rc,"Score follower create failed.");
goto errLabel; 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 ) 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++; 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); //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); rc = cwLogError(rc,"SF2 note processing failed on note UID:%i.",e->uid);
goto errLabel; goto errLabel;

View File

@ -44,6 +44,7 @@
in: { type:midi, doc:"MIDI messages to send."}, in: { type:midi, doc:"MIDI messages to send."},
rin: { type:record, fmt:{ required:["midi"]}, doc:"Record input. (must have 'midi' field)"}, 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."} 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." }, in: { type:uint, flags:["notify","src"], doc:"List selection index." },
list: { type:cfg, flags:["init"], value:{} doc:"List as a 'cfg' object." }, list: { type:cfg, flags:["init"], value:{} doc:"List as a 'cfg' object." },
out: { type:runtime, doc:"List output value." }, 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."}, in: { type:record, fmt:{ required:["midi"]} doc:"MIDI input."},
voice_cnt: { type:uint, value:3, flags:["init"], doc:"Count of voices." }, 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." }, 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: { piano_voice: {
vars: { vars: {
wtb_fname: { type:string, flags:["init"], doc:"Wave table bank cfg. file." }, 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."}, wtb_instr: { type:string, value:"piano", flags:["init"], doc:"Instrument label of the selected wave-table bank."},
in: { type:midi, doc:"MIDI in" }, in: { type:midi, doc:"MIDI in" },
out: { type:audio, doc:"Audio out" }, out: { type:audio, doc:"Audio out" },
done_fl: { type:bool, value:false, doc:"Triggers when voice is available."}, done_fl: { type:bool, value:false, doc:"Output: True when voice is available, false when active."},
gate_fl: { type:bool, value:false, doc:"True when voice is active, false when inactive." }, 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_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."}, 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: { print: {
vars: { vars: {
@ -797,10 +808,23 @@
"bpm = beats per minute should be given on the first line. (Defaults to 60)." ] "bpm = beats per minute should be given on the first line. (Defaults to 60)." ]
vars: { vars: {
fname: { type:string, flags:["init"], value:"", doc:"MIDI file name." }, 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()." }, 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." }, alt_csv_fname: { type:string, flags:["init"], value:"", doc:"MIDI CSV fname. See: midi::file open_csv_2()." },
out: { type:midi, doc:"MIDI output."} 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." }, b_meas: { type:uint, flags:["notify"], value:0, doc:"Score begin measure." },
e_meas: { type:uint, flags:["notify"], value:0, doc:"Score end measure." }, e_meas: { type:uint, flags:["notify"], value:0, doc:"Score end measure." },
done_fl: { type:bool, value:false, doc:"Emits true on done." }, 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.", out: { type:record, doc:"Score event record.",
fmt: { fmt: {
fields: { fields: {
@ -851,7 +876,9 @@
}, },
vel_table: { 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: { vars: {
vel_tbl_fname:{ type:string, flags:["init"], value:"", doc:"Velocity table filename as create by vwVelTableTuner." }, 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'."}, 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: { preset_select: {
doc:[ "Given a score location emit a preset label."], doc:[ "Given a score location emit a preset label."],
@ -920,12 +967,18 @@
gutim_ps: { gutim_ps: {
doc:[ "Given score location and MIDI note messages emit transform parameters."], doc:[ "Given score location and MIDI note messages emit transform parameters."],
vars: { vars: {
cfg: { type:cfg, flags:["init"], doc:"Initial preset configuration." }, cfg: { type:cfg, flags:["init"], doc:"Initial preset configuration." },
fname: { type:string, flags:["init"], value:"", doc:"Preset file name."}, 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."},
loc: { type:uint, value:0, doc:"Seek to this location." }, //in: { type:record, fmt:{ required:["loc"] }, doc:"Input record with 'loc' field." },
reset: { type:bool, flags:["notify"], value:false, doc:"Reset to initial state."}, in: { type:record, doc:"Input record with 'loc' field." },
per_note_fl: { type:bool, value:false, doc:"Update the selected preset on every note, otherwise update on new location values." }, 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_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." }, 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: ### 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: 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: 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. - 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. existing record by reference. No data is copied.
For an example of this see the `vel_table` implementation. 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 Variable Change Notification
---------------------------- ----------------------------
Processors are not directly notified when one of their connected variables changes. Processors are not directly notified when one of their connected variables changes.