Compare commits

...

9 Commits

12 changed files with 789 additions and 453 deletions

View File

@ -22,8 +22,8 @@ libcwSRC += src/libcw/cwObject.cpp src/libcw/cwText
libcwHDR += src/libcw/cwThread.h src/libcw/cwMutex.h src/libcw/cwThreadMach.h
libcwSRC += src/libcw/cwThread.cpp src/libcw/cwMutex.cpp src/libcw/cwThreadMach.cpp
libcwHDR += src/libcw/cwMpScNbQueue.h src/libcw/cwSpScBuf.h src/libcw/cwSpScQueueTmpl.h
libcwSRC += src/libcw/cwSpScBuf.cpp src/libcw/cwSpScQueueTmpl.cpp
libcwHDR += src/libcw/cwMtQueueTester.h src/libcw/cwMpScNbQueue.h src/libcw/cwSpScBuf.h src/libcw/cwSpScQueueTmpl.h src/libcw/cwMpScNbCircQueue.h
libcwSRC += src/libcw/cwMtQueueTester.cpp src/libcw/cwSpScBuf.cpp src/libcw/cwSpScQueueTmpl.cpp
libcwHDR += src/libcw/cwNbMpScQueue.h
libcwSRC += src/libcw/cwNbMpScQueue.cpp

View File

@ -952,67 +952,71 @@ namespace cw
template< typename T0, typename T1 >
rc_t exec( struct obj_str<T0,T1>* p, const T0* magV, const T0* phsV, unsigned binN )
rc_t exec( struct obj_str<T0,T1>* p, const T0* magV, const T0* phsV, unsigned binN, bool enable_fl=true )
{
rc_t rc = kOkRC;
double X0m[binN];
double X1m[binN];
// take the mean of the the input magntitude spectrum
double u0 = vop::mean(magV,binN);
// convert magnitude to db (range=-1000.0 to 0.0)
vop::ampl_to_db(X0m, magV, binN );
vop::copy(X1m,X0m,binN);
// bump transform X0m
_cmSpecDist2Bump(p,X0m, binN, p->ceiling, p->expo);
// mix bump output with raw input: X1m = (X0m*mix) + (X1m*(1.0-mix))
vop::mul(X0m, p->mix, binN );
vop::mul(X1m, 1.0 - p->mix, binN );
vop::add(X1m, X0m, binN );
// basic transform
_cmSpecDist2BasicMode_WithKnee(p,X1m,binN,p->thresh,p->uprSlope,p->lwrSlope);
// convert db back to magnitude
vop::db_to_ampl(X1m, X1m, binN );
// convert the mean input magnitude to db
double idb = 20*log10(u0);
// get the mean output magnitude spectra
double u1 = vop::mean(X1m,binN);
//if( p->mix > 0 )
if(1)
if( p->bypassFl || !enable_fl )
{
if( idb > -150.0 )
{
// set the output gain such that the mean output magnitude
// will match the mean input magnitude
p->ogain = u0/u1;
}
else
{
T0 a0 = 0.9;
p->ogain *= a0;
}
}
// apply the output gain
if( p->bypassFl )
vop::copy( p->outMagV, magV, binN );
}
else
{
double X0m[binN];
double X1m[binN];
// take the mean of the the input magntitude spectrum
double u0 = vop::mean(magV,binN);
// convert magnitude to db (range=-1000.0 to 0.0)
vop::ampl_to_db(X0m, magV, binN );
vop::copy(X1m,X0m,binN);
// bump transform X0m
_cmSpecDist2Bump(p,X0m, binN, p->ceiling, p->expo);
// mix bump output with raw input: X1m = (X0m*mix) + (X1m*(1.0-mix))
vop::mul(X0m, p->mix, binN );
vop::mul(X1m, 1.0 - p->mix, binN );
vop::add(X1m, X0m, binN );
// basic transform
_cmSpecDist2BasicMode_WithKnee(p,X1m,binN,p->thresh,p->uprSlope,p->lwrSlope);
// convert db back to magnitude
vop::db_to_ampl(X1m, X1m, binN );
// convert the mean input magnitude to db
double idb = 20*log10(u0);
// get the mean output magnitude spectra
double u1 = vop::mean(X1m,binN);
//if( p->mix > 0 )
if(1)
{
if( idb > -100.0 )
{
// set the output gain such that the mean output magnitude
// will match the mean input magnitude
p->ogain = u0/u1;
}
else
{
T0 a0 = 0.9;
p->ogain *= a0;
}
}
// apply the output gain
//vop::mul( p->outMagV, X1m, std::min((T1)4.0,p->ogain), binN);
vop::mul( p->outMagV, X1m, p->ogain, binN);
}
vop::copy( p->outPhsV, phsV, binN);
vop::copy( p->outPhsV, phsV, binN);
return rc;
}

View File

@ -1337,10 +1337,12 @@ namespace cw
if( max_vid != kInvalidId )
{
// create the variable map array
proc->varMapChN = max_chIdx + 1;
proc->varMapIdN = max_vid + 1;
proc->varMapN = proc->varMapIdN * proc->varMapChN;
proc->varMapA = mem::allocZ<variable_t*>( proc->varMapN );
proc->varMapChN = max_chIdx + 1;
proc->varMapIdN = max_vid + 1;
proc->varMapN = proc->varMapIdN * proc->varMapChN;
proc->varMapA = mem::allocZ<variable_t*>( proc->varMapN );
proc->modVarMapN = proc->varMapN;
proc->modVarMapA = mem::allocZ<variable_t*>( proc->modVarMapN );
// assign each variable to a location in the map
for(variable_t* var=proc->varL; var!=nullptr; var=var->var_link)

View File

@ -141,7 +141,8 @@ namespace cw
const perf_score::event_t* score_evt = nullptr;
char* fname = nullptr;
unsigned pedalStateFlags = 0;
unsigned uuid = 0;
unsigned last_loc = 0;
p->score_end_loc = 0;
p->score_end_meas = 0;
@ -157,6 +158,8 @@ namespace cw
goto errLabel;
}
cwLogInfo("Opening:%s",fname);
if((rc= perf_score::create( perfScoreH, fname )) != kOkRC )
{
rc = cwLogError(rc,"Score create failed on '%s'.",fname);
@ -184,13 +187,18 @@ namespace cw
{
msg_t* m = p->msgA + p->msgN;
midi::ch_msg_t* mm = p->chMsgA + p->msgN;
if( score_evt->loc != kInvalidId )
last_loc = score_evt->loc;
m->sample_idx = (unsigned)(proc->ctx->sample_rate * score_evt->sec);
m->loc = score_evt->loc;
m->loc = last_loc;
m->meas = score_evt->meas;
m->midi = mm;
if( m->loc > p->score_end_loc )
//printf("%i %i\n",m->meas,m->loc);
if( m->loc!=kInvalidId && m->loc > p->score_end_loc )
p->score_end_loc = m->loc;
if( m->meas > p->score_end_meas )
@ -200,7 +208,7 @@ namespace cw
mm->devIdx = kInvalidIdx;
mm->portIdx= kInvalidIdx;
mm->uid = score_evt->loc;
mm->uid = uuid++;
mm->ch = score_evt->status & 0x0f;
mm->status = score_evt->status & 0xf0;
mm->d0 = score_evt->d0;
@ -282,121 +290,6 @@ namespace cw
}
rc_t _create( proc_t* proc, inst_t* p )
{
rc_t rc = kOkRC;
const char* score_fname = nullptr;
double stopping_secs = 5.0;
if((rc = var_register_and_get(proc,kAnyChIdx,
kScoreFNamePId, "fname", kBaseSfxId, score_fname,
kStoppingMsPId, "stopping_ms", kBaseSfxId, p->stopping_ms)) != kOkRC )
{
goto errLabel;
}
// load the score
if((rc = _load_score( proc, p, score_fname )) != kOkRC )
{
goto errLabel;
}
if((rc = var_register(proc,kAnyChIdx,
kStartPId, "start", kBaseSfxId,
kStopPId, "stop", kBaseSfxId )) != kOkRC )
{
goto errLabel;
}
if((rc = var_register_and_set(proc,kAnyChIdx,
kDoneFlPId,"done_fl", kBaseSfxId, false,
kBLocPId, "b_loc", kBaseSfxId, 0,
kBMeasPId, "b_meas", kBaseSfxId, 0,
kELocPId, "e_loc", kBaseSfxId, p->score_end_loc,
kEMeasPId, "e_meas", kBaseSfxId, p->score_end_meas + 1)) != kOkRC )
{
goto errLabel;
}
// allocate the output recd array
if((rc = _alloc_recd_array( proc, "out", kBaseSfxId, kAnyChIdx, nullptr, 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->midi_fld_idx = recd_type_field_index( p->recd_array->type, "midi");
p->loc_fld_idx = recd_type_field_index( p->recd_array->type, "loc");
p->meas_fld_idx = recd_type_field_index( p->recd_array->type, "meas");
p->bVId = kInvalidId;
p->eVId = kInvalidId;
p->end_msg_idx = kInvalidIdx;
p->midiChMsgA[kAllNotesOffMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kAllNotesOffMdId, .d1=0 };
p->midiChMsgA[kResetAllCtlsMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kResetAllCtlsMdId, .d1=0 };
p->midiChMsgA[kDampPedalDownMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kSustainCtlMdId, .d1=64 };
p->midiChMsgA[kSostPedalDownMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kSostenutoCtlMdId, .d1=64 };
p->midiMsgA[kAllNotesOffMsgIdx].midi = p->midiChMsgA + kAllNotesOffMsgIdx;
p->midiMsgA[kResetAllCtlsMsgIdx].midi = p->midiChMsgA + kResetAllCtlsMsgIdx;
p->midiMsgA[kDampPedalDownMsgIdx].midi = p->midiChMsgA + kDampPedalDownMsgIdx;
p->midiMsgA[kSostPedalDownMsgIdx].midi = p->midiChMsgA + kSostPedalDownMsgIdx;
p->state = kIdleStateId;
errLabel:
return rc;
}
rc_t _destroy( proc_t* proc, inst_t* p )
{
rc_t rc = kOkRC;
recd_array_destroy(p->recd_array);
mem::release(p->msgA);
mem::release(p->chMsgA);
return rc;
}
rc_t _value( proc_t* proc, inst_t* p, variable_t* var )
{
rc_t rc = kOkRC;
if( proc->ctx->isInRuntimeFl )
{
switch( var->vid )
{
case kStartPId:
p->start_trig_fl = true;
printf("Start Clicked\n");
break;
case kStopPId:
p->stop_trig_fl = true;
printf("Stop Clicked\n");
break;
case kBLocPId:
case kBMeasPId:
p->bVId = var->vid;
break;
case kELocPId:
case kEMeasPId:
p->eVId = var->vid;
break;
}
}
return rc;
}
rc_t _on_new_begin_loc( proc_t* proc, inst_t* p, unsigned vid )
{
rc_t rc = kOkRC;
@ -416,6 +309,7 @@ namespace cw
switch( vid )
{
case kBLocPId:
printf("Setting: bloc:%i %i meas:%i msg_idx:%i\n",bloc,p->msgA[i].loc, p->msgA[i].meas+1,i);
if( i < p->msgN )
var_set(proc,kBMeasPId,kAnyChIdx,p->msgA[i].meas+1);
else
@ -426,6 +320,8 @@ namespace cw
break;
case kBMeasPId:
printf("Setting: bmeas:%i %i meas:%i msg_idx:%i\n",bmeas,p->msgA[i].loc, p->msgA[i].meas, i);
if( i < p->msgN )
var_set(proc,kBLocPId,kAnyChIdx,p->msgA[i].loc);
else
@ -483,6 +379,132 @@ namespace cw
return rc;
}
rc_t _create( proc_t* proc, inst_t* p )
{
rc_t rc = kOkRC;
const char* score_fname = nullptr;
unsigned bloc=0;
unsigned eloc=0;
unsigned bmeas=0;
unsigned emeas=0;
if((rc = var_register_and_get(proc,kAnyChIdx,
kScoreFNamePId, "fname", kBaseSfxId, score_fname,
kStoppingMsPId, "stopping_ms", kBaseSfxId, p->stopping_ms,
kBLocPId, "b_loc", kBaseSfxId, bloc,
kBMeasPId, "b_meas", kBaseSfxId, bmeas,
kELocPId, "e_loc", kBaseSfxId, eloc,
kEMeasPId, "e_meas", kBaseSfxId, emeas,
kStartPId, "start", kBaseSfxId, p->start_trig_fl)) != kOkRC )
{
goto errLabel;
}
// load the score
if((rc = _load_score( proc, p, score_fname )) != kOkRC )
{
goto errLabel;
}
if((rc = var_register(proc,kAnyChIdx,
kStopPId, "stop", kBaseSfxId )) != kOkRC )
{
goto errLabel;
}
if((rc = var_register_and_set(proc,kAnyChIdx,
kDoneFlPId,"done_fl", kBaseSfxId, false)) != kOkRC )
{
goto errLabel;
}
// allocate the output recd array
if((rc = _alloc_recd_array( proc, "out", kBaseSfxId, kAnyChIdx, nullptr, 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->midi_fld_idx = recd_type_field_index( p->recd_array->type, "midi");
p->loc_fld_idx = recd_type_field_index( p->recd_array->type, "loc");
p->meas_fld_idx = recd_type_field_index( p->recd_array->type, "meas");
p->bVId = bloc!=0 ? (unsigned)kBLocPId : (bmeas !=0 ? (unsigned)kBMeasPId : kInvalidId);
p->eVId = eloc!=0 ? (unsigned)kELocPId : (emeas !=0 ? (unsigned)kEMeasPId : kInvalidId);
p->end_msg_idx = kInvalidIdx;
p->midiChMsgA[kAllNotesOffMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kAllNotesOffMdId, .d1=0 };
p->midiChMsgA[kResetAllCtlsMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kResetAllCtlsMdId, .d1=0 };
p->midiChMsgA[kDampPedalDownMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kSustainCtlMdId, .d1=64 };
p->midiChMsgA[kSostPedalDownMsgIdx] = { .timeStamp={ .tv_sec=0, .tv_nsec=0}, .devIdx=kInvalidIdx, .portIdx=kInvalidIdx, .uid=0, .ch=0, .status=midi::kCtlMdId, .d0=midi::kSostenutoCtlMdId, .d1=64 };
p->midiMsgA[kAllNotesOffMsgIdx].midi = p->midiChMsgA + kAllNotesOffMsgIdx;
p->midiMsgA[kResetAllCtlsMsgIdx].midi = p->midiChMsgA + kResetAllCtlsMsgIdx;
p->midiMsgA[kDampPedalDownMsgIdx].midi = p->midiChMsgA + kDampPedalDownMsgIdx;
p->midiMsgA[kSostPedalDownMsgIdx].midi = p->midiChMsgA + kSostPedalDownMsgIdx;
if( eloc==0 && emeas==0 )
{
var_set(proc,kELocPId,kAnyChIdx,p->score_end_loc);
var_set(proc,kEMeasPId,kAnyChIdx,p->score_end_meas+1 );
}
p->state = kIdleStateId;
errLabel:
return rc;
}
rc_t _destroy( proc_t* proc, inst_t* p )
{
rc_t rc = kOkRC;
recd_array_destroy(p->recd_array);
mem::release(p->msgA);
mem::release(p->chMsgA);
return rc;
}
rc_t _value( proc_t* proc, inst_t* p, variable_t* var )
{
rc_t rc = kOkRC;
if( proc->ctx->isInRuntimeFl )
{
switch( var->vid )
{
case kStartPId:
p->start_trig_fl = true;
printf("Start Clicked\n");
break;
case kStopPId:
p->stop_trig_fl = true;
printf("Stop Clicked\n");
break;
case kBLocPId:
case kBMeasPId:
p->bVId = var->vid;
break;
case kELocPId:
case kEMeasPId:
p->eVId = var->vid;
break;
}
}
return rc;
}
rc_t _do_begin_stopping( proc_t* proc, inst_t* p, unsigned stopping_ms )
{
@ -492,10 +514,12 @@ namespace cw
return kOkRC;
}
rc_t _set_output_record( inst_t* p, rbuf_t* rbuf, const msg_t* m, recd_t* r )
rc_t _set_output_record( inst_t* p, rbuf_t* rbuf, const msg_t* m )
{
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 )
{
@ -517,8 +541,8 @@ namespace cw
rc_t rc = kOkRC;
// copy the 'all-note-off','all-ctl-off' msg into output record array
_set_output_record(p,rbuf,p->midiMsgA + kAllNotesOffMsgIdx, p->recd_array->recdA + rbuf->recdN);
_set_output_record(p,rbuf,p->midiMsgA + kResetAllCtlsMsgIdx, p->recd_array->recdA + rbuf->recdN);
_set_output_record(p,rbuf,p->midiMsgA + kAllNotesOffMsgIdx);
_set_output_record(p,rbuf,p->midiMsgA + kResetAllCtlsMsgIdx);
p->state = kIdleStateId;
@ -527,7 +551,7 @@ namespace cw
cwLogInfo("Stopped.");
return kOkRC;
return rc;
}
@ -540,12 +564,17 @@ namespace cw
if( p->state != kIdleStateId )
if((rc = _do_stop_now(proc,p,rbuf)) != kOkRC )
goto errLabel;
// BUG BUG BUG - using measure instead of loc because when we use loc
// we go to the wrong place
var_get( proc, kBLocPId, kAnyChIdx, bloc );
var_get( proc, kBMeasPId, kAnyChIdx, bloc );
printf("Starting at loc:%i\n",bloc);
// Rewind the current position to the begin location
for(i=0; i<p->msgN; ++i)
if( p->msgA[i].loc == bloc )
if( p->msgA[i].meas >= bloc )
{
p->sample_idx = p->msgA[i].sample_idx;
p->msg_idx = i;
@ -553,13 +582,13 @@ namespace cw
// if the damper pedal is down at the start location
if( p->msgA[i].flags & kDampPedalDownFl )
_set_output_record(p,rbuf,p->midiMsgA + kDampPedalDownMsgIdx, p->recd_array->recdA + rbuf->recdN);
_set_output_record(p,rbuf,p->midiMsgA + kDampPedalDownMsgIdx);
// if the sostenuto pedal was put down at the start location
if( p->msgA[i].flags & kSostPedalDownFl )
_set_output_record(p,rbuf,p->midiMsgA + kSostPedalDownMsgIdx, p->recd_array->recdA + rbuf->recdN);
_set_output_record(p,rbuf,p->midiMsgA + kSostPedalDownMsgIdx);
cwLogInfo("New current: msg_idx:%i meas:%i loc:%i",p->msg_idx, p->msgA[i].meas, p->msgA[i].loc );
cwLogInfo("New current: msg_idx:%i meas:%i loc:%i %i",p->msg_idx, p->msgA[i].meas, p->msgA[i].loc, bloc );
break;
}
@ -631,7 +660,6 @@ namespace cw
// transmit all msgs, beginning with the msg at p->msg_idx, whose 'sample_idx' is <= p->sample_idx
while( p->msg_idx < p->msgN && p->sample_idx >= p->msgA[p->msg_idx].sample_idx )
{
recd_t* r = p->recd_array->recdA + rbuf->recdN;
msg_t* m = p->msgA + p->msg_idx;
// if the end-loc was encountered
@ -640,16 +668,12 @@ namespace cw
_do_begin_stopping(proc,p,p->stopping_ms);
}
// if the base pointer of the output recd array has not yet been set
if( rbuf->recdA == nullptr )
rbuf->recdA = r;
bool note_on_fl = midi::isNoteOn(m->midi->status, m->midi->d1);
// 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) )
{
_set_output_record(p, rbuf, m, r );
_set_output_record(p, rbuf, m );
if( note_on_fl )
p->note_cnt += 1;
@ -680,119 +704,6 @@ namespace cw
errLabel:
return rc;
}
/*
rc_t _exec( proc_t* proc, inst_t* p )
{
rc_t rc = kOkRC;
rbuf_t* rbuf = nullptr;
// if the begin loc/meas was changed
if( p->bVId != kInvalidId )
{
_on_new_begin_loc(proc,p,p->bVId);
p->bVId = kInvalidId;
}
// if the end loc/meas was changed
if( p->eVId != kInvalidId )
{
_on_new_end_loc(proc,p,p->eVId);
p->eVId = kInvalidId;
}
// if the start button was clicked
if( p->start_trig_fl )
{
_on_start_clicked(proc,p);
p->start_trig_fl = false;
}
// if the stop button was clicked
if( p->stop_trig_fl )
{
_on_stop_clicked(proc,p);
p->stop_trig_fl = false;
}
// if in idle state then there is noting to do
if( p->state == kIdleStateId )
goto errLabel;
// advance sample_idx to the end sample associated with this cycle
p->sample_idx += proc->ctx->framesPerCycle;
// get the output variable
if((rc = var_get(proc,kOutPId,kAnyChIdx,rbuf)) != kOkRC )
rc = cwLogError(kInvalidStateRC,"The MIDI file instance '%s' does not have a valid MIDI output buffer.",proc->label);
else
{
if( p->state == kStoppedStateId )
{
rc = _do_stop_now(proc,p,rbuf);
}
else
{
rbuf->recdA = nullptr;
rbuf->recdN = 0;
// transmit all msgs, beginning with the msg at p->msg_idx, whose 'sample_idx' is <= p->sample_idx
while( p->msg_idx < p->msgN && p->sample_idx >= p->msgA[p->msg_idx].sample_idx )
{
recd_t* r = p->recd_array->recdA + rbuf->recdN;
msg_t* m = p->msgA + p->msg_idx;
bool note_on_fl = false;
// if the end-loc was encountered
if( p->state==kPlayStateId && p->end_msg_idx != kInvalidIdx && p->msg_idx > p->end_msg_idx )
{
_do_begin_stopping(proc,p,p->stopping_ms);
}
// if the base pointer of the output recd array has not yet been set
if( rbuf->recdA == nullptr )
rbuf->recdA = r;
// if we are in play-state and this is a note-on
if( p->state==kPlayStateId && (note_on_fl = midi::isNoteOn(m->midi->status, m->midi->d1)) )
p->note_cnt += 1;
// if this is a note-off
if( midi::isNoteOff(m->midi->status, m->midi->d1 ) && p->note_cnt>0 )
p->note_cnt -= 1;
// 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) )
{
_set_output_record(p, rbuf, m, r );
}
p->msg_idx += 1;
// track the current measure
if( m->meas > p->cur_meas )
{
cwLogInfo("meas:%i",m->meas);
p->cur_meas = m->meas;
}
} // end-while
if( (p->state==kStoppingStateId && (p->note_cnt == 0 || p->sample_idx> p->stopping_sample_idx)) || p->msg_idx == p->msgN )
{
p->state = kStoppedStateId; // this will be noticed in the next execution
}
}
}
errLabel:
return rc;
}
*/
rc_t _report( proc_t* proc, inst_t* p )
{ return kOkRC; }
@ -1937,6 +1848,9 @@ namespace cw
goto errLabel;
}
//preset_sel::report(p->psH);
//preset_sel::report_presets(p->psH);
// The location is coming from a 'record', get the location field.
if((p->loc_fld_idx = recd_type_field_index( rbuf->type, "loc")) == kInvalidIdx )
{

View File

@ -157,6 +157,67 @@ namespace cw
}
// Incr the var->modN value and put the var pointer in var->proc->modVarMapA[]
// where it will be picked up by a later call to _mod-var_map_dispatch().
// This function runs in a multi-thread context.
rc_t _mod_var_map_update( variable_t* var )
{
// if the var is in already modVarMapA[] then there is nothing to do
// (use acquire to prevent rd/wr from moving before this op)
if( var->modN.load(std::memory_order_acquire) > 0 )
return kOkRC;
// reserve a slot in proc->modVarMapA[]
// (use acquire to prevent rd/wr from moving before this op)
if( var->proc->modVarMapFullCnt.fetch_add(1,std::memory_order_acquire) >= var->proc->modVarMapN )
return kBufTooSmallRC;
// Get the next empty slot in proc->modVarMapA[]
// (use acquire to prevent rd/wr from moving before this op)
unsigned idx = var->proc->modVarMapHeadIdx.fetch_add(1,std::memory_order_acquire) % var->proc->modVarMapN;
var->proc->modVarMapA[ idx ] = var;
// mark the var as in the list
var->modN.fetch_add(1,std::memory_order_release);
return kOkRC;
}
// Call proc->proc_desc->value() on every var in the proc->modVarMapA[].
// This function is called inside proc->proc_desc->exec().
rc_t _mod_var_map_dispatch( proc_t* proc, bool callback_fl )
{
// get the count of variables to be updated
unsigned n = proc->modVarMapFullCnt.load( std::memory_order_acquire );
if( n )
{
if( callback_fl )
{
for(unsigned i=0; i<n; ++i)
{
// get a pointer to the var that has been marked as modified
variable_t* var = proc->modVarMapA[ proc->modVarMapTailIdx ];
// callback to inform the proc that the var has changed
proc->class_desc->members->value( var->proc, var );
// mark this var as having been removed from the modVarMapA[]
var->modN.store(0,std::memory_order_relaxed );
// increment modVarMapA[]'s tail index
proc->modVarMapTailIdx = (proc->modVarMapTailIdx + 1) % proc->modVarMapN;
}
}
// decrement the count of elemnts in the modVarMapA[]
proc->modVarMapFullCnt.fetch_sub(n, std::memory_order_release );
}
return kOkRC;
}
// 'argTypeFlag' is the type (tflag) of 'val'.
template< typename T >
@ -950,6 +1011,7 @@ void cw::flow::proc_destroy( proc_t* proc )
mem::release(proc->label);
mem::release(proc->varMapA);
mem::release(proc->modVarMapA);
mem::release(proc);
}

View File

@ -114,6 +114,9 @@ namespace cw
ui_var_t* ui_var; // this variables UI description
std::atomic<struct variable_str*> ui_var_link; // UI update var link based on flow_t ui_var_head;
std::atomic<unsigned> modN; // count of modifactions made to this variable during this cycl
} variable_t;
@ -147,6 +150,12 @@ namespace cw
unsigned varMapN; // varMapN = varMapIdN * varMapChN
variable_t** varMapA; // varMapA[ varMapN ] = allows fast lookup from ('vid','chIdx) to variable
variable_t** modVarMapA; // modVarMapA[ modVarMapN ]
unsigned modVarMapN; // modVarMapN == varMapN
unsigned modVarMapTailIdx; // index of next full slot in varMapA[]
std::atomic<unsigned> modVarMapFullCnt; // count of elements in modVarMapA[]
std::atomic<unsigned> modVarMapHeadIdx; // index of next empty slot in varMapA[]
// For 'poly' proc's 'internal_net' is a list linked by network_t.poly_link.
struct network_str* internal_net;
unsigned internal_net_cnt; // count of hetergenous networks contained in the internal_net linked list.

140
cwMpScNbCircQueue.h Normal file
View File

@ -0,0 +1,140 @@
#ifndef cwMpScNbCircQueue_h
#define cwMpScNbCircQueue_h
namespace cw
{
namespace mp_sc_nb_circ_queue
{
template< typename T >
struct node_str
{
std::atomic<struct node_str<T>* > next;
T t;
};
template< typename T >
struct cq_str
{
struct node_str<T>* array;
unsigned alloc_cnt;
unsigned index_mask;
std::atomic<unsigned> res_cnt;
std::atomic<unsigned> res_head_idx;
std::atomic<struct node_str<T>*> head; // last-in
struct node_str<T>* tail; // first-out
struct node_str<T> stub; // dummy node
};
template< typename T >
struct cq_str<T>* create( unsigned alloc_cnt )
{
struct cq_str<T>* p = mem::allocZ<struct cq_str<T>>();
// increment alloc_cnt to next power of two so we can use
// a bit mask rather than modulo to keep the head and tail indexes in range
alloc_cnt = math::nextPowerOfTwo(alloc_cnt);
p->array = mem::allocZ< struct node_str<T> >(alloc_cnt);
p->alloc_cnt = alloc_cnt;
p->index_mask = alloc_cnt - 1; // decr. alloc_cnt by 1 to create an index mask
p->res_cnt.store(0);
p->res_head_idx.store(0);
p->stub.next.store(nullptr);
p->head.store(&p->stub);
p->tail = &p->stub;
for(unsigned i=0; i<alloc_cnt; ++i)
p->array[i].next.store(nullptr);
return p;
}
template< typename T >
rc_t destroy( struct cq_str<T>*& p_ref)
{
if( p_ref != nullptr )
{
mem::release(p_ref->array);
mem::release(p_ref);
}
return kOkRC;
}
template< typename T >
rc_t push( struct cq_str<T>* p, T value )
{
rc_t rc = kOkRC;
// allocate a slot in the array - on succes this thread owns space on the array
// (acquire prevents reordering with rd/wr ops below - sync with fetch_sub() below)
if( p->res_cnt.fetch_add(1,std::memory_order_acquire) >= p->alloc_cnt )
{
// a slot is not available - undo the increment
p->res_cnt.fetch_sub(1,std::memory_order_release);
rc = kBufTooSmallRC;
}
else
{
// get the location of the reserved slot and then advance the res_head_idx.
unsigned idx = p->res_head_idx.fetch_add(1,std::memory_order_acquire) & p->index_mask;
struct node_str<T>* n = p->array + idx;
// store the pushed element in the reserved slot
n->t = value;
n->next.store(nullptr);
// Note that the elements of the queue are only accessed from the front of the queue (tail).
// New nodes are added to the end of the list (head).
// New node will therefore always have it's next ptr set to null.
// 1. Atomically set head to the new node and return 'old-head'.
// We use acq_release to prevent code movement above or below this instruction.
struct node_str<T>* prev = p->head.exchange(n,std::memory_order_acq_rel);
// Note that at this point only the new node may have the 'old-head' as it's predecssor.
// Other threads may therefore safely interrupt at this point - they will
// have the new node as their predecessor. Note that none of these nodes are accessible
// yet because __tail next__ pointer is still pointing to the 'old-head' - whose next pointer
// is still null.
// 2. Set the old-head next pointer to the new node (thereby adding the new node to the list)
prev->next.store(n,std::memory_order_release); // RELEASE 'next' to consumer
}
return rc;
}
template< typename T >
rc_t pop( struct cq_str<T>* p, T& value_ref )
{
rc_t rc = kEofRC;
// We always access the tail element through tail->next. Always accessing the
// next tail element via the tail->next pointer is critical to correctness.
// See note in push().
struct node_str<T>* next = p->tail->next.load(std::memory_order_acquire); // ACQUIRE 'next' from producer
if( next != nullptr )
{
value_ref = next->t;
p->tail = next;
// decrease the count of full slots
p->res_cnt.fetch_sub(1,std::memory_order_release);
rc = kOkRC;
}
return rc;
}
}
}
#endif

316
cwMtQueueTester.cpp Normal file
View File

@ -0,0 +1,316 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTest.h"
#include "cwMem.h"
#include "cwTime.h"
#include "cwObject.h"
#include "cwMath.h"
#include "cwNbMpScQueue.h"
#include "cwMpScNbCircQueue.h"
#include "cwMtQueueTester.h"
#include "cwThread.h"
#include "cwThreadMach.h"
#include "cwFile.h"
namespace cw
{
namespace mt_queue_tester
{
struct shared_str;
typedef struct test_str
{
unsigned id; // thread id
unsigned iter; // execution counter
unsigned value; //
struct shared_str* share; // pointer to global shared data
} test_t;
typedef test_t cq_data_t;
typedef struct mp_sc_nb_circ_queue::cq_str<cq_data_t> cq_t;
typedef struct shared_str
{
nbmpscq::handle_t qH;
cq_t* cq;
std::atomic<unsigned> cnt;
} test_share_t;
bool _nbmpscq_threadFunc( void* arg )
{
test_t* t = (test_t*)arg;
// get and increment a global shared counter
t->value = t->share->cnt.fetch_add(1,std::memory_order_acq_rel);
// push the current thread instance record
push(t->share->qH,t,sizeof(test_t));
// incrmemen this threads exec. counter
t->iter += 1;
sleepMs( rand() & 0xf );
return true;
}
void nbmpscq_main( file::handle_t fH, nbmpscq::handle_t qH )
{
nbmpscq::blob_t b = get(qH);
if( b.blob != nullptr )
{
test_t* t = (test_t*)b.blob;
printf(fH,"%i %i %i %i\n",t->id,t->iter,t->value,b.blobByteN);
advance(qH);
}
}
bool _cq_threadFunc( void* arg )
{
rc_t rc = kOkRC;
test_t* t = (test_t*)arg;
// get and increment a global shared counter
t->value = t->share->cnt.fetch_add(1,std::memory_order_acq_rel);
// push the current thread instance record
if((rc = mp_sc_nb_circ_queue::push<cq_data_t>(t->share->cq, *t )) != kOkRC )
{
cwLogError(rc,"Circular queue is full.");
}
// incrmement this threads exec. counter
t->iter += 1;
sleepUs( rand() & 0xf );
return true;
}
unsigned fail_N = 0;
void cq_main( file::handle_t fH, cq_t* cq )
{
cq_data_t t;
unsigned res_cnt = cq->res_cnt.load();
if( mp_sc_nb_circ_queue::pop<cq_data_t>(cq, t ) == kOkRC )
{
printf(fH,"%i %i %i %i\n",t.id,t.iter,t.value,res_cnt);
fail_N = 0;
}
else
{
if( fail_N < 10 )
{
//printf(fH,"F: %i %i : %i %i\n",res_idx,res_cnt,cq->tail_idx,value);
fail_N += 1;
}
}
}
}
rc_t _check_results( const char* fname )
{
rc_t rc = kOkRC;
unsigned lineN = 0;
unsigned* valueA = nullptr;
char* lineBuf = nullptr;
unsigned lineCharN = 0;
file::handle_t fH;
cwLogInfo("Validation started ...");
if((rc = open(fH,fname,file::kReadFl)) != kOkRC )
{
rc = cwLogError(rc,"Result file open failed on '%s'.",fname);
goto errLabel;
}
if((rc = file::lineCount(fH,&lineN)) != kOkRC )
{
rc = cwLogError(rc,"Line count could not be deteremined on '%s'.",fname);
goto errLabel;
}
if( lineN == 0 )
{
rc = cwLogError(rc,"Empty file detected on '%s'.",fname);
goto errLabel;
}
lineN -= 1;
valueA = mem::allocZ<unsigned>(lineN);
for(unsigned i=0; i<lineN; ++i)
{
unsigned v0,v1,v2,v3;
if((rc = getLineAuto(fH,&lineBuf,&lineCharN)) != kOkRC )
{
rc = cwLogError(rc,"Line buffer load failed on line index %i of %i in '%s'.",i,lineN,fname);
goto errLabel;
}
if( lineCharN == 0 )
{
rc = cwLogError(rc,"A blank line was encountered at line index %i in '%s'.",i,fname);
goto errLabel;
}
if( sscanf(lineBuf,"%i %i %i %i",&v0,&v1,&v2,&v3) != 4 )
{
rc = cwLogError(rc,"Line parse failed at line index %i in '%s'.",i,fname);
goto errLabel;
}
valueA[i] = v2;
}
std::sort( valueA, valueA+lineN, [](auto a, auto b){return a<b;});
for(unsigned i=0,j=0; i<lineN; ++i,++j)
if( valueA[i] != j )
{
unsigned k = j;
if(i+1 < lineN)
k = valueA[i+1] - 1;
cwLogInfo("Missing: %i to %i",j,k);
j = k;
}
cwLogInfo("Validation complete : %i rows.",lineN);
errLabel:
close(fH);
mem::release(valueA);
mem::release(lineBuf);
return rc;
}
}
cw::rc_t cw::mt_queue_tester::test( const object_t* cfg )
{
rc_t rc=kOkRC,rc0,rc1,rc2,rc3;
unsigned threadN = 2;
test_t* threadA = nullptr;
unsigned blkN = 2;
unsigned blkByteN = 1024;
unsigned cqEleCnt = 1024*64;
bool cqFl = false;
const char* out_fname = nullptr;
time::spec_t t0 = time::current_time();
unsigned testDurMs = 0;
thread::cbFunc_t thread_func= nullptr;
test_share_t share;
nbmpscq::handle_t qH;
thread_mach::handle_t tmH;
file::handle_t fH;
if((rc = cfg->getv("blkN",blkN,
"blkByteN",blkByteN,
"circQueueEleCnt",cqEleCnt,
"circQueueFl",cqFl,
"testDurMs",testDurMs,
"threadN",threadN,
"out_fname",out_fname)) != kOkRC )
{
rc = cwLogError(rc,"Test params parse failed.");
goto errLabel;
}
if((rc = file::open(fH,out_fname,file::kWriteFl)) != kOkRC )
{
rc = cwLogError(rc,"Error creating the output file:%s",cwStringNullGuard(out_fname));
goto errLabel;
}
if( threadN == 0 )
{
rc = cwLogError(kInvalidArgRC,"The 'threadN' parameter must be greater than 0.");
goto errLabel;
}
// create the thread intance records
threadA = mem::allocZ<test_t>(threadN);
// create the cwNbMpScQueue queue
if((rc = create( qH, blkN, blkByteN )) != kOkRC )
{
rc = cwLogError(rc,"nbmpsc create failed.");
goto errLabel;
}
thread_func = cqFl ? _cq_threadFunc : _nbmpscq_threadFunc;
share.cq = mp_sc_nb_circ_queue::create<cq_data_t>(cqEleCnt); // create the circular queue
share.qH = qH;
share.cnt.store(0);
for(unsigned i=0; i<threadN; ++i)
{
threadA[i].id = i;
threadA[i].share = &share;
}
// create the thread machine
if((rc = thread_mach::create( tmH, thread_func, threadA, sizeof(test_t), threadN )) != kOkRC )
{
rc = cwLogError(rc,"Thread machine create failed.");
goto errLabel;
}
// start the thread machine
if((rc = thread_mach::start(tmH)) != kOkRC )
{
cwLogError(rc,"Thread machine start failed.");
goto errLabel;
}
// run the test for 'testDurMs' milliseconds
while( time::elapsedMs(t0) < testDurMs )
{
if( cqFl )
cq_main(fH,share.cq);
else
nbmpscq_main(fH,qH);
}
errLabel:
file::close(fH);
if((rc0 = thread_mach::destroy(tmH)) != kOkRC )
cwLogError(rc0,"Thread machine destroy failed.");
if((rc1 = destroy(qH)) != kOkRC )
cwLogError(rc1,"nbmpsc queue destroy failed.");
if((rc2 = destroy(share.cq)) != kOkRC )
cwLogError(rc2,"mp_sc_nb_circ_queue destroy failed.");
if((rc3 = _check_results(out_fname)) != kOkRC )
cwLogError(rc3,"Check failed.");
mem::release(threadA);
return rcSelect(rc,rc0,rc1,rc2,rc3);
}

7
cwMtQueueTester.h Normal file
View File

@ -0,0 +1,7 @@
namespace cw
{
namespace mt_queue_tester
{
rc_t test( const object_t* cfg );
}
}

View File

@ -10,9 +10,6 @@
#include "cwNbMpScQueue.h"
#include "cwThread.h"
#include "cwThreadMach.h"
#include "cwFile.h"
namespace cw
{
@ -132,42 +129,7 @@ namespace cw
b.rc = kOkRC;
}
typedef struct shared_str
{
handle_t qH;
std::atomic<unsigned> cnt;
} test_share_t;
typedef struct test_str
{
unsigned id; // thread id
unsigned iter; // execution counter
unsigned value; //
test_share_t* share; // pointer to global shared data
} test_t;
bool _test_threadFunc( void* arg )
{
test_t* t = (test_t*)arg;
// get and increment a global shared counter
t->value = t->share->cnt.fetch_add(1,std::memory_order_acq_rel);
// push the current thread instance record
push(t->share->qH,t,sizeof(test_t));
// incrmemen this threads exec. counter
t->iter += 1;
sleepMs( rand() & 0xf );
return true;
}
void _block_report( nbmpscq_t* p )
{
block_t* b = p->blockL;
@ -301,14 +263,20 @@ cw::rc_t cw::nbmpscq::push( handle_t h, const void* blob, unsigned blobByteN )
b->eleN.fetch_add(1,std::memory_order_release);
n->next.store(nullptr);
// Note that the elements of the queue are only accessed from the end of the queue (tail).
// New nodes can therefore safely be updated in two steps:
// Note that the elements of the queue are only accessed from the front of the queue (tail).
// New nodes are added to the end of the list (head).
// New node will therefore always have it's next ptr set to null.
// 1. Atomically set _head to the new node and return 'old-head'
// We use acq_release to prevent code movement above or below this instruction.
node_t* prev = p->head.exchange(n,std::memory_order_acq_rel);
// Note that at this point only the new node may have the 'old-head' as it's predecssor.
// Other threads may therefore safely interrupt at this point.
// Other threads may therefore safely interrupt at this point - they will
// have the new node as their predecessor. Note that none of these nodes are accessible
// yet because __tail next__ pointer is still pointing to the 'old-head' - whose next pointer
// is still null.
// 2. Set the old-head next pointer to the new node (thereby adding the new node to the list)
prev->next.store(n,std::memory_order_release); // RELEASE 'next' to consumer
@ -341,7 +309,9 @@ cw::nbmpscq::blob_t cw::nbmpscq::get( handle_t h )
blob_t blob;
nbmpscq_t* p = _handleToPtr(h);
// We always access the tail element through tail->next.
// We always access the tail element through tail->next. Always accessing the
// next tail element via the tail->next pointer is critical to correctness.
// See note in push().
node_t* node = p->tail->next.load(std::memory_order_acquire); // ACQUIRE 'next' from producer
_init_blob( blob, node );
@ -436,107 +406,3 @@ unsigned cw::nbmpscq::count( handle_t h )
return eleN;
}
cw::rc_t cw::nbmpscq::test( const object_t* cfg )
{
rc_t rc=kOkRC,rc0,rc1;
unsigned testArrayN = 2;
test_t* testArray = nullptr;
unsigned blkN = 2;
unsigned blkByteN = 1024;
const char* out_fname = nullptr;
time::spec_t t0 = time::current_time();
unsigned testDurMs = 0;
test_share_t share;
handle_t qH;
thread_mach::handle_t tmH;
file::handle_t fH;
if((rc = cfg->getv("blkN",blkN,
"blkByteN",blkByteN,
"testDurMs",testDurMs,
"threadN",testArrayN,
"out_fname",out_fname)) != kOkRC )
{
rc = cwLogError(rc,"Test params parse failed.");
goto errLabel;
}
if((rc = file::open(fH,out_fname,file::kWriteFl)) != kOkRC )
{
rc = cwLogError(rc,"Error creating the output file:%s",cwStringNullGuard(out_fname));
goto errLabel;
}
if( testArrayN == 0 )
{
rc = cwLogError(kInvalidArgRC,"The 'threadN' parameter must be greater than 0.");
goto errLabel;
}
// create the thread intance records
testArray = mem::allocZ<test_t>(testArrayN);
// create the queue
if((rc = create( qH, blkN, blkByteN )) != kOkRC )
{
rc = cwLogError(rc,"nbmpsc create failed.");
goto errLabel;
}
share.qH = qH;
share.cnt.store(0);
for(unsigned i=0; i<testArrayN; ++i)
{
testArray[i].id = i;
testArray[i].share = &share;
}
// create the thread machine
if((rc = thread_mach::create( tmH, _test_threadFunc, testArray, sizeof(test_t), testArrayN )) != kOkRC )
{
rc = cwLogError(rc,"Thread machine create failed.");
goto errLabel;
}
// start the thread machine
if((rc = thread_mach::start(tmH)) != kOkRC )
{
cwLogError(rc,"Thread machine start failed.");
goto errLabel;
}
// run the test for 'testDurMs' milliseconds
while( time::elapsedMs(t0) < testDurMs )
{
blob_t b = get(qH);
if( b.blob != nullptr )
{
test_t* t = (test_t*)b.blob;
printf(fH,"%i %i %i %i\n",t->id,t->iter,t->value,b.blobByteN);
advance(qH);
}
}
errLabel:
file::close(fH);
if((rc0 = thread_mach::destroy(tmH)) != kOkRC )
cwLogError(rc0,"Thread machine destroy failed.");
if((rc1 = destroy(qH)) != kOkRC )
cwLogError(rc1,"nbmpsc queue destroy failed.");
if( testArray != nullptr )
printf("P:%i %i\n",testArray[0].iter, testArray[1].iter);
// TODO: read back the file and verify that none of the
// global incrment values were dropped.
mem::release(testArray);
return rcSelect(rc,rc0,rc1);
}

View File

@ -27,6 +27,10 @@ Pop
2. decr. block->ele_count
3. if the ele-count is 0 and write-offset is invalid
reset the write-offset to 0.
This code is tested in cwMtQueueTester.h/cpp.
*/
@ -89,8 +93,6 @@ namespace cw
// Count of elements in the queue.
unsigned count( handle_t h );
rc_t test( const object_t* cfg );
}
}

View File

@ -945,6 +945,10 @@ resolvable without more information.
- 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
- All outputs must be set via var_set() call, otherwise proc's that rely on noticing changed variables
will not work. For example the 'print' proc does not work for record,midi,audio because
in general these types are not set via calls to var_set().
- The following two tasks need more consideration. As it is variables assume that aggregate
types are destroyed on exit. This is very convenient. Consider using 'symbols' to represent
strings, consider adding a 'const-string' type to eliminate memory allocation of string assignment
@ -1099,6 +1103,7 @@ Processors that have mandatory signal inputs should never need to also have an s
- Implement matrix types.
- Add a 'trigger' data type. The 'kAllTId' isn't really doing anything.
Perhaps this could be a 'symbol' data type?
- There should be special logging macros inside procs that automatically log the instance name.
@ -1702,3 +1707,12 @@ Given that the system isn't currently limited in this way maybe it doesn't matte
- Processor instance presets have not been implemented.
- Preset application request deferrment has not been implemented.
Variable Modifcation Tracking
------------------------------
The only thread that can write a variable value is the thread executing the variables proc.