Merge branch 'master' of gitea.larke.org:kevin/libcw

This commit is contained in:
kevin 2022-05-15 10:37:51 -04:00
commit f75510f624
30 changed files with 1855 additions and 670 deletions

View File

@ -2,6 +2,15 @@
# To Do
- The ui manageer should buffer the current valid value of a given control
so that the value can be accessed synchronously. This would prevent the application
from having to explicitely store all UI values and handle all the 'value' and 'echo'
request. It would support a model where the UI values get changed and then
read by the app (e.g. getUiValue( appId, valueRef)) just prior to being used.
As it is the UI values that are on the interface cannot be accessed synchronously
instead the app is forced to notice all 'value' changes and store the last legal value.
- Should a warning be issued by audioBuf functions which return a set of values:
muteFlags(),toneFlags(), gain( ... gainA) but where the size of the dest array
does not match the actual number of channesl?

View File

@ -605,7 +605,11 @@ namespace cw
int* dp = (int*)obuf;
while( sp < ep )
*dp++ = (int)(*sp++ * 0x7fffffff);
{
device::sample_t v = *sp++;
v = ((v > 1 ? 1 : v) < -1 ? -1 : v);
*dp++ = (int)(v * 0x7fffffff);
}
//*dp++ = (rand() - (RAND_MAX/2)) * 2;
}

View File

@ -785,16 +785,16 @@ namespace cw
}
template< typename T0, typename T1 >
void _cmSpecDist2Bump( struct obj_str<T0,T1>* p, T0* x, unsigned binCnt, T1 thresh, T1 expo)
void _cmSpecDist2Bump( struct obj_str<T0,T1>* p, double* x, unsigned binCnt, double thresh, double expo)
{
unsigned i = 0;
T1 minDb = -100.0;
double minDb = -100.0;
thresh = -fabs(thresh);
for(i=0; i<binCnt; ++i)
{
T1 y;
double y;
if( x[i] < minDb )
x[i] = minDb;
@ -813,7 +813,7 @@ namespace cw
}
template< typename T0, typename T1 >
void _cmSpecDist2BasicMode( struct obj_str<T0,T1>* p, T0* X1m, unsigned binCnt, T1 thresh, T1 upr, T1 lwr )
void _cmSpecDist2BasicMode( struct obj_str<T0,T1>* p, double* X1m, unsigned binCnt, double thresh, double upr, double lwr )
{
unsigned i=0;
@ -823,8 +823,8 @@ namespace cw
for(i=0; i<binCnt; ++i)
{
T0 a = fabs(X1m[i]);
T0 d = a - thresh;
double a = fabs(X1m[i]);
double d = a - thresh;
X1m[i] = -thresh;
@ -841,11 +841,11 @@ namespace cw
{
rc_t rc = kOkRC;
T0 X0m[binN];
T0 X1m[binN];
double X0m[binN];
double X1m[binN];
// take the mean of the the input magntitude spectrum
T0 u0 = vop::mean(magV,binN);
double u0 = vop::mean(magV,binN);
// convert magnitude to db (range=-1000.0 to 0.0)
vop::ampl_to_db(X0m, magV, binN );
@ -867,10 +867,10 @@ namespace cw
vop::db_to_ampl(X1m, X1m, binN );
// convert the mean input magnitude to db
T0 idb = 20*log10(u0);
double idb = 20*log10(u0);
// get the mean output magnitude spectra
T0 u1 = vop::mean(X1m,binN);
double u1 = vop::mean(X1m,binN);
if( idb > -150.0 )
{
@ -890,7 +890,6 @@ namespace cw
return rc;
}
}

View File

@ -63,6 +63,9 @@ bool cw::filesys::isDir( const char* dirStr )
errno = 0;
if( dirStr == nullptr )
return false;
if( stat(dirStr,&s) != 0 )
{
// if the dir does not exist
@ -81,6 +84,9 @@ bool cw::filesys::isFile( const char* fnStr )
struct stat s;
errno = 0;
if( fnStr == nullptr )
return false;
if( stat(fnStr,&s) != 0 )
{
@ -101,6 +107,9 @@ bool cw::filesys::isLink( const char* fnStr )
struct stat s;
errno = 0;
if( fnStr == nullptr )
return false;
if( lstat(fnStr,&s) != 0 )
{
// if the file does not exist
@ -314,6 +323,9 @@ char* cw::filesys::expandPath( const char* dir )
memset(&res,0,sizeof(res));
if( dir == nullptr )
return nullptr;
if((sysRC = wordexp(dir,&res,flags)) != 0)
{
switch(sysRC)

View File

@ -368,16 +368,18 @@ namespace cw
// since 'var' is on the 'any' channel the 'src' var must also be on the 'any' channel
assert( base_src_var->chIdx == kAnyChIdx );
printf("%s %s\n",inst->label,var->label);
//printf("%s %s\n",inst->label,var->label);
// for each var channel in the input var
for(variable_t* in_var = var->ch_link; in_var != nullptr; in_var=in_var->ch_link)
{
// locate the matching channel on the 'src' var
variable_t* svar = base_src_var;
for(; svar!=nullptr; svar=svar->ch_link)
if( svar->chIdx == in_var->chIdx )
break;
// connect the src->input var
_connect_vars( svar==nullptr ? base_src_var : svar, in_var);
}
}

View File

@ -40,6 +40,7 @@ namespace cw
unsigned fadeSmpN; //
unsigned net_idx;
} flow_network_t;
@ -54,6 +55,7 @@ namespace cw
flow::external_device_t* deviceA;
unsigned deviceN;
bool fadeInputFl;
} flow_cross_t;
flow_cross_t* _handleToPtr(handle_t h)
@ -208,9 +210,9 @@ namespace cw
flow::abuf_t* src = p->deviceA[devIdx].u.a.abuf;
flow::abuf_t* dst = net->deviceA[devIdx].u.a.abuf;
memset( dst->buf, 0, dst->chN * dst->frameN * sizeof(flow::sample_t));
memcpy( dst->buf, src->buf, dst->chN * dst->frameN * sizeof(flow::sample_t));
_fade_audio( src, dst, net );
//_fade_audio( src, dst, net );
}
void _zero_audio_output( flow_cross_t* p, flow_network_t* net, unsigned devIdx )
@ -253,11 +255,20 @@ namespace cw
template< typename T >
rc_t _set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, const T& value )
{
//rc_t rc = kOkRC;
rc_t rc = kOkRC;
flow_cross_t* p = _handleToPtr(h);
unsigned flow_idx = _get_flow_index(p, destId );
return set_variable_value( p->netA[ flow_idx ].flowH, inst_label, var_label, chIdx, value );
unsigned flow_idx = destId == kAllDestId ? 0 : _get_flow_index(p, destId );
unsigned flow_cnt = destId == kAllDestId ? p->netN : flow_idx + 1;
for(; flow_idx < flow_cnt; ++flow_idx )
if((rc = set_variable_value( p->netA[ flow_idx ].flowH, inst_label, var_label, chIdx, value )) != kOkRC )
{
cwLogError(rc,"Set variable value failed on cross-network index: %i.",flow_idx);
goto errLabel;
}
errLabel:
return rc;
}
@ -331,28 +342,33 @@ cw::rc_t cw::flow_cross::exec_cycle( handle_t h )
rc_t rc = kOkRC;
flow_cross_t* p = _handleToPtr(h);
// Note that all networks must be updated so that they maintain
// their state (e.g. delay line memory) event if their output
// is faded out
for(unsigned i=0; i<p->netN; ++i)
{
flow_network_t* net = p->netA + i;
if( net->stateId != kInactiveStateId )
{
// We generally don't want to fade the input because the state
// of the network delay lines would then be invalid when the
// network is eventually made active again
for(unsigned j=0; j<p->deviceN; ++j)
if( p->deviceA[j].typeId == flow::kAudioDevTypeId && cwIsFlag(p->deviceA[j].flags, flow::kInFl ) )
_update_audio_input( p, p->netA + i, j );
for(unsigned j=0; j<p->deviceN; ++j)
if( p->deviceA[j].typeId == flow::kAudioDevTypeId && cwIsFlag(p->deviceA[j].flags, flow::kInFl ) )
_update_audio_input( p, p->netA + i, j );
for(unsigned j=0; j<p->deviceN; ++j)
if( p->deviceA[j].typeId == flow::kAudioDevTypeId && cwIsFlag(p->deviceA[j].flags, flow::kOutFl ) )
// zero the audio device output buffers because we are about to sum into them
for(unsigned j=0; j<p->deviceN; ++j)
if( p->deviceA[j].typeId == flow::kAudioDevTypeId && cwIsFlag(p->deviceA[j].flags, flow::kOutFl ) )
_zero_audio_output( p, net, j );
flow::exec_cycle( net->flowH );
// update the network
flow::exec_cycle( net->flowH );
for(unsigned j=0; j<p->deviceN; ++j)
if( p->deviceA[j].typeId == flow::kAudioDevTypeId && cwIsFlag(p->deviceA[j].flags, flow::kOutFl ) )
_update_audio_output( p, net, j );
}
// sum the output from the network into the audio output device buffer
// (this is were newly active networks are faded in)
for(unsigned j=0; j<p->deviceN; ++j)
if( p->deviceA[j].typeId == flow::kAudioDevTypeId && cwIsFlag(p->deviceA[j].flags, flow::kOutFl ) )
_update_audio_output( p, net, j );
}
return rc;
@ -426,6 +442,9 @@ void cw::flow_cross::print( handle_t h )
{
flow_cross_t* p = _handleToPtr(h);
unsigned cur_flow_idx = _get_flow_index( p, kCurDestId );
unsigned next_flow_idx = _get_flow_index( p, kNextDestId );
printf("flow_cross: sr:%7.1f\n", p->srate );
printf("master devices:\n");
@ -434,7 +453,21 @@ void cw::flow_cross::print( handle_t h )
for(unsigned i=0; i<p->netN; ++i)
{
printf("cross network:%i \n",i);
const char* label = i==cur_flow_idx ? "Current" : (i==next_flow_idx ? "Next" : "");
printf("cross network:%i (%s)\n",i,label);
flow::print_network( p->netA[i].flowH );
}
}
void cw::flow_cross::print_network( handle_t h, destId_t destId )
{
flow_cross_t* p = _handleToPtr(h);
unsigned flow_idx = _get_flow_index( p, destId );
if( flow_idx == kInvalidIdx )
cwLogError(kInvalidArgRC,"The network id '%i' is not valid. Cannot print the network state.",destId);
else
flow::print_network( p->netA[flow_idx].flowH );
}

View File

@ -12,6 +12,7 @@ namespace cw
{
kCurDestId, // Apply value to the current flow network
kNextDestId, // Apply value to the next flow network (i.e. network which will be current following the next cross-fade)
kAllDestId, // Apply value to all the flow networks
} destId_t;
rc_t create( handle_t& hRef,
@ -38,6 +39,7 @@ namespace cw
rc_t begin_cross_fade( handle_t h, unsigned crossFadeMs );
void print( handle_t h );
void print_network( handle_t h, flow_cross::destId_t destId );
}
}

View File

@ -98,7 +98,7 @@ namespace cw
typedef struct
{
real_t value;
} inst_t;
@ -106,6 +106,7 @@ namespace cw
{
rc_t rc = kOkRC;
real_t in_value = 0.5;
ctx->userPtr = mem::allocZ<inst_t>();
if((rc = var_register_and_get( ctx, kAnyChIdx, kInPId, "in", in_value )) != kOkRC )
goto errLabel;
@ -122,20 +123,33 @@ namespace cw
}
rc_t destroy( instance_t* ctx )
{ return kOkRC; }
{
mem::release( ctx->userPtr );
return kOkRC;
}
rc_t value( instance_t* ctx, variable_t* var )
{ return kOkRC; }
{
return kOkRC;
}
rc_t exec( instance_t* ctx )
{
rc_t rc = kOkRC;
inst_t* inst = (inst_t*)(ctx->userPtr);
real_t value = 1;
var_get(ctx, kInPId, kAnyChIdx, value);
var_set(ctx, kOutPId, kAnyChIdx, value);
var_set(ctx, kInvOutPId, kAnyChIdx, (real_t)(1.0 - value) );
if( inst->value != value )
{
inst->value = value;
}
return rc;
}
@ -228,7 +242,9 @@ namespace cw
}
else
{
memcpy(abuf->buf,inst->ext_dev->u.a.abuf->buf, abuf->frameN*abuf->chN*sizeof(sample_t));
unsigned chN = std::min(inst->ext_dev->u.a.abuf->chN, abuf->chN );
unsigned frameN = std::min(inst->ext_dev->u.a.abuf->frameN, abuf->frameN );
memcpy(abuf->buf,inst->ext_dev->u.a.abuf->buf, frameN*chN*sizeof(sample_t));
}
return rc;
@ -316,8 +332,9 @@ namespace cw
rc = cwLogError(kInvalidStateRC,"The audio file instance '%s' does not have a valid input connection.",ctx->label);
else
{
unsigned n = src_abuf->frameN*src_abuf->chN;
unsigned chN = std::min(inst->ext_dev->u.a.abuf->chN, src_abuf->chN);
unsigned frameN = std::min(inst->ext_dev->u.a.abuf->frameN, src_abuf->frameN);
unsigned n = chN * frameN;
for(unsigned i=0; i<n; ++i)
inst->ext_dev->u.a.abuf->buf[i] += src_abuf->buf[i];
@ -384,7 +401,7 @@ namespace cw
cwLogInfo("Audio '%s' srate:%f chs:%i frames:%i %f seconds.",inst->filename,info.srate,info.chCnt,info.frameCnt, info.frameCnt/info.srate );
// create one output audio buffer
// create one output audio buffer - with the same configuration as the source audio file
rc = var_register_and_set( ctx, "out", kOutPId, kAnyChIdx, info.srate, info.chCnt, ctx->ctx->framesPerCycle );
errLabel:
@ -491,7 +508,7 @@ namespace cw
goto errLabel;
}
// create the audio file
// create the audio file with the same channel count as the incoming signal
if((rc = audiofile::create( inst->afH, inst->filename, src_abuf->srate, audioFileBits, src_abuf->chN)) != kOkRC )
{
rc = cwLogError(kOpFailRC,"The audio file create failed on '%s'.",cwStringNullGuard(inst->filename));
@ -577,10 +594,18 @@ namespace cw
kOutPId
};
typedef struct inst_str
{
unsigned n;
real_t vgain;
real_t gain;
} inst_t;
rc_t create( instance_t* ctx )
{
rc_t rc = kOkRC;
const abuf_t* abuf = nullptr; //
const abuf_t* abuf = nullptr; //
ctx->userPtr = mem::allocZ<inst_t>();
// get the source audio buffer
if((rc = var_register_and_get(ctx, kAnyChIdx,kInPId,"in",abuf )) != kOkRC )
@ -600,16 +625,32 @@ namespace cw
}
rc_t destroy( instance_t* ctx )
{ return kOkRC; }
{
inst_t* inst = (inst_t*)(ctx->userPtr);
mem::release(inst);
return kOkRC;
}
rc_t value( instance_t* ctx, variable_t* var )
{ return kOkRC; }
{
real_t value = 0;
inst_t* inst = (inst_t*)ctx->userPtr;
var_get(ctx,kGainPId,0,value);
if( inst->vgain != value )
{
inst->vgain = value;
//printf("VALUE GAIN: %s %s : %f\n", ctx->label, var->label, value );
}
return kOkRC;
}
rc_t exec( instance_t* ctx )
{
rc_t rc = kOkRC;
const abuf_t* ibuf = nullptr;
abuf_t* obuf = nullptr;
inst_t* inst = (inst_t*)(ctx->userPtr);
// get the src buffer
if((rc = var_get(ctx,kInPId, kAnyChIdx, ibuf )) != kOkRC )
@ -631,6 +672,13 @@ namespace cw
// apply the gain
for(unsigned j=0; j<ibuf->frameN; ++j)
osig[j] = gain * isig[j];
if( i==0 && gain != inst->gain )
{
inst->gain = gain;
//printf("EXEC GAIN: %s %f\n",ctx->label,gain);
//instance_print(ctx);
}
}
errLabel:
@ -684,13 +732,19 @@ namespace cw
if( abuf->chN )
{
unsigned selChN = 0;
inst->chSelMap = mem::allocZ<bool>(abuf->chN);
if((rc = var_channel_count(ctx,"select",selChN)) != kOkRC )
goto errLabel;
// register the gain
for(unsigned i=0; i<abuf->chN; ++i)
{
if((rc = var_register_and_get( ctx, i, kSelectPId, "select", inst->chSelMap[i] )) != kOkRC )
goto errLabel;
if( i < selChN )
if((rc = var_register_and_get( ctx, i, kSelectPId, "select", inst->chSelMap[i] )) != kOkRC )
goto errLabel;
if( inst->chSelMap[i] )
{
@ -1659,17 +1713,23 @@ namespace cw
if( var->chIdx != kAnyChIdx && var->chIdx < inst->sdN )
{
double val = 0;
spec_dist_t* sd = inst->sdA[ var->chIdx ];
switch( var->vid )
{
case kCeilingPId: var_get( var, inst->sdA[ var->chIdx ]->ceiling ); break;
case kExpoPId: var_get( var, inst->sdA[ var->chIdx ]->expo ); break;
case kThreshPId: var_get( var, inst->sdA[ var->chIdx ]->thresh ); break;
case kUprSlopePId: var_get( var, inst->sdA[ var->chIdx ]->uprSlope ); break;
case kLwrSlopePId: var_get( var, inst->sdA[ var->chIdx ]->lwrSlope ); break;
case kMixPId: var_get( var, inst->sdA[ var->chIdx ]->mix ); break;
case kCeilingPId: rc = var_get( var, val ); sd->ceiling = val; break;
case kExpoPId: rc = var_get( var, val ); sd->expo = val; break;
case kThreshPId: rc = var_get( var, val ); sd->thresh = val; break;
case kUprSlopePId: rc = var_get( var, val ); sd->uprSlope = val; break;
case kLwrSlopePId: rc = var_get( var, val ); sd->lwrSlope = val; break;
case kMixPId: rc = var_get( var, val ); sd->mix = val; break;
default:
cwLogWarning("Unhandled variable id '%i' on instance: %s.", var->vid, ctx->label );
}
//printf("sd: ceil:%f expo:%f thresh:%f upr:%f lwr:%f mix:%f : rc:%i val:%f\n",
// sd->ceiling, sd->expo, sd->thresh, sd->uprSlope, sd->lwrSlope, sd->mix, rc, val );
}
return rc;
@ -1833,20 +1893,24 @@ namespace cw
if( var->chIdx != kAnyChIdx && var->chIdx < inst->cmpN )
{
compressor_t* c = inst->cmpA[ var->chIdx ];
switch( var->vid )
{
case kBypassPId: var_get( var, inst->cmpA[ var->chIdx ]->bypassFl ); break;
case kInGainPId: var_get( var, inst->cmpA[ var->chIdx ]->inGain ); break;
case kOutGainPId: var_get( var, inst->cmpA[ var->chIdx ]->outGain ); break;
case kRatioPId: var_get( var, inst->cmpA[ var->chIdx ]->ratio_num ); break;
case kThreshPId: var_get( var, inst->cmpA[ var->chIdx ]->threshDb ); break;
case kAtkMsPId: var_get( var, tmp ); set_attack_ms(inst->cmpA[ var->chIdx ], tmp ); break;
case kRlsMsPId: var_get( var, tmp ); set_release_ms(inst->cmpA[ var->chIdx ], tmp ); break;
case kWndMsPId: var_get( var, tmp ); set_rms_wnd_ms(inst->cmpA[ var->chIdx ], tmp ); break;
case kBypassPId: rc = var_get( var, tmp ); c->bypassFl=tmp; break;
case kInGainPId: rc = var_get( var, tmp ); c->inGain=tmp; break;
case kOutGainPId: rc = var_get( var, tmp ); c->outGain=tmp; break;
case kRatioPId: rc = var_get( var, tmp ); c->ratio_num=tmp; break;
case kThreshPId: rc = var_get( var, tmp ); c->threshDb=tmp; break;
case kAtkMsPId: rc = var_get( var, tmp ); dsp::compressor::set_attack_ms(c, tmp ); break;
case kRlsMsPId: rc = var_get( var, tmp ); dsp::compressor::set_release_ms(c, tmp ); break;
case kWndMsPId: rc = var_get( var, tmp ); dsp::compressor::set_rms_wnd_ms(c, tmp ); break;
case kMaxWndMsPId: break;
default:
cwLogWarning("Unhandled variable id '%i' on instance: %s.", var->vid, ctx->label );
}
//printf("cmp byp:%i igain:%f ogain:%f rat:%f thresh:%f atk:%i rls:%i wnd:%i : rc:%i val:%f\n",
// c->bypassFl, c->inGain, c->outGain,c->ratio_num,c->threshDb,c->atkSmp,c->rlsSmp,c->rmsWndCnt,rc,tmp);
}
@ -1957,6 +2021,12 @@ namespace cw
goto errLabel;
}
if( delayMs > maxDelayMs )
{
cwLogWarning("'delayMs' (%i) is being reduced to 'maxDelayMs' (%i) on the delay instance:%s.",delayMs,maxDelayMs,ctx->label);
delayMs = maxDelayMs;
}
inst->maxDelayFrameN = std::max(inst->maxDelayFrameN, (unsigned)(fabs(maxDelayMs) * abuf->srate / 1000.0) );
inst->cntV[i] = (unsigned)(fabs(delayMs) * abuf->srate / 1000.0);
@ -1991,9 +2061,10 @@ namespace cw
rc_t exec( instance_t* ctx )
{
rc_t rc = kOkRC;
inst_t* inst = (inst_t*)ctx->userPtr;
const abuf_t* ibuf = nullptr;
abuf_t* obuf = nullptr;
inst_t* inst = (inst_t*)ctx->userPtr;
abuf_t* dbuf = inst->delayBuf;
// get the src buffer
if((rc = var_get(ctx,kInPId, kAnyChIdx, ibuf )) != kOkRC )
@ -2008,6 +2079,7 @@ namespace cw
{
sample_t* isig = ibuf->buf + i*ibuf->frameN;
sample_t* osig = obuf->buf + i*obuf->frameN;
sample_t* dsig = dbuf->buf + i*dbuf->frameN;
unsigned di = inst->idxV[i];
// if the delay is set to zero samples
@ -2018,8 +2090,8 @@ namespace cw
// otherwise the delay is non-zero positive sample count
for(unsigned j=0; j<ibuf->frameN; ++j)
{
osig[j] = inst->delayBuf->buf[di]; // read delay output
inst->delayBuf->buf[di] = isig[j]; // set delay input
osig[j] = dsig[di]; // read delay output
dsig[di] = isig[j]; // set delay input
di = (di+1) % inst->cntV[i]; // update the delay index
}
}

View File

@ -339,7 +339,19 @@ namespace cw
valRef = non_const_val;
return rc;
}
template< typename T >
rc_t _val_get_driver( const variable_t* var, T& valRef )
{
if( var == nullptr )
return cwLogError(kInvalidArgRC,"Cannnot get the value of a non-existent variable.");
if( var->value == nullptr )
return cwLogError(kInvalidStateRC,"No value has been assigned to the variable: %s.%s ch:%i.",cwStringNullGuard(var->inst->label),cwStringNullGuard(var->label),var->chIdx);
return _val_get(var->value,valRef);
}
rc_t _var_find_to_set( instance_t* inst, unsigned vid, unsigned chIdx, unsigned typeFl, variable_t*& varRef )
{
@ -390,39 +402,6 @@ namespace cw
}
// Find a variable: If an exact match on label,chIdx is found this is returned.
// Otherwise if the arg 'chIdx' is a numeric channel idx the 'any' var is returned.
// if the arg 'chIdx' is 'any' then the 'any' or the lowest numeric channel is returned
variable_t* _var_find_best( instance_t* inst, const char* label, unsigned chIdx )
{
/*
variable_t* v0 = nullptr;
variable_t* var = inst->varL;
for(; var!=nullptr; var=var->var_link)
if( textCompare(var->label,label) == 0 )
{
// if the channel matches exactly - return this var
if( var->chIdx == chIdx )
return var;
// if we encounter a var with 'any' channel - store this we will keep looking because we may still find an exact match
if( var->chIdx == kAnyChIdx )
v0 = var;
else
// if 'any' was requested then try to get ch==0 but a non-zero channel will suffice otherwise
if( chIdx == kAnyChIdx )
if( v0 == nullptr || var->chIdx < v0->chIdx )
v0 = var;
}
return v0;
*/
return _var_find_on_label_and_ch(inst,label,chIdx);
}
rc_t _validate_var_assignment( variable_t* var, unsigned typeFl )
{
if( cwIsFlag(var->varDesc->flags, kSrcVarFl ) )
@ -447,73 +426,73 @@ namespace cw
}
template< typename T >
void _var_setter( variable_t* var, T val )
void _var_setter( variable_t* var, unsigned local_value_idx, T val )
{
cwLogError(kAssertFailRC,"Unimplemented variable setter.");
assert(0);
}
template<>
void _var_setter<bool>( variable_t* var, bool val )
void _var_setter<bool>( variable_t* var, unsigned local_value_idx, bool val )
{
var->local_value.u.b = val;
var->local_value.flags = kBoolTFl;
var->local_value[ local_value_idx ].u.b = val;
var->local_value[ local_value_idx ].flags = kBoolTFl;
cwLogMod("%s.%s ch:%i %i (bool).",var->inst->label,var->label,var->chIdx,val);
}
template<>
void _var_setter<unsigned>( variable_t* var, unsigned val )
void _var_setter<unsigned>( variable_t* var, unsigned local_value_idx, unsigned val )
{
var->local_value.u.u = val;
var->local_value.flags = kUIntTFl;
var->local_value[ local_value_idx ].u.u = val;
var->local_value[ local_value_idx ].flags = kUIntTFl;
cwLogMod("%s.%s ch:%i %i (uint).",var->inst->label,var->label,var->chIdx,val);
}
template<>
void _var_setter<int>( variable_t* var, int val )
void _var_setter<int>( variable_t* var, unsigned local_value_idx, int val )
{
var->local_value.u.i = val;
var->local_value.flags = kIntTFl;
var->local_value[ local_value_idx ].u.i = val;
var->local_value[ local_value_idx ].flags = kIntTFl;
cwLogMod("%s.%s ch:%i %i (int).",var->inst->label,var->label,var->chIdx,val);
}
template<>
void _var_setter<float>( variable_t* var, float val )
void _var_setter<float>( variable_t* var, unsigned local_value_idx, float val )
{
var->local_value.u.f = val;
var->local_value.flags = kFloatTFl;
var->local_value[ local_value_idx ].u.f = val;
var->local_value[ local_value_idx ].flags = kFloatTFl;
cwLogMod("%s.%s ch:%i %f (float).",var->inst->label,var->label,var->chIdx,val);
}
template<>
void _var_setter<double>( variable_t* var, double val )
void _var_setter<double>( variable_t* var, unsigned local_value_idx, double val )
{
var->local_value.u.d = val;
var->local_value.flags = kDoubleTFl;
var->local_value[ local_value_idx ].u.d = val;
var->local_value[ local_value_idx ].flags = kDoubleTFl;
cwLogMod("%s.%s ch:%i %f (double).",var->inst->label,var->label,var->chIdx,val);
}
template<>
void _var_setter<const char*>( variable_t* var, const char* val )
void _var_setter<const char*>( variable_t* var, unsigned local_value_idx, const char* val )
{
var->local_value.u.s = mem::duplStr(val);
var->local_value.flags = kStringTFl;
var->local_value[ local_value_idx ].u.s = mem::duplStr(val);
var->local_value[ local_value_idx ].flags = kStringTFl;
cwLogMod("%s.%s ch:%i %s (string).",var->inst->label,var->label,var->chIdx,val);
}
template<>
void _var_setter<abuf_t*>( variable_t* var, abuf_t* val )
void _var_setter<abuf_t*>( variable_t* var, unsigned local_value_idx, abuf_t* val )
{
var->local_value.u.abuf = val;
var->local_value.flags = kABufTFl;
var->local_value[ local_value_idx ].u.abuf = val;
var->local_value[ local_value_idx ].flags = kABufTFl;
cwLogMod("%s.%s ch:%i %s (abuf).",var->inst->label,var->label,var->chIdx,abuf==nullptr ? "null" : "valid");
}
template<>
void _var_setter<fbuf_t*>( variable_t* var, fbuf_t* val )
void _var_setter<fbuf_t*>( variable_t* var, unsigned local_value_idx, fbuf_t* val )
{
var->local_value.u.fbuf = val;
var->local_value.flags = kFBufTFl;
var->local_value[ local_value_idx ].u.fbuf = val;
var->local_value[ local_value_idx ].flags = kFBufTFl;
cwLogMod("%s.%s ch:%i %s (fbuf).",var->inst->label,var->label,var->chIdx,fbuf==nullptr ? "null" : "valid");
}
@ -521,183 +500,69 @@ namespace cw
rc_t _var_set_template( variable_t* var, unsigned typeFlag, T val )
{
rc_t rc;
unsigned next_local_value_idx = (var->local_value_idx + 1) % kLocalValueN;
// store the pointer to the current value of this variable
value_t* original_value = var->value;
unsigned original_value_idx = var->local_value_idx;
// verify that this is a legal assignment
if((rc = _validate_var_assignment( var, typeFlag )) != kOkRC )
return rc;
{
goto errLabel;
}
// release the previous value in the next slot
_value_release(&var->local_value[next_local_value_idx]);
// set the new local value
_var_setter(var,next_local_value_idx,val);
// make the new local value current
var->value = var->local_value + next_local_value_idx;
var->local_value_idx = next_local_value_idx;
// If the instance is fully initialized ...
if( var->inst->varMapA != nullptr )
{
// ... then inform the proc. that the value changed
// (we don't want to do call to occur if we are inside or prior to 'proc.create()'
// Note 1: We don't want to this call to occur if we are inside or prior to 'proc.create()'
// call because calls' to 'proc.value()' will see the instance in a incomplete state)
rc = var->inst->class_desc->members->value( var->inst, var );
// Note 2: If this call returns an error then the value assignment is cancelled
// and the value does not change.
rc = var->inst->class_desc->members->value( var->inst, var );
}
if( rc == kOkRC )
{
// release the current value
_value_release(&var->local_value);
// set the new local value
_var_setter(var,val);
// make the var point to the local value
var->value = &var->local_value;
// send the value to connected downstream proc's
rc = _var_broadcast_new_value( var );
}
return rc;
}
rc_t _var_set( variable_t* var, bool val )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kBoolTFl )) != kOkRC )
return rc;
if((rc = var->inst->class_desc->members->value( var->inst, var )) == kOkRC )
else
{
_value_release(&var->local_value);
var->local_value.u.b = val;
var->local_value.flags = kBoolTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %i (bool).",var->inst->label,var->label,var->chIdx,val);
rc = _var_broadcast_new_value( var );
// cancel the assignment and restore the original value
var->value = original_value;
var->local_value_idx = original_value_idx;
}
errLabel:
return rc;
}
rc_t _var_set( variable_t* var, unsigned val )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kUIntTFl )) != kOkRC )
return rc;
if((rc = var->inst->class_desc->members->value( var->inst, var )) == kOkRC )
{
_value_release(&var->local_value);
var->local_value.u.u = val;
var->local_value.flags = kUIntTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %i (uint_t).",var->inst->label,var->label,var->chIdx,val);
_var_broadcast_new_value( var );
}
return rc;
}
rc_t _var_set( variable_t* var, int val )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kIntTFl )) != kOkRC )
return rc;
_value_release(&var->local_value);
var->local_value.u.i = val;
var->local_value.flags = kIntTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %i (int_t).",var->inst->label,var->label,var->chIdx,val);
return _var_broadcast_new_value( var );
}
rc_t _var_set( variable_t* var, float val )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kFloatTFl )) != kOkRC )
return rc;
_value_release(&var->local_value);
var->local_value.u.f = val;
var->local_value.flags = kFloatTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %f (float).",var->inst->label,var->label,var->chIdx,val);
return _var_broadcast_new_value( var );
}
rc_t _var_set( variable_t* var, double val )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kDoubleTFl )) != kOkRC )
return rc;
_value_release(&var->local_value);
var->local_value.u.d = val;
var->local_value.flags = kDoubleTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %f (double).",var->inst->label,var->label,var->chIdx,val);
return _var_broadcast_new_value( var );
}
rc_t _var_set( variable_t* var, const char* val )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kStringTFl )) != kOkRC )
return rc;
_value_release(&var->local_value);
var->local_value.u.s = mem::duplStr(val);
var->local_value.flags = kStringTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %s (string).",var->inst->label,var->label,var->chIdx,cwStringNullGuard(val));
return _var_broadcast_new_value( var );
}
rc_t _var_set( variable_t* var, abuf_t* abuf )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kABufTFl )) != kOkRC )
return rc;
_value_release(&var->local_value);
var->local_value.u.abuf = abuf;
var->local_value.flags = kABufTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %s (abuf).",var->inst->label,var->label,var->chIdx,abuf==nullptr ? "null" : "valid");
return _var_broadcast_new_value( var );
}
rc_t _var_set( variable_t* var, fbuf_t* fbuf )
{
rc_t rc;
if((rc = _validate_var_assignment( var, kFBufTFl )) != kOkRC )
return rc;
_value_release(&var->local_value);
var->local_value.u.fbuf = fbuf;
var->local_value.flags = kFBufTFl;
var->value = &var->local_value;
cwLogMod("%s.%s ch:%i %s (fbuf).",var->inst->label,var->label,var->chIdx,fbuf==nullptr ? "null" : "valid");
return _var_broadcast_new_value( var );
}
bool is_connected_to_external_proc( const variable_t* var )
{
return var->src_var != nullptr && var->value != nullptr && var->value != &var->local_value;
// if this var does not have a 'src_ptr' then it can't be connected to an external proc
if( var->src_var == nullptr || var->value == nullptr )
return false;
// if this var is using a local value then it can't be connected to an external proc
for(unsigned i=0; i<kLocalValueN; ++i)
if( var->value == var->local_value + i )
return false;
return true;
}
template< typename T >
@ -950,13 +815,12 @@ namespace cw
void _var_print( const variable_t* var )
{
//const char* local_label = var->value==nullptr || var->value == &var->local_value ? "local" : " ";
const char* conn_label = is_connected_to_external_proc(var) ? "extern" : " ";
printf(" %20s id:%4i ch:%3i : %s : ", var->label, var->vid, var->chIdx, conn_label );
if( var->value == nullptr )
_value_print( &var->local_value );
_value_print( &var->local_value[0] );
else
_value_print( var->value );
@ -999,7 +863,7 @@ cw::flow::abuf_t* cw::flow::abuf_create( srate_t srate, unsigned chN, unsigned f
return a;
}
void cw::flow::abuf_destroy( abuf_t* abuf )
void cw::flow::abuf_destroy( abuf_t*& abuf )
{
if( abuf == nullptr )
return;
@ -1077,7 +941,7 @@ cw::flow::fbuf_t* cw::flow::fbuf_create( srate_t srate, unsigned chN, unsigned
return f;
}
void cw::flow::fbuf_destroy( fbuf_t* fbuf )
void cw::flow::fbuf_destroy( fbuf_t*& fbuf )
{
if( fbuf == nullptr )
return;
@ -1210,7 +1074,8 @@ void cw::flow::_var_destroy( variable_t* var )
{
if( var != nullptr )
{
_value_release(&var->local_value);
for(unsigned i=0; i<kLocalValueN; ++i)
_value_release(var->local_value+i);
mem::release(var->label);
mem::release(var);
}
@ -1262,11 +1127,11 @@ cw::rc_t cw::flow::var_channelize( instance_t* inst, const char* var_label, uns
if( value_cfg == nullptr )
{
// Set the value of the new variable to the value of the 'any' channel
_value_duplicate( var->local_value, base_var->local_value );
_value_duplicate( var->local_value[ var->local_value_idx], base_var->local_value[ base_var->local_value_idx ] );
// If the 'any' channel value was set to point to it's local value then do same with this value
if( &base_var->local_value == base_var->value )
var->value = &var->local_value;
if( base_var->local_value + base_var->local_value_idx == base_var->value )
var->value = var->local_value + var->local_value_idx;
}
}
@ -1340,7 +1205,7 @@ cw::rc_t cw::flow::var_find( instance_t* inst, const char* label, unsigned chIdx
variable_t* var;
vRef = nullptr;
if((var = _var_find_best(inst,label,chIdx)) != nullptr )
if((var = _var_find_on_label_and_ch(inst,label,chIdx)) != nullptr )
{
vRef = var;
return kOkRC;
@ -1357,6 +1222,37 @@ cw::rc_t cw::flow::var_find( instance_t* inst, const char* label, unsigned chIdx
return rc;
}
cw::rc_t cw::flow::var_channel_count( instance_t* inst, const char* label, unsigned& chCntRef )
{
rc_t rc = kOkRC;
const variable_t* var= nullptr;
if((rc = var_find(inst,label,kAnyChIdx,var)) != kOkRC )
return cwLogError(rc,"Channel count was not available because the variable '%s.%s' does not exist.",cwStringNullGuard(inst->label),cwStringNullGuard(label));
return var_channel_count(var,chCntRef);
}
cw::rc_t cw::flow::var_channel_count( const variable_t* var, unsigned& chCntRef )
{
rc_t rc = kOkRC;
const variable_t* v;
chCntRef = 0;
if((rc = var_find( var->inst, var->label, kAnyChIdx, v )) != kOkRC )
{
rc = cwLogError(kInvalidStateRC,"The base channel variable instance could not be found for the variable '%s.%s'.",var->inst->label,var->label);
goto errLabel;
}
for(v = v->ch_link; v!=nullptr; v=v->ch_link)
chCntRef += 1;
errLabel:
return rc;
}
cw::rc_t cw::flow::var_register( instance_t* inst, const char* var_label, unsigned vid, unsigned chIdx, const object_t* value_cfg, variable_t*& varRef )
{
@ -1432,34 +1328,34 @@ cw::rc_t cw::flow::var_register_and_set( instance_t* inst, const char* var_label
cw::rc_t cw::flow::var_get( const variable_t* var, bool& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, uint_t& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, int_t& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, float& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, double& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, const char*& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, const abuf_t*& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( variable_t* var, abuf_t*& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( const variable_t* var, const fbuf_t*& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_get( variable_t* var, fbuf_t*& valRef )
{ return _val_get(var->value,valRef); }
{ return _val_get_driver(var,valRef); }
cw::rc_t cw::flow::var_set( instance_t* inst, unsigned vid, unsigned chIdx, bool val )
{

View File

@ -25,7 +25,8 @@ namespace cw
enum {
kFbufVectN = 3,
kAnyChIdx = kInvalidIdx
kAnyChIdx = kInvalidIdx,
kLocalValueN = 2
};
typedef struct fbuf_str
@ -162,11 +163,12 @@ namespace cw
char* label; // this variables label
unsigned vid; // this variables numeric id ( cat(vid,chIdx) forms a unique variable identifier on this 'inst'
var_desc_t* varDesc; // the variable description for this variable
value_t local_value; // the local value instance (actual value if this is not a 'src' variable)
value_t local_value[ kLocalValueN ]; // the local value instance (actual value if this is not a 'src' variable)
unsigned local_value_idx;
value_t* value; // pointer to the value associated with this variable
unsigned chIdx; // channel index
struct variable_str* src_var; // pointer to this input variables source link (or null if it uses the local_value)
struct variable_str* var_link; // link to other var's on 'inst'
struct variable_str* var_link; // instance.varL link list
struct variable_str* connect_link; // list of outgoing connections
struct variable_str* ch_link; // list of channels that share this variable (rooted on 'any' channel - in order by channel number)
} variable_t;
@ -186,7 +188,7 @@ namespace cw
void* userPtr; // instance state
variable_t* varL; // list of instance value
variable_t* varL; // list of all variables on this instance
unsigned varMapChN; // max count of channels among all variables
unsigned varMapIdN;
@ -224,13 +226,13 @@ namespace cw
//
abuf_t* abuf_create( srate_t srate, unsigned chN, unsigned frameN );
void abuf_destroy( abuf_t* buf );
void abuf_destroy( abuf_t*& buf );
abuf_t* abuf_duplicate( const abuf_t* src );
rc_t abuf_set_channel( abuf_t* buf, unsigned chIdx, const sample_t* v, unsigned vN );
const sample_t* abuf_get_channel( abuf_t* buf, unsigned chIdx );
fbuf_t* fbuf_create( srate_t srate, unsigned chN, unsigned binN, unsigned hopSmpN, const sample_t** magV=nullptr, const sample_t** phsV=nullptr, const sample_t** hzV=nullptr );
void fbuf_destroy( fbuf_t* buf );
void fbuf_destroy( fbuf_t*& buf );
fbuf_t* fbuf_duplicate( const fbuf_t* src );
inline bool value_is_abuf( const value_t* v ) { return v->flags & kABufTFl; }
@ -395,10 +397,15 @@ namespace cw
void _var_destroy( variable_t* var );
bool var_exists( instance_t* inst, const char* label, unsigned chIdx );
rc_t var_find( instance_t* inst, const char* var_label, unsigned chIdx, const variable_t*& varRef );
rc_t var_find( instance_t* inst, const char* var_label, unsigned chIdx, variable_t*& varRef );
rc_t var_find( instance_t* inst, unsigned vid, unsigned chIdx, variable_t*& varRef );
// Count of numbered channels - does not count the kAnyChIdx variable instance.
rc_t var_channel_count( instance_t* inst, const char* label, unsigned& chCntRef );
rc_t var_channel_count( const variable_t* var, unsigned& chCntRef );
rc_t var_get( const variable_t* var, bool& valRef );
rc_t var_get( const variable_t* var, uint_t& valRef );

View File

@ -1553,7 +1553,7 @@ namespace cw
// UI
//
// This function is called by the websocket with messages comring from a remote UI.
// This function is called by the websocket with messages coming from a remote UI.
rc_t _uiCallback( void* cbArg, unsigned wsSessId, ui::opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, unsigned chanId, const ui::value_t* v )
{
io_t* p = (io_t*)cbArg;
@ -1569,7 +1569,7 @@ namespace cw
{
rc_t rc = kOkRC;
const char* uiCfgLabel = "ui";
ui::ws::args_t args;
ui::ws::args_t args = {};
// Duplicate the application id map
@ -3134,3 +3134,9 @@ cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, const char* value )
return rc;
}
void cw::io::uiReport( handle_t h )
{
ui::handle_t uiH;
if(_handleToUiHandle(h,uiH) == kOkRC )
ui::report(uiH);
}

2
cwIo.h
View File

@ -364,7 +364,7 @@ namespace cw
rc_t uiSendValue( handle_t h, unsigned uuId, double value );
rc_t uiSendValue( handle_t h, unsigned uuId, const char* value );
void uiReport( handle_t h );
}
}

View File

@ -251,7 +251,7 @@ namespace cw
rc_t _audio_callback( io_flow_t* p, io::audio_msg_t& m )
{
rc_t rc = kOkRC;
flow::abuf_t* abuf;
flow::abuf_t* abuf = nullptr;
// if there is incoming (recorded) audio
if( m.iBufChCnt > 0 )
@ -436,3 +436,9 @@ cw::rc_t cw::io_flow::set_variable_value( handle_t h, flow_cross::destId_t destI
cw::rc_t cw::io_flow::begin_cross_fade( handle_t h, unsigned crossFadeMs )
{ return flow_cross::begin_cross_fade( _handleToPtr(h)->crossFlowH, crossFadeMs ); }
void cw::io_flow::print( handle_t h )
{ return flow_cross::print( _handleToPtr(h)->crossFlowH ); }
void cw::io_flow::print_network( handle_t h, flow_cross::destId_t destId )
{ return flow_cross::print_network( _handleToPtr(h)->crossFlowH, destId ); }

View File

@ -30,6 +30,8 @@ namespace cw
rc_t begin_cross_fade( handle_t h, unsigned crossFadeMs );
void print( handle_t h );
void print_network( handle_t h, flow_cross::destId_t destId );
}
}

View File

@ -27,43 +27,94 @@ namespace cw
unsigned id;
time::spec_t timestamp;
unsigned loc;
uint8_t ch;
uint8_t status;
uint8_t d0;
uint8_t d1;
} am_midi_msg_t;
typedef struct midi_record_play_str
{
io::handle_t ioH;
am_midi_msg_t* msgArray;
unsigned msgArrayN;
unsigned msgArrayInIdx;
unsigned msgArrayOutIdx;
unsigned midi_timer_period_micro_sec;
typedef struct midi_device_str
{
char* midiOutDevLabel;
char* midiOutPortLabel;
unsigned midiOutDevIdx;
unsigned midiOutPortIdx;
bool enableFl;
unsigned velTableN;
uint8_t* velTableArray;
bool pedalMapEnableFl;
unsigned pedalDownVelId;
unsigned pedalDownVel;
unsigned pedalDownHalfVelId;
unsigned pedalDownHalfVel;
unsigned pedalUpHalfVelId;
unsigned pedalUpHalfVel;
} midi_device_t;
enum
{
kHalfPedalDone,
kWaitForBegin,
kWaitForNoteOn,
kWaitForNoteOff,
kWaitForPedalUp,
kWaitForPedalDown,
};
typedef struct midi_record_play_str
{
io::handle_t ioH;
am_midi_msg_t* msgArray; // msgArray[ msgArrayN ]
unsigned msgArrayN; // Count of messages allocated in msgArray.
unsigned msgArrayInIdx; // Next available space for loaded MIDI messages (also the current count of msgs in msgArray[])
unsigned msgArrayOutIdx; // Next message to transmit in msgArray[]
unsigned midi_timer_period_micro_sec; // Timer period in microseconds
unsigned all_off_delay_ms; // Wait this long before turning all notes off after the last note-on has played
am_midi_msg_t* iMsgArray; // msgArray[ msgArrayN ]
unsigned iMsgArrayN; // Count of messages allocated in msgArray.
unsigned iMsgArrayInIdx; // Next available space for incoming MIDI messages (also the current count of msgs in msgArray[])
midi_device_t* midiDevA;
unsigned midiDevN;
bool startedFl;
bool recordFl;
bool thruFl;
bool logInFl; // log incoming message when not in 'record' mode.
bool logOutFl; // log outgoing messages
bool halfPedalFl;
unsigned halfPedalState;
unsigned halfPedalNextUs;
unsigned halfPedalNoteDelayUs;
unsigned halfPedalNoteDurUs;
unsigned halfPedalUpDelayUs;
unsigned halfPedalDownDelayUs;
uint8_t halfPedalMidiPitch;
uint8_t halfPedalMidiNoteVel;
uint8_t halfPedalMidiPedalVel;
time::spec_t play_time;
time::spec_t start_time;
time::spec_t end_play_event_timestamp;
bool pedalFl;
time::spec_t all_off_timestamp;
time::spec_t store_time;
event_callback_t cb;
void* cb_arg;
} midi_record_play_t;
enum
{
kMidiRecordPlayTimerId
@ -79,10 +130,17 @@ namespace cw
if((timerIdx = io::timerLabelToIndex( p->ioH, TIMER_LABEL )) != kInvalidIdx )
io::timerDestroy( p->ioH, timerIdx);
for(unsigned i=0; i<p->midiDevN; ++i)
{
mem::release(p->midiDevA[i].midiOutDevLabel);
mem::release(p->midiDevA[i].midiOutPortLabel);
mem::release(p->midiDevA[i].velTableArray);
}
mem::release(p->midiDevA);
mem::release(p->msgArray);
mem::release(p->midiOutDevLabel);
mem::release(p->midiOutPortLabel);
mem::release(p->iMsgArray);
mem::release(p);
return rc;
@ -91,74 +149,293 @@ namespace cw
rc_t _parseCfg(midi_record_play_t* p, const object_t& cfg )
{
rc_t rc = kOkRC;
const object_t* midiDevL = nullptr;
if((rc = cfg.getv(
"max_midi_msg_count", p->msgArrayN,
"midi_timer_period_micro_sec", p->midi_timer_period_micro_sec,
"midi_out_device", p->midiOutDevLabel,
"midi_out_port", p->midiOutPortLabel)) != kOkRC )
"all_off_delay_ms", p->all_off_delay_ms,
"midi_device_list", midiDevL,
"log_in_flag", p->logInFl,
"log_out_flag", p->logOutFl,
"half_pedal_flag", p->halfPedalFl)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"MIDI record play configuration parse failed.");
goto errLabel;
}
p->iMsgArrayN = p->msgArrayN;
if( midiDevL->child_count() > 0 )
{
p->midiDevN = midiDevL->child_count();
p->midiDevA = mem::allocZ<midi_device_t>(p->midiDevN);
printf("Midi record play devices:%i\n",p->midiDevN);
for(unsigned i=0; i<p->midiDevN; ++i)
{
const object_t* ele = midiDevL->child_ele(i);
const char* midiOutDevLabel = nullptr;
const char* midiOutPortLabel = nullptr;
const object_t* velTable = nullptr;
const object_t* pedalRecd = nullptr;
bool enableFl = false;
if((rc = ele->getv( "midi_out_device", midiOutDevLabel,
"midi_out_port", midiOutPortLabel,
"enableFl", enableFl)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"MIDI record play device list configuration parse failed.");
goto errLabel;
}
if((rc = ele->getv_opt( "vel_table", velTable,
"pedal", pedalRecd)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"MIDI record play device optional argument parsing failed.");
goto errLabel;
}
p->midiDevA[i].midiOutDevLabel = mem::duplStr( midiOutDevLabel);
p->midiDevA[i].midiOutPortLabel = mem::duplStr( midiOutPortLabel);
p->midiDevA[i].enableFl = enableFl;
if( velTable != nullptr )
{
p->midiDevA[i].velTableN = velTable->child_count();
p->midiDevA[i].velTableArray = mem::allocZ<uint8_t>(p->midiDevA[i].velTableN);
for(unsigned j=0; j<p->midiDevA[i].velTableN; ++j)
{
if((rc = velTable->child_ele(j)->value( p->midiDevA[i].velTableArray[j] )) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"An error occured while parsing the velocity table for MIDI device:'%s' port:'%s'.",midiOutDevLabel,midiOutPortLabel);
goto errLabel;
}
}
}
if( pedalRecd != nullptr )
{
if((rc = pedalRecd->getv( "down_id", p->midiDevA[i].pedalDownVelId,
"down_vel", p->midiDevA[i].pedalDownVel,
"half_down_id", p->midiDevA[i].pedalDownHalfVelId,
"half_down_vel", p->midiDevA[i].pedalDownHalfVel,
"half_up_id", p->midiDevA[i].pedalUpHalfVelId,
"half_up_vel", p->midiDevA[i].pedalUpHalfVel
)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"An error occured while parsing the pedal record for MIDI device:'%s' port:'%s'.",midiOutDevLabel,midiOutPortLabel);
goto errLabel;
}
else
{
p->midiDevA[i].pedalMapEnableFl = true;
}
}
}
}
// allocate the MIDI msg buffer
p->msgArray = mem::allocZ<am_midi_msg_t>( p->msgArrayN );
p->midiOutDevLabel = mem::duplStr( p->midiOutDevLabel);
p->midiOutPortLabel = mem::duplStr( p->midiOutPortLabel);
p->iMsgArray = mem::allocZ<am_midi_msg_t>( p->iMsgArrayN );
errLabel:
return rc;
}
rc_t _stop( midi_record_play_t* p );
rc_t _event_callback( midi_record_play_t* p, unsigned id, const time::spec_t timestamp, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
const am_midi_msg_t* _midi_store( midi_record_play_t* p, unsigned devIdx, unsigned portIdx, const time::spec_t& ts, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
{
am_midi_msg_t* am = nullptr;
// verify that space exists in the record buffer
if( p->iMsgArrayInIdx < p->iMsgArrayN )
{
// MAKE THIS ATOMIC
unsigned id = p->iMsgArrayInIdx;
++p->iMsgArrayInIdx;
am = p->iMsgArray + id;
am->id = id;
am->devIdx = devIdx;
am->portIdx = portIdx;
am->timestamp = ts;
am->ch = ch;
am->status = status;
am->d0 = d0;
am->d1 = d1;
}
return am;
}
rc_t _event_callback( midi_record_play_t* p, unsigned id, const time::spec_t timestamp, unsigned loc, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1, bool log_fl=true )
{
rc_t rc = kOkRC;
// if we have arrived at the stop time
bool after_stop_time_fl = !time::isZero(p->end_play_event_timestamp) && time::isGT(timestamp,p->end_play_event_timestamp);
bool after_all_off_fl = after_stop_time_fl && time::isGT(timestamp,p->all_off_timestamp);
bool is_note_on_fl = status==midi::kNoteOnMdId and d1 != 0;
bool supress_fl = is_note_on_fl && after_stop_time_fl;
if( !time::isZero(p->end_play_event_timestamp) && time::isGTE(timestamp,p->end_play_event_timestamp))
if( after_all_off_fl )
{
rc = _stop(p);
}
else
else
{
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, status + ch, d0, d1 );
if( p->cb )
p->cb( p->cb_arg, id, timestamp, ch, status, d0, d1 );
if( p->halfPedalFl )
{
if( status==midi::kCtlMdId && d0 == midi::kSustainCtlMdId && d1 != 0 )
d1 = p->halfPedalMidiPedalVel;
}
// for each midi device
for(unsigned i=0; i<p->midiDevN; ++i)
if(p->midiDevA[i].enableFl )
{
uint8_t out_d1 = d1;
if( !p->halfPedalFl )
{
// map the note on velocity
if( is_note_on_fl and p->midiDevA[i].velTableArray != nullptr )
{
if( d1 >= p->midiDevA[i].velTableN )
cwLogError(kInvalidIdRC,"A MIDI note-on velocity (%i) outside the velocity table range was encountered.",d1);
else
out_d1 = p->midiDevA[i].velTableArray[ d1 ];
}
// map the pedal down velocity
if( status==midi::kCtlMdId && d0 == midi::kSustainCtlMdId && p->midiDevA[i].pedalMapEnableFl )
{
if( d1 == 0 )
out_d1 = 0;
else
if( d1 == p->midiDevA[i].pedalDownVelId )
out_d1 = p->midiDevA[i].pedalDownVel;
else
if( d1 == p->midiDevA[i].pedalDownHalfVelId )
out_d1 = p->midiDevA[i].pedalDownHalfVel;
else
cwLogError(kInvalidIdRC,"Unexpected pedal down velocity (%i) during pedal velocity mapping.",d1);
}
}
if( !supress_fl )
io::midiDeviceSend( p->ioH, p->midiDevA[i].midiOutDevIdx, p->midiDevA[i].midiOutPortIdx, status + ch, d0, out_d1 );
}
if( !after_stop_time_fl and p->cb )
p->cb( p->cb_arg, id, timestamp, loc, ch, status, d0, d1 );
if( log_fl && p->logOutFl )
{
// Note: The device of outgoing messages is set to p->midiDevN + 1 to distinguish it from
// incoming messages.
_midi_store( p, p->midiDevN, 0, timestamp, ch, status, d0, d1 );
}
}
return rc;
}
rc_t _transmit_msg( midi_record_play_t* p, const am_midi_msg_t* am )
rc_t _transmit_msg( midi_record_play_t* p, const am_midi_msg_t* am, bool log_fl=true )
{
return _event_callback( p, am->id, am->timestamp, am->ch, am->status, am->d0, am->d1 );
return _event_callback( p, am->id, am->timestamp, am->loc, am->ch, am->status, am->d0, am->d1, log_fl );
}
rc_t _transmit_ctl( midi_record_play_t* p, unsigned ch, unsigned ctlId, unsigned ctlVal )
rc_t _transmit_note( midi_record_play_t* p, unsigned ch, unsigned pitch, unsigned vel, unsigned microsecs )
{
time::spec_t ts = {0};
return _event_callback( p, kInvalidId, ts, ch, midi::kCtlMdId, ctlId, ctlVal );
time::microsecondsToSpec( ts, microsecs );
return _event_callback( p, kInvalidId, ts, kInvalidId, ch, midi::kNoteOnMdId, pitch, vel );
}
rc_t _transmit_ctl( midi_record_play_t* p, unsigned ch, unsigned ctlId, unsigned ctlVal, unsigned microsecs )
{
time::spec_t ts = {0};
time::microsecondsToSpec( ts, microsecs );
return _event_callback( p, kInvalidId, ts, kInvalidId, ch, midi::kCtlMdId, ctlId, ctlVal );
}
rc_t _transmit_pedal( midi_record_play_t* p, unsigned ch, unsigned pedalCtlId, bool pedalDownFl )
rc_t _transmit_pedal( midi_record_play_t* p, unsigned ch, unsigned pedalCtlId, bool pedalDownFl, unsigned microsecs )
{
return _transmit_ctl( p, ch, pedalCtlId, pedalDownFl ? 127 : 0);
return _transmit_ctl( p, ch, pedalCtlId, pedalDownFl ? 127 : 0, microsecs);
}
void _half_pedal_update( midi_record_play_t* p, unsigned cur_time_us )
{
if( cur_time_us >= p->halfPedalNextUs )
{
unsigned midi_ch = 0;
switch( p->halfPedalState )
{
case kWaitForBegin:
printf("down: %i %i\n",cur_time_us/1000,p->halfPedalMidiPedalVel);
_transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, p->halfPedalMidiPedalVel, cur_time_us);
p->halfPedalState = kWaitForNoteOn;
p->halfPedalNextUs += p->halfPedalNoteDelayUs;
break;
case kWaitForNoteOn:
printf("note: %i\n",cur_time_us/1000);
_transmit_note( p, midi_ch, p->halfPedalMidiPitch, p->halfPedalMidiNoteVel, cur_time_us );
p->halfPedalNextUs += p->halfPedalNoteDurUs;
p->halfPedalState = kWaitForNoteOff;
break;
case kWaitForNoteOff:
printf("off: %i\n",cur_time_us/1000);
_transmit_note( p, midi_ch, p->halfPedalMidiPitch, 0, cur_time_us );
p->halfPedalNextUs += p->halfPedalUpDelayUs;
p->halfPedalState = kWaitForPedalUp;
break;
case kWaitForPedalUp:
printf("up: %i\n",cur_time_us/1000);
_transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, 0, cur_time_us);
p->halfPedalNextUs += p->halfPedalDownDelayUs;
p->halfPedalState = kWaitForPedalDown;
break;
case kWaitForPedalDown:
//printf("down: %i\n",cur_time_us/1000);
//_transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, p->halfPedalMidiPedalVel, cur_time_us);
//_stop(p);
p->halfPedalState = kHalfPedalDone;
break;
case kHalfPedalDone:
break;
}
}
}
// Set the next location to store an incoming MIDI message
void _set_midi_msg_next_index( midi_record_play_t* p, unsigned next_idx )
{
p->msgArrayInIdx = next_idx;
//io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->midiMsgArrayInIdx );
p->iMsgArrayInIdx = next_idx;
}
// Set the next index of the next MIDI message to transmit
void _set_midi_msg_next_play_index(midi_record_play_t* p, unsigned next_idx)
{
p->msgArrayOutIdx = next_idx;
@ -214,7 +491,8 @@ namespace cw
return rc;
}
// Fill the play buffer from a previously store AM file.
rc_t _midi_read( midi_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
@ -245,21 +523,24 @@ namespace cw
goto errLabel;
}
_set_midi_msg_next_index(p, n );
p->msgArrayInIdx = n;
cwLogInfo("Read %i from '%s'.",n,fn);
errLabel:
file::close(fH);
return rc;
}
// Write the record buffer to an AM file
rc_t _midi_write( midi_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
file::handle_t fH;
if( p->msgArrayInIdx == 0 )
if( p->iMsgArrayInIdx == 0 )
{
cwLogWarning("Nothing to write.");
return rc;
@ -273,30 +554,77 @@ namespace cw
}
// write the file header
if((rc = write(fH,p->msgArrayInIdx)) != kOkRC )
if((rc = write(fH,p->iMsgArrayInIdx)) != kOkRC )
{
rc = cwLogError(kWriteFailRC,"Header write to '%s' failed.",cwStringNullGuard(fn));
goto errLabel;
}
// write the file data
if((rc = write(fH,p->msgArray,sizeof(am_midi_msg_t)*p->msgArrayInIdx)) != kOkRC )
if((rc = write(fH,p->iMsgArray,sizeof(am_midi_msg_t)*p->iMsgArrayInIdx)) != kOkRC )
{
rc = cwLogError(kWriteFailRC,"Data write to '%s' failed.",cwStringNullGuard(fn));
goto errLabel;
}
// update UI msg count
//io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->msgArrayInIdx );
errLabel:
file::close(fH);
cwLogInfo("Saved %i events to '%s'.", p->msgArrayInIdx, fn );
cwLogInfo("Saved %i events to '%s'.", p->iMsgArrayInIdx, fn );
errLabel:
return rc;
}
rc_t _write_csv( midi_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
file::handle_t fH;
if( p->iMsgArrayInIdx == 0 )
{
cwLogWarning("Nothing to write.");
return rc;
}
// open the file
if((rc = file::open(fH,fn,file::kWriteFl)) != kOkRC )
{
rc = cwLogError(kOpenFailRC,"Unable to create the file '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
file::printf(fH,"dev,port,microsec,id,sec,ch,status,d0,d1\n");
for(unsigned i=0; i<p->iMsgArrayInIdx; ++i)
{
const am_midi_msg_t* m = p->iMsgArray + i;
double secs = time::elapsedSecs( p->iMsgArray[0].timestamp, p->iMsgArray[i].timestamp );
char sciPitch[ midi::kMidiSciPitchCharCnt + 1 ];
if( m->status == midi::kNoteOnMdId )
midi::midiToSciPitch( m->d0, sciPitch, midi::kMidiSciPitchCharCnt );
else
strcpy(sciPitch,"");
if((rc = file::printf(fH, "%3i,%3i,%8i,%3i,%8.4f,%2i,0x%2x,%5s,%3i,%3i\n",
m->devIdx, m->portIdx, m->microsec, m->id, secs,
m->ch, m->status, sciPitch, m->d0, m->d1 )) != kOkRC )
{
rc = cwLogError(rc,"Write failed on line:%i", i+1 );
goto errLabel;
}
}
errLabel:
file::close(fH);
cwLogInfo("Saved %i events to '%s'.", p->iMsgArrayInIdx, fn );
return rc;
}
rc_t _midi_file_write( const char* fn, const am_midi_msg_t* msgArray, unsigned msgArrayCnt )
{
rc_t rc = kOkRC;
@ -384,13 +712,14 @@ namespace cw
time::spec_t t1;
time::get(t1);
// if we were recording
if( p->recordFl )
{
// set the 'microsec' value for each MIDI msg
for(unsigned i=0; i<p->msgArrayInIdx; ++i)
// set the 'microsec' value for each MIDI msg as an offset from the first message[]
for(unsigned i=0; i<p->iMsgArrayInIdx; ++i)
{
p->msgArray[i].microsec = time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp);
p->msgArray[i].microsec = time::elapsedMicros(p->iMsgArray[0].timestamp,p->iMsgArray[i].timestamp);
}
cwLogInfo("MIDI messages recorded: %i",p->msgArrayInIdx );
@ -400,26 +729,19 @@ namespace cw
{
io::timerStop( p->ioH, io::timerIdToIndex(p->ioH, kMidiRecordPlayTimerId) );
// TODO: should work for all channels
// TODO:
// BUG BUG BUG: should work for all channels
// all notes off
_transmit_ctl( p, 0, 121, 0 ); // reset all controllers
_transmit_ctl( p, 0, 123, 0 ); // all notes off
_transmit_ctl( p, 0, 0, 0 ); // switch to bank 0
// send pgm change 0
time::spec_t ts = {0};
_event_callback( p, kInvalidId, ts, 0, midi::kPgmMdId, 0, 0 );
_transmit_ctl( p, 0, 121, 0, 0 ); // reset all controllers
_transmit_ctl( p, 0, 123, 0, 0 ); // all notes off
_transmit_ctl( p, 0, 0, 0, 0 ); // switch to bank 0
p->pedalFl = false;
}
cwLogInfo("Runtime: %5.2f seconds.", time::elapsedMs(p->start_time,t1)/1000.0 );
return rc;
}
rc_t _midi_receive( midi_record_play_t* p, const io::midi_msg_t& m )
{
rc_t rc = kOkRC;
@ -436,68 +758,42 @@ namespace cw
{
//if( !midi::isPedal(pkt->msgArray[j].status,pkt->msgArray[j].d0) )
// printf("0x%x 0x%x 0x%x\n", pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1 );
//printf("IN: 0x%x 0x%x 0x%x\n", pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1 );
if( p->recordFl && p->startedFl )
if( (p->recordFl || p->logInFl) && p->startedFl )
{
// verify that space exists in the record buffer
if( p->msgArrayInIdx >= p->msgArrayN )
if( p->iMsgArrayInIdx >= p->iMsgArrayN )
{
_stop(p);
rc = cwLogError(kBufTooSmallRC,"MIDI message record buffer is full. % messages.",p->msgArrayN);
rc = cwLogError(kBufTooSmallRC,"MIDI message record buffer is full. % messages.",p->iMsgArrayN);
goto errLabel;
}
else
{
// copy the msg into the record buffer
am_midi_msg_t* am = p->msgArray + p->msgArrayInIdx;
midi::msg_t* mm = pkt->msgArray + j;
if( midi::isChStatus(mm->status) )
{
const am_midi_msg_t* am = _midi_store( p, pkt->devIdx, pkt->portIdx, mm->timeStamp, mm->status & 0x0f, mm->status & 0xf0, mm->d0, mm->d1 );
am->id = p->msgArrayInIdx;
am->devIdx = pkt->devIdx;
am->portIdx = pkt->portIdx;
am->timestamp = mm->timeStamp;
am->ch = mm->status & 0x0f;
am->status = mm->status & 0xf0;
am->d0 = mm->d0;
am->d1 = mm->d1;
if( p->thruFl && am != nullptr )
_transmit_msg( p, am, false );
//printf("st:0x%x ch:%i d0:0x%x d1:0x%x\n",am->status,am->ch,am->d0,am->d1);
p->msgArrayInIdx += 1;
if( p->thruFl )
{
_transmit_msg( p, am );
//io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, am->status + am->ch, am->d0, am->d1 );
}
// send msg count
//io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->msgArrayInIdx );
}
}
}
}
/*
if( pkt->msgArray == NULL )
printf("io midi cb: 0x%x ",pkt->sysExMsg[j]);
else
{
if( !_midi_filter(pkt->msgArray + j) )
printf("io midi cb: %ld %ld 0x%x %i %i\n", pkt->msgArray[j].timeStamp.tv_sec, pkt->msgArray[j].timeStamp.tv_nsec, pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1);
}
*/
}
errLabel:
return rc;
}
rc_t _timer_callback(midi_record_play_t* p, io::timer_msg_t& m)
{
rc_t rc = kOkRC;
@ -510,46 +806,27 @@ namespace cw
unsigned cur_time_us = time::elapsedMicros(p->play_time,t);
while( p->msgArray[ p->msgArrayOutIdx ].microsec <= cur_time_us )
{
am_midi_msg_t* mm = p->msgArray + p->msgArrayOutIdx;
//_print_midi_msg(mm);
bool skipFl = false;
/*
// if this is a pedal message
if( mm->status == midi::kCtlMdId && (mm->d0 == midi::kSustainCtlMdId || mm->d0 == midi::kSostenutoCtlMdId || mm->d0 == midi::kSoftPedalCtlMdId ) )
{
// if the pedal is down
if( p->pedalFl )
{
skipFl = mm->d1 > 64;
p->pedalFl = false;
}
else
{
skipFl = mm->d1 <= 64;
p->pedalFl = true;
}
}
*/
if( !skipFl )
if( p->halfPedalFl )
_half_pedal_update( p, cur_time_us );
else
while( p->msgArray[ p->msgArrayOutIdx ].microsec <= cur_time_us )
{
am_midi_msg_t* mm = p->msgArray + p->msgArrayOutIdx;
//_print_midi_msg(mm);
_transmit_msg( p, mm );
}
_set_midi_msg_next_play_index(p, p->msgArrayOutIdx+1 );
_set_midi_msg_next_play_index(p, p->msgArrayOutIdx+1 );
// if all MIDI messages have been played
if( p->msgArrayOutIdx >= p->msgArrayInIdx )
{
_stop(p);
break;
// if all MIDI messages have been played
if( p->msgArrayOutIdx >= p->msgArrayInIdx )
{
_stop(p);
break;
}
}
}
}
return rc;
@ -574,17 +851,37 @@ cw::rc_t cw::midi_record_play::create( handle_t& hRef, io::handle_t ioH, const o
p->ioH = ioH;
p->cb = cb;
p->cb_arg = cb_arg;
p->halfPedalState = kHalfPedalDone;
p->halfPedalNextUs = 0;
p->halfPedalNoteDelayUs = 100 * 1000;
p->halfPedalNoteDurUs = 1000 * 1000;
p->halfPedalUpDelayUs = 1000 * 1000;
p->halfPedalDownDelayUs = 1000 * 1000;
p->halfPedalMidiPitch = 64;
p->halfPedalMidiNoteVel = 64;
p->halfPedalMidiPedalVel = 127;
if((p->midiOutDevIdx = io::midiDeviceIndex(p->ioH,p->midiOutDevLabel)) == kInvalidIdx )
for( unsigned i=0; i<p->midiDevN; ++i)
{
rc = cwLogError(kInvalidArgRC,"The MIDI output device: '%s' was not found.", cwStringNullGuard(p->midiOutDevLabel) );
goto errLabel;
}
if((p->midiOutPortIdx = io::midiDevicePortIndex(p->ioH,p->midiOutDevIdx,false,p->midiOutPortLabel)) == kInvalidIdx )
{
rc = cwLogError(kInvalidArgRC,"The MIDI output port: '%s' was not found.", cwStringNullGuard(p->midiOutPortLabel) );
goto errLabel;
midi_device_t* dev = p->midiDevA + i;
if( !p->midiDevA[i].enableFl )
continue;
if((dev->midiOutDevIdx = io::midiDeviceIndex(p->ioH,dev->midiOutDevLabel)) == kInvalidIdx )
{
rc = cwLogError(kInvalidArgRC,"The MIDI output device: '%s' was not found.", cwStringNullGuard(dev->midiOutDevLabel) );
goto errLabel;
}
if((dev->midiOutPortIdx = io::midiDevicePortIndex(p->ioH,dev->midiOutDevIdx,false,dev->midiOutPortLabel)) == kInvalidIdx )
{
rc = cwLogError(kInvalidArgRC,"The MIDI output port: '%s' was not found.", cwStringNullGuard(dev->midiOutPortLabel) );
goto errLabel;
}
printf("%s %s : %i %i\n",dev->midiOutDevLabel, dev->midiOutPortLabel, dev->midiOutDevIdx, dev->midiOutPortIdx );
}
// create the MIDI playback timer
@ -624,21 +921,25 @@ cw::rc_t cw::midi_record_play::start( handle_t h, bool rewindFl, const time::spe
{
midi_record_play_t* p = _handleToPtr(h);
p->startedFl = true;
p->pedalFl = false;
// set the end play time
if( end_play_event_timestamp == nullptr or time::isZero(*end_play_event_timestamp) )
time::setZero(p->end_play_event_timestamp);
else
{
p->end_play_event_timestamp = *end_play_event_timestamp;
p->all_off_timestamp = *end_play_event_timestamp;
time::advanceMs( p->all_off_timestamp, p->all_off_delay_ms);
}
time::get(p->start_time);
if( p->recordFl )
if( p->recordFl || p->logInFl or p->logOutFl )
{
_set_midi_msg_next_index(p, 0 );
}
else
if( !p->recordFl )
{
time::get(p->play_time);
@ -650,6 +951,12 @@ cw::rc_t cw::midi_record_play::start( handle_t h, bool rewindFl, const time::spe
// This will cause that event to be played back immediately.
time::subtractMicros(p->play_time, p->msgArray[ p->msgArrayOutIdx ].microsec );
}
if( p->halfPedalFl )
{
p->halfPedalNextUs = 0;
p->halfPedalState = kWaitForBegin;
}
io::timerStart( p->ioH, io::timerIdToIndex(p->ioH, kMidiRecordPlayTimerId) );
}
@ -712,6 +1019,12 @@ cw::rc_t cw::midi_record_play::save( handle_t h, const char* fn )
return _midi_write(p,fn);
}
cw::rc_t cw::midi_record_play::save_csv( handle_t h, const char* fn )
{
midi_record_play_t* p = _handleToPtr(h);
return _write_csv(p,fn);
}
cw::rc_t cw::midi_record_play::open( handle_t h, const char* fn )
{
midi_record_play_t* p = _handleToPtr(h);
@ -734,13 +1047,15 @@ cw::rc_t cw::midi_record_play::load( handle_t h, const midi_msg_t* msg, unsigned
{
p->msgArray[i].id = msg[i].id;
p->msgArray[i].timestamp = msg[i].timestamp;
p->msgArray[i].loc = msg[i].loc;
p->msgArray[i].ch = msg[i].ch;
p->msgArray[i].status = msg[i].status;
p->msgArray[i].d0 = msg[i].d0;
p->msgArray[i].d1 = msg[i].d1;
p->msgArray[i].devIdx = p->midiOutDevIdx;
p->msgArray[i].portIdx = p->midiOutPortIdx;
p->msgArray[i].devIdx = kInvalidIdx;
p->msgArray[i].portIdx = kInvalidIdx;
p->msgArray[i].microsec = time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp);
}
p->msgArrayInIdx = msg_count;
@ -766,13 +1081,12 @@ cw::rc_t cw::midi_record_play::seek( handle_t h, time::spec_t seek_timestamp )
{
p->msgArrayOutIdx = i;
_transmit_pedal( p, mm->ch, midi::kSustainCtlMdId, damp_down_fl );
_transmit_pedal( p, mm->ch, midi::kSostenutoCtlMdId, sost_down_fl );
_transmit_pedal( p, mm->ch, midi::kSoftPedalCtlMdId, soft_down_fl );
_transmit_pedal( p, mm->ch, midi::kSustainCtlMdId, damp_down_fl, 0 );
_transmit_pedal( p, mm->ch, midi::kSostenutoCtlMdId, sost_down_fl, 0 );
_transmit_pedal( p, mm->ch, midi::kSoftPedalCtlMdId, soft_down_fl, 0 );
cwLogInfo("damper: %s.", damp_down_fl ? "down" : "up");
//io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, midi::kSustainCtlMdId, damp_down_fl ? 127 : 0 );
//io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, midi::kSostenutoCtlMdId, sost_down_fl ? 127 : 0 );
//io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, midi::kSoftPedalCtlMdId, soft_down_fl ? 127 : 0 );
break;
}
@ -813,7 +1127,17 @@ unsigned cw::midi_record_play::event_count( handle_t h )
unsigned cw::midi_record_play::event_index( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->recordFl ? p->msgArrayInIdx : p->msgArrayOutIdx;
return p->recordFl ? p->iMsgArrayInIdx : p->msgArrayOutIdx;
}
unsigned cw::midi_record_play::event_loc( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
if( !p->recordFl && 0 <= p->msgArrayOutIdx && p->msgArrayOutIdx < p->msgArrayN )
return p->msgArray[ p->msgArrayOutIdx ].loc;
return kInvalidId;
}
@ -842,7 +1166,49 @@ cw::rc_t cw::midi_record_play::exec( handle_t h, const io::msg_t& m )
return rc;
}
unsigned cw::midi_record_play::device_count( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->midiDevN;
}
bool cw::midi_record_play::is_device_enabled( handle_t h, unsigned devIdx )
{
midi_record_play_t* p = _handleToPtr(h);
bool fl = false;
if( devIdx >= p->midiDevN )
cwLogError(kInvalidArgRC,"The MIDI record-play device index '%i' is invalid.",devIdx );
else
fl = p->midiDevA[devIdx].enableFl;
return fl;
}
void cw::midi_record_play::enable_device( handle_t h, unsigned devIdx, bool enableFl )
{
midi_record_play_t* p = _handleToPtr(h);
if( devIdx >= p->midiDevN )
cwLogError(kInvalidArgRC,"The MIDI record-play device index '%i' is invalid.",devIdx );
else
{
p->midiDevA[devIdx].enableFl = enableFl;
printf("Enable: %i = %i\n",devIdx,enableFl);
}
}
void cw::midi_record_play::half_pedal_params( handle_t h, unsigned noteDelayMs, unsigned pitch, unsigned vel, unsigned pedal_vel, unsigned noteDurMs, unsigned downDelayMs )
{
midi_record_play_t* p = _handleToPtr(h);
p->halfPedalNoteDelayUs = noteDelayMs * 1000;
p->halfPedalNoteDurUs = noteDurMs * 1000;
p->halfPedalDownDelayUs = downDelayMs * 1000;
p->halfPedalMidiPitch = pitch;
p->halfPedalMidiNoteVel = vel;
p->halfPedalMidiPedalVel= pedal_vel;
}
cw::rc_t cw::midi_record_play::am_to_midi_file( const char* am_filename, const char* midi_filename )
{

View File

@ -12,14 +12,15 @@ namespace cw
{
unsigned id;
time::spec_t timestamp;
unsigned loc;
uint8_t ch;
uint8_t status;
uint8_t d0;
uint8_t d1;
uint8_t d1;
} midi_msg_t;
typedef void (*event_callback_t)( void* arg, unsigned id, const time::spec_t timestamp, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 );
typedef void (*event_callback_t)( void* arg, unsigned id, const time::spec_t timestamp, unsigned loc, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 );
rc_t create( handle_t& hRef, io::handle_t ioH, const object_t& cfg, event_callback_t cb=nullptr, void* cb_arg=nullptr );
@ -39,15 +40,27 @@ namespace cw
bool thru_state( handle_t h );
rc_t save( handle_t h, const char* fn );
rc_t save_csv( handle_t h, const char* fn );
rc_t open( handle_t h, const char* fn );
// Load the playback buffer with messages to output.
rc_t load( handle_t h, const midi_msg_t* msg, unsigned msg_count );
rc_t seek( handle_t h, time::spec_t timestamp );
unsigned event_count( handle_t h ); // Current count of stored messages.
unsigned event_index( handle_t h ); // record mode: index of next event to store play mode:index of next event to play
unsigned event_loc( handle_t h ); // play mode: loc of next event to play record mode:kInvalidId
unsigned event_count( handle_t h );
unsigned event_index( handle_t h );
rc_t exec( handle_t h, const io::msg_t& msg );
unsigned device_count( handle_t h );
bool is_device_enabled( handle_t h, unsigned devIdx );
void enable_device( handle_t h, unsigned devIdx, bool enableFl );
void half_pedal_params( handle_t h, unsigned noteDelayMs, unsigned pitch, unsigned vel, unsigned pedal_vel, unsigned noteDurMs, unsigned downDelayMs );
// Convert an audio-midi file to a MIDI file
rc_t am_to_midi_file( const char* am_filename, const char* midi_filename );
rc_t am_to_midi_dir( const char* inDir );

View File

@ -35,10 +35,20 @@ namespace cw
kPanelDivId = 1000,
kQuitBtnId,
kIoReportBtnId,
kNetPrintBtnId,
kReportBtnId,
kStartBtnId,
kStopBtnId,
kPrintMidiCheckId,
kPianoMidiCheckId,
kSamplerMidiCheckId,
kSyncDelayMsId,
kWetInGainId,
kWetOutGainId,
kDryGainId,
kMidiThruCheckId,
kCurMidiEvtCntId,
@ -59,6 +69,13 @@ namespace cw
kStatusId,
kHalfPedalPedalVel,
kHalfPedalDelayMs,
kHalfPedalPitch,
kHalfPedalVel,
kHalfPedalDurMs,
kHalfPedalDnDelayMs,
kLogId,
kFragListId,
@ -81,6 +98,12 @@ namespace cw
};
enum
{
kPiano_MRP_DevIdx = 0,
kSampler_MRP_DevIdx = 1
};
enum
{
kAmMidiTimerId
@ -93,10 +116,20 @@ namespace cw
{ ui::kRootAppId, kPanelDivId, "panelDivId" },
{ kPanelDivId, kQuitBtnId, "quitBtnId" },
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
{ kPanelDivId, kNetPrintBtnId, "netPrintBtnId" },
{ kPanelDivId, kReportBtnId, "reportBtnId" },
{ kPanelDivId, kStartBtnId, "startBtnId" },
{ kPanelDivId, kStopBtnId, "stopBtnId" },
{ kPanelDivId, kPrintMidiCheckId, "printMidiCheckId" },
{ kPanelDivId, kPianoMidiCheckId, "pianoMidiCheckId" },
{ kPanelDivId, kSamplerMidiCheckId,"samplerMidiCheckId" },
{ kPanelDivId, kSyncDelayMsId, "syncDelayMsId" },
{ kPanelDivId, kWetInGainId, "wetInGainId" },
{ kPanelDivId, kWetOutGainId, "wetOutGainId" },
{ kPanelDivId, kDryGainId, "dryGainId" },
{ kPanelDivId, kMidiThruCheckId, "midiThruCheckId" },
{ kPanelDivId, kCurMidiEvtCntId, "curMidiEvtCntId" },
@ -114,6 +147,15 @@ namespace cw
{ kPanelDivId, kInsertLocId, "insertLocId" },
{ kPanelDivId, kInsertBtnId, "insertBtnId" },
{ kPanelDivId, kDeleteBtnId, "deleteBtnId" },
{ kPanelDivId, kHalfPedalPedalVel, "halfPedalPedalVelId" },
{ kPanelDivId, kHalfPedalDelayMs, "halfPedalDelayMsId" },
{ kPanelDivId, kHalfPedalPitch, "halfPedalPitchId" },
{ kPanelDivId, kHalfPedalVel, "halfPedalVelId" },
{ kPanelDivId, kHalfPedalDurMs, "halfPedalDurMsId" },
{ kPanelDivId, kHalfPedalDnDelayMs, "halfPedalDnDelayMsId" },
{ kPanelDivId, kStatusId, "statusId" },
{ kPanelDivId, kLogId, "logId" },
@ -156,6 +198,7 @@ namespace cw
const char* record_fn;
const char* record_fn_ext;
const char* scoreFn;
const object_t* midi_play_record_cfg;
const object_t* frag_panel_cfg;
const object_t* presets_cfg;
const object_t* flow_cfg;
@ -184,7 +227,15 @@ namespace cw
double crossFadeSrate;
unsigned crossFadeCnt;
bool printMidiFl;
unsigned hpDelayMs;
unsigned hpPedalVel;
unsigned hpPitch;
unsigned hpVel;
unsigned hpDurMs;
unsigned hpDnDelayMs;
} app_t;
rc_t _parseCfg(app_t* app, const object_t* cfg, const object_t*& params_cfgRef )
@ -198,18 +249,31 @@ namespace cw
goto errLabel;
}
if((rc = params_cfgRef->getv( "record_dir", app->record_dir,
"record_fn", app->record_fn,
"record_fn_ext", app->record_fn_ext,
"score_fn", app->scoreFn,
"frag_panel", app->frag_panel_cfg,
"presets", app->presets_cfg,
"crossFadeSrate",app->crossFadeSrate,
"crossFadeCount",app->crossFadeCnt)) != kOkRC )
if((rc = params_cfgRef->getv( "record_dir", app->record_dir,
"record_fn", app->record_fn,
"record_fn_ext", app->record_fn_ext,
"score_fn", app->scoreFn,
"midi_play_record", app->midi_play_record_cfg,
"frag_panel", app->frag_panel_cfg,
"presets", app->presets_cfg,
"crossFadeSrate", app->crossFadeSrate,
"crossFadeCount", app->crossFadeCnt)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"Preset Select App configuration parse failed.");
}
if((app->scoreFn = filesys::expandPath( app->scoreFn )) == nullptr )
{
rc = cwLogError(kInvalidArgRC,"The score file name is invalid.");
goto errLabel;
}
if((app->record_dir = filesys::expandPath(app->record_dir)) == nullptr )
{
rc = cwLogError(kInvalidArgRC,"The record directory path is invalid.");
goto errLabel;
}
// verify that the output directory exists
if((rc = filesys::isDir(app->record_dir)) != kOkRC )
if((rc = filesys::makeDir(app->record_dir)) != kOkRC )
@ -257,6 +321,8 @@ namespace cw
rc_t _free( app_t& app )
{
mem::release((char*&)app.record_dir);
mem::release((char*&)app.scoreFn);
preset_sel::destroy(app.psH);
io_flow::destroy(app.ioFlowH);
midi_record_play::destroy(app.mrpH);
@ -283,15 +349,15 @@ namespace cw
{
const char* preset_label = preset_sel::preset_label(app->psH,preset_idx);
cwLogInfo("Apply preset: '%s'.\n", preset_idx==kInvalidIdx ? "<invalid>" : preset_label);
cwLogInfo("Apply preset: '%s'.", preset_idx==kInvalidIdx ? "<invalid>" : preset_label);
if( preset_label != nullptr )
{
io_flow::apply_preset( app->ioFlowH, flow_cross::kNextDestId, preset_label );
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wd_bal", "in", flow::kAnyChIdx, (dsp::real_t)frag->wetDryGain );
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "split_wet", "gain", flow::kAnyChIdx, (dsp::real_t)frag->igain );
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "cmp", "ogain", flow::kAnyChIdx, (dsp::real_t)frag->ogain );
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wet_in_gain", "gain", flow::kAnyChIdx, (dsp::real_t)frag->igain );
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wet_out_gain","gain", flow::kAnyChIdx, (dsp::real_t)frag->ogain );
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wd_bal", "in", flow::kAnyChIdx, (dsp::real_t)frag->wetDryGain );
io_flow::begin_cross_fade( app->ioFlowH, frag->fadeOutMs );
}
@ -301,34 +367,98 @@ namespace cw
return kOkRC;
}
void _midi_play_callback( void* arg, unsigned id, const time::spec_t timestamp, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
// Turn on the selection border for the clicked fragment
rc_t _do_select_frag( app_t* app, unsigned clickedUuId )
{
app_t* app = (app_t*)arg;
if( id != kInvalidId )
rc_t rc = kOkRC;
// get the last selected fragment
unsigned prevFragId = preset_sel::ui_select_fragment_id(app->psH);
unsigned prevUuId = preset_sel::frag_to_gui_id(app->psH,prevFragId,false);
// is the last selected fragment the same as the clicked fragment
bool reclickFl = prevUuId == clickedUuId;
// if a different fragment was clicked then deselect the last fragment in the UI
if( !reclickFl )
{
const unsigned buf_byte_cnt = 256;
char buf[ buf_byte_cnt ];
event_to_string( app->scoreH, id, buf, buf_byte_cnt );
printf("%s\n",buf);
const preset_sel::frag_t* f = nullptr;
if( preset_sel::track_timestamp( app->psH, timestamp, f ) )
{
//printf("NEW FRAG: id:%i loc:%i\n", f->fragId, f->endLoc );
_apply_preset( app, timestamp, f );
}
if(prevUuId != kInvalidId )
io::uiSetSelect( app->ioH, prevUuId, false );
// select or deselect the clicked fragment
io::uiSetSelect( app->ioH, clickedUuId, !reclickFl );
}
// Note: calls to uiSetSelect() produce callbacks to _onUiSelect().
return rc;
}
void _midi_play_callback( void* arg, unsigned id, const time::spec_t timestamp, unsigned loc, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
{
app_t* app = (app_t*)arg;
if( app->printMidiFl )
{
const unsigned buf_byte_cnt = 256;
char buf[ buf_byte_cnt ];
// if this event is not in the score
if( id == kInvalidId )
{
// TODO: print this out in the same format as event_to_string()
snprintf(buf,buf_byte_cnt,"ch:%i status:0x%02x d0:%i d1:%i",ch,status,d0,d1);
}
else
score::event_to_string( app->scoreH, id, buf, buf_byte_cnt );
printf("%s\n",buf);
}
if( midi_record_play::is_started(app->mrpH) )
{
const preset_sel::frag_t* f = nullptr;
if( preset_sel::track_timestamp( app->psH, timestamp, f ) )
{
//printf("NEW FRAG: id:%i loc:%i\n", f->fragId, f->endLoc );
_apply_preset( app, timestamp, f );
if( f != nullptr )
_do_select_frag( app, f->guiUuId );
}
}
}
// Find the closest locMap equal to or after 'loc'
loc_map_t* _find_loc( app_t* app, unsigned loc )
{
unsigned i=0;
loc_map_t* pre_loc_map = nullptr;
for(; i<app->locMapN; ++i)
if( app->locMap[i].loc == loc )
return app->locMap + i;
return nullptr;
{
if( app->locMap[i].loc >= loc )
return app->locMap +i;
pre_loc_map = app->locMap + i;
}
return pre_loc_map;
}
rc_t _do_stop_play( app_t* app )
{
rc_t rc = kOkRC;
if((rc = midi_record_play::stop(app->mrpH)) != kOkRC )
{
rc = cwLogError(rc,"MIDI stop failed.");
goto errLabel;
}
errLabel:
return rc;
}
@ -339,6 +469,16 @@ namespace cw
loc_map_t* begMap = nullptr;
loc_map_t* endMap = nullptr;
// if the player is already playing then stop it
if( midi_record_play::is_started(app->mrpH) )
{
rc = _do_stop_play(app);
goto errLabel;
}
midi_record_play::half_pedal_params( app->mrpH, app->hpDelayMs, app->hpPitch, app->hpVel, app->hpPedalVel, app->hpDurMs, app->hpDnDelayMs );
if((begMap = _find_loc(app,begLoc)) == nullptr )
{
rc = cwLogError(kInvalidArgRC,"The begin play location is not valid.");
@ -381,7 +521,7 @@ namespace cw
}
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_loc(app->mrpH) );
errLabel:
return rc;
@ -415,8 +555,10 @@ namespace cw
void _update_event_ui( app_t* app )
{
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), midi_record_play::event_count(app->mrpH) );
//io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
//io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), midi_record_play::event_count(app->mrpH) );
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), app->maxLoc );
}
// Update the UI with the value from the the fragment data record.
@ -752,7 +894,7 @@ namespace cw
}
rc_t _restore( app_t* app )
rc_t _restore_fragment_data( app_t* app )
{
rc_t rc = kOkRC;
char* fn = nullptr;
@ -778,10 +920,10 @@ namespace cw
goto errLabel;
}
preset_sel::report( app->psH );
//preset_sel::report( app->psH );
f = preset_sel::get_fragment_base(app->psH);
for(; f!=nullptr; f=f->link)
for(int i=0; f!=nullptr; f=f->link,++i)
{
unsigned fragId = f->fragId;
@ -792,7 +934,7 @@ namespace cw
}
_update_frag_ui(app, fragId );
}
@ -834,21 +976,18 @@ namespace cw
return rc;
}
int _compare_loc_map( const void* m0, const void* m1 )
{ return ((const loc_map_t*)m0)->loc - ((const loc_map_t*)m1)->loc; }
rc_t _do_load( app_t* app )
rc_t _load_piano_score( app_t* app, unsigned& midiEventCntRef )
{
rc_t rc = kOkRC;
const score::event_t* e = nullptr;
unsigned midiEventN = 0;
midi_record_play::midi_msg_t* m = nullptr;
// if the score is already loaded
if( app->scoreH.isValid() )
return rc;
cwLogInfo("Loading");
_set_status(app,"Loading...");
midiEventCntRef = 0;
// create the score
if((rc = score::create( app->scoreH, app->scoreFn )) != kOkRC )
{
@ -885,6 +1024,7 @@ namespace cw
m[i].d0 = e->d0;
m[i].d1 = e->d1;
m[i].id = e->uid;
m[i].loc = e->loc;
app->locMap[i].loc = e->loc;
app->locMap[i].timestamp = m[i].timestamp;
@ -895,6 +1035,8 @@ namespace cw
++i;
}
qsort( app->locMap, app->locMapN, sizeof(loc_map_t), _compare_loc_map );
// load the player with the msg list
if((rc = midi_record_play::load( app->mrpH, m, midiEventN )) != kOkRC )
{
@ -902,36 +1044,80 @@ namespace cw
goto errLabel;
}
mem::free(m);
cwLogInfo("%i MIDI events loaded from score.", midiEventN );
mem::free(m);
}
// set the range of the global play location controls
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kBegPlayLocNumbId), app->minLoc, app->maxLoc, 1, 0, app->minLoc );
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kEndPlayLocNumbId), app->minLoc, app->maxLoc, 1, 0, app->maxLoc );
errLabel:
midiEventCntRef = midiEventN;
// enable the 'End Loc' number box since the score is loaded
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kInsertLocId ), true );
return rc;
}
rc_t _do_load( app_t* app )
{
rc_t rc = kOkRC;
unsigned midiEventN = 0;
bool firstLoadFl = !app->scoreH.isValid();
unsigned minLoc = firstLoadFl ? 0 : app->minLoc;
unsigned maxLoc = firstLoadFl ? 0 : app->maxLoc;
// if the score is already loaded
//if( app->scoreH.isValid() )
// return rc;
cwLogInfo("Loading");
_set_status(app,"Loading...");
// Load the piano score
if((rc = _load_piano_score(app,midiEventN)) != kOkRC )
goto errLabel;
if( !firstLoadFl)
{
minLoc = std::max(minLoc,app->minLoc);
maxLoc = std::min(maxLoc,app->maxLoc);
}
// reset the timestamp tracker
track_timestamp_reset( app->psH );
// set the range of the global play location controls
if( firstLoadFl )
{
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kBegPlayLocNumbId), app->minLoc, app->maxLoc, 1, 0, minLoc );
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kEndPlayLocNumbId), app->minLoc, app->maxLoc, 1, 0, maxLoc );
// enable the 'End Loc' number box since the score is loaded
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kInsertLocId ), true );
}
// update the current event and event count
_update_event_ui(app);
// enable the start/stop buttons
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kStartBtnId ), true );
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kStopBtnId ), true );
// restore the fragment records
if((rc = _restore( app )) != kOkRC )
{
rc = cwLogError(rc,"Restore failed.");
goto errLabel;
}
if( firstLoadFl )
if((rc = _restore_fragment_data( app )) != kOkRC )
{
rc = cwLogError(rc,"Restore failed.");
goto errLabel;
}
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kLoadBtnId ), false );
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kLoadBtnId ), true );
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kSaveBtnId ), true );
cwLogInfo("'%s' loaded.",app->scoreFn);
errLabel:
@ -946,25 +1132,11 @@ namespace cw
return rc;
}
rc_t _on_ui_start( app_t* app )
{
return _do_play(app, app->beg_play_loc, app->end_play_loc );
}
rc_t _on_ui_stop( app_t* app )
{
rc_t rc = kOkRC;
if((rc = midi_record_play::stop(app->mrpH)) != kOkRC )
{
rc = cwLogError(rc,"MIDI start failed.");
goto errLabel;
}
errLabel:
return rc;
}
rc_t _set_midi_thru_state( app_t* app, bool thru_fl )
{
@ -990,26 +1162,31 @@ namespace cw
{
rc_t rc = kOkRC;
switch( appId )
{
case kBegPlayLocNumbId:
app->beg_play_loc = loc;
break;
case kEndPlayLocNumbId:
app->end_play_loc = loc;
break;
}
bool enableFl = app->beg_play_loc < app->end_play_loc;
_enable_global_play_btn(app, enableFl );
if(enableFl)
_clear_status(app);
// verify that the location exists
if( _find_loc(app,loc) == nullptr )
_set_status(app,"%i is an invalid location.",loc);
else
_set_status(app,"Invalid play location range.");
{
switch( appId )
{
case kBegPlayLocNumbId:
app->beg_play_loc = loc;
break;
case kEndPlayLocNumbId:
app->end_play_loc = loc;
break;
}
bool enableFl = app->beg_play_loc < app->end_play_loc;
_enable_global_play_btn(app, enableFl );
if(enableFl)
_clear_status(app);
else
_set_status(app,"Invalid play location range.");
}
return rc;
}
@ -1131,10 +1308,126 @@ namespace cw
if( rc != kOkRC )
rc = cwLogError(rc,"Fragment delete failed.");
else
cwLogInfo("Fragment %i deleted.",fragId);
return rc;
}
void _on_echo_midi_enable( app_t* app, unsigned uuId, unsigned mrp_dev_idx )
{
if( mrp_dev_idx <= midi_record_play::device_count(app->mrpH) )
{
bool enableFl = midi_record_play::is_device_enabled(app->mrpH, mrp_dev_idx );
io::uiSendValue( app->ioH, uuId, enableFl );
}
}
void _on_midi_enable( app_t* app, unsigned checkAppId, unsigned mrp_dev_idx, bool enableFl )
{
unsigned midi_dev_n = midi_record_play::device_count(app->mrpH);
if( mrp_dev_idx < midi_dev_n )
midi_record_play::enable_device(app->mrpH, mrp_dev_idx, enableFl );
else
cwLogError(kInvalidArgRC,"%i is not a valid MIDI device index for device count:%i.",mrp_dev_idx,midi_dev_n);
}
rc_t _on_echo_master_value( app_t* app, unsigned varId, unsigned uuId )
{
rc_t rc = kOkRC;
double val = 0;
if((rc = get_value( app->psH, kInvalidId, varId, kInvalidId, val )) != kOkRC )
rc = cwLogError(rc,"Unable to get the master value for var id:%i.",varId);
else
io::uiSendValue( app->ioH, uuId, val );
return rc;
}
rc_t _on_master_value( app_t* app, const char* inst_label, const char* var_label, unsigned varId, double value )
{
rc_t rc = kOkRC;
if((rc = preset_sel::set_value( app->psH, kInvalidId, varId, kInvalidId, value )) != kOkRC )
rc = cwLogError(rc,"Master value set failed on varId:%i %s.%s.",varId,cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
else
if((rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, inst_label, var_label, flow::kAnyChIdx, (dsp::real_t)value )) != kOkRC )
rc = cwLogError(rc,"Master value send failed on %s.%s.",cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
return rc;
}
rc_t _on_ui_half_pedal_value( app_t* app, unsigned appId, unsigned uuId, unsigned value )
{
switch( appId )
{
case kHalfPedalDelayMs:
app->hpDelayMs = value;
break;
case kHalfPedalPedalVel:
app->hpPedalVel = value;
break;
case kHalfPedalPitch:
app->hpPitch = value;
break;
case kHalfPedalVel:
app->hpVel = value;
break;
case kHalfPedalDurMs:
app->hpDurMs = value;
break;
case kHalfPedalDnDelayMs:
app->hpDnDelayMs = value;
break;
default:
{ assert(0); }
}
return kOkRC;
}
rc_t _on_echo_half_pedal( app_t* app, unsigned appId, unsigned uuId )
{
switch( appId )
{
case kHalfPedalDelayMs:
io::uiSendValue( app->ioH, uuId, app->hpDelayMs );
break;
case kHalfPedalPedalVel:
io::uiSendValue( app->ioH, uuId, app->hpPedalVel );
break;
case kHalfPedalPitch:
io::uiSendValue( app->ioH, uuId, app->hpPitch );
break;
case kHalfPedalVel:
io::uiSendValue( app->ioH, uuId, app->hpVel );
break;
case kHalfPedalDurMs:
io::uiSendValue( app->ioH, uuId, app->hpDurMs );
break;
case kHalfPedalDnDelayMs:
io::uiSendValue( app->ioH, uuId, app->hpDnDelayMs );
break;
default:
{ assert(0); }
}
return kOkRC;
}
rc_t _onUiInit(app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
@ -1150,7 +1443,7 @@ namespace cw
for(; f!=nullptr; f=f->link)
_update_frag_ui( app, f->fragId );
//_do_load(app);
_do_load(app);
return rc;
}
@ -1168,11 +1461,17 @@ namespace cw
case kIoReportBtnId:
io::report( app->ioH );
break;
case kNetPrintBtnId:
io_flow::print_network(app->ioFlowH,flow_cross::kCurDestId);
break;
case kReportBtnId:
//preset_sel::report( app->psH );
preset_sel::report( app->psH );
//io_flow::apply_preset( app->ioFlowH, 2000.0, app->tmp==0 ? "a" : "b");
//app->tmp = !app->tmp;
//io_flow::print(app->ioFlowH);
//midi_record_play::save_csv(app->mrpH,"/home/kevin/temp/mrp_1.csv");
break;
case kSaveBtnId:
@ -1193,9 +1492,37 @@ namespace cw
break;
case kStopBtnId:
_on_ui_stop(app);
_do_stop_play(app);
break;
case kPrintMidiCheckId:
app->printMidiFl = m.value->u.b;
break;
case kPianoMidiCheckId:
_on_midi_enable( app, m.appId, kPiano_MRP_DevIdx, m.value->u.b );
break;
case kSamplerMidiCheckId:
_on_midi_enable( app, m.appId, kSampler_MRP_DevIdx, m.value->u.b );
break;
case kWetInGainId:
_on_master_value( app, "mstr_wet_in_gain","gain", preset_sel::kMasterWetInGainVarId, m.value->u.d );
break;
case kWetOutGainId:
_on_master_value( app, "mstr_wet_out_gain","gain",preset_sel::kMasterWetOutGainVarId, m.value->u.d );
break;
case kDryGainId:
_on_master_value( app, "mstr_dry_out_gain", "gain", preset_sel::kMasterDryGainVarId, m.value->u.d );
break;
case kSyncDelayMsId:
_on_master_value( app, "sync_delay","delayMs",preset_sel::kMasterSyncDelayMsVarId, (double)m.value->u.i );
break;
case kBegPlayLocNumbId:
_on_ui_play_loc(app, m.appId, m.value->u.i);
break;
@ -1216,6 +1543,15 @@ namespace cw
_on_ui_delete_btn(app);
break;
case kHalfPedalPedalVel:
case kHalfPedalDelayMs:
case kHalfPedalPitch:
case kHalfPedalVel:
case kHalfPedalDurMs:
case kHalfPedalDnDelayMs:
_on_ui_half_pedal_value( app, m.appId, m.uuId, m.value->u.u );
break;
case kFragInGainId:
_on_ui_frag_value( app, m.uuId, m.value->u.d);
break;
@ -1286,31 +1622,6 @@ namespace cw
return kOkRC;
}
rc_t _onUiClick( app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
// get the last selected fragment
unsigned prevFragId = preset_sel::ui_select_fragment_id(app->psH);
unsigned prevUuId = preset_sel::frag_to_gui_id(app->psH,prevFragId,false);
// is the last selected fragment the same as the clicked fragment
bool reclickFl = prevUuId == m.uuId;
// if a different fragment was clicked then deselect the last fragment in the UI
if( !reclickFl )
{
if(prevUuId != kInvalidId )
uiSetSelect( app->ioH, prevUuId, false );
// select or deselect the clicked fragment
uiSetSelect( app->ioH, m.uuId, !reclickFl );
}
// Note: calls to uiSetSelect() produce callbacks to _onUiSelect().
return rc;
}
rc_t _onUiSelect( app_t* app, const io::ui_msg_t& m )
{
@ -1333,10 +1644,50 @@ namespace cw
errLabel:
return rc;
}
rc_t _onUiEcho(app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
switch( m.appId )
{
case kPrintMidiCheckId:
break;
case kPianoMidiCheckId:
_on_echo_midi_enable( app, m.uuId, kPiano_MRP_DevIdx );
break;
case kSamplerMidiCheckId:
_on_echo_midi_enable( app, m.uuId, kSampler_MRP_DevIdx );
break;
case kWetInGainId:
_on_echo_master_value( app, preset_sel::kMasterWetInGainVarId, m.uuId );
break;
case kWetOutGainId:
_on_echo_master_value( app, preset_sel::kMasterWetOutGainVarId, m.uuId );
break;
case kDryGainId:
_on_echo_master_value( app, preset_sel::kMasterDryGainVarId, m.uuId );
break;
case kSyncDelayMsId:
_on_echo_master_value( app, preset_sel::kMasterSyncDelayMsVarId, m.uuId );
break;
case kHalfPedalPedalVel:
case kHalfPedalDelayMs:
case kHalfPedalPitch:
case kHalfPedalVel:
case kHalfPedalDurMs:
case kHalfPedalDnDelayMs:
_on_echo_half_pedal( app, m.appId, m.uuId );
break;
}
return rc;
}
@ -1367,7 +1718,7 @@ namespace cw
break;
case ui::kClickOpId:
_onUiClick( app, m );
_do_select_frag( app, m.uuId );
break;
case ui::kSelectOpId:
@ -1402,7 +1753,7 @@ namespace cw
{
midi_record_play::exec( app->mrpH, *m );
if( midi_record_play::is_started(app->mrpH) )
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_loc(app->mrpH) );
}
if( app->ioFlowH.isValid() )
@ -1453,8 +1804,9 @@ namespace cw
cw::rc_t cw::preset_sel_app::main( const object_t* cfg, const object_t* flow_proc_dict )
{
rc_t rc;
app_t app = { };
app_t app = { .hpDelayMs=250, .hpPedalVel=127, .hpPitch=64, .hpVel=64, .hpDurMs=500, .hpDnDelayMs=1000 };
const object_t* params_cfg = nullptr;
// Parse the configuration
@ -1477,14 +1829,13 @@ cw::rc_t cw::preset_sel_app::main( const object_t* cfg, const object_t* flow_pro
goto errLabel;
}
// create the MIDI record-play object
if((rc = midi_record_play::create(app.mrpH,app.ioH,*params_cfg,_midi_play_callback,&app)) != kOkRC )
if((rc = midi_record_play::create(app.mrpH,app.ioH,*app.midi_play_record_cfg,_midi_play_callback,&app)) != kOkRC )
{
rc = cwLogError(rc,"MIDI record-play object create failed.");
goto errLabel;
}
// create the IO Flow controller
if(app.flow_cfg==nullptr || flow_proc_dict==nullptr || (rc = io_flow::create(app.ioFlowH,app.ioH,app.crossFadeSrate,app.crossFadeCnt,*flow_proc_dict,*app.flow_cfg)) != kOkRC )
{
@ -1492,13 +1843,14 @@ cw::rc_t cw::preset_sel_app::main( const object_t* cfg, const object_t* flow_pro
goto errLabel;
}
// start the io framework instance
// start the IO framework instance
if((rc = io::start(app.ioH)) != kOkRC )
{
rc = cwLogError(rc,"Preset-select app start failed.");
goto errLabel;
}
// execute the io framework
while( !isShuttingDown(app.ioH))
{

View File

@ -129,7 +129,7 @@ namespace cw
// If label is NULL or labelCharCnt==0 then a pointer to an internal static
// buffer is returned. If label[] is given the it
// should have at least 5 (kMidiPitchCharCnt) char's (including the terminating zero).
// should have at least 5 (kMidiSciPitchCharCnt) char's (including the terminating zero).
// If 'pitch' is outside of the range 0-127 then a blank string is returned.
const char* midiToSciPitch( uint8_t pitch, char* label, unsigned labelCharCnt );

View File

@ -464,7 +464,7 @@ namespace cw
void _cmMpReportPort( textBuf::handle_t tbH, const port_t* port )
{
textBuf::print( tbH," client:%i port:%i %s caps:(",port->alsa_addr.client,port->alsa_addr.port,port->nameStr);
textBuf::print( tbH," client:%i port:%i '%s' caps:(",port->alsa_addr.client,port->alsa_addr.port,port->nameStr);
if( port->alsa_cap & SND_SEQ_PORT_CAP_READ ) textBuf::print( tbH,"Read " );
if( port->alsa_cap & SND_SEQ_PORT_CAP_WRITE ) textBuf::print( tbH,"Writ " );
if( port->alsa_cap & SND_SEQ_PORT_CAP_SYNC_READ ) textBuf::print( tbH,"Syrd " );
@ -896,7 +896,7 @@ void cw::midi::device::report( handle_t h, textBuf::handle_t tbH )
{
const dev_t* d = p->devArray + i;
textBuf::print( tbH,"%i : Device: %s \n",i,cwStringNullGuard(d->nameStr));
textBuf::print( tbH,"%i : Device: '%s' \n",i,cwStringNullGuard(d->nameStr));
if(d->iPortCnt > 0 )
textBuf::print( tbH," Input:\n");

View File

@ -6,6 +6,7 @@
#include "cwPianoScore.h"
#include "cwMidi.h"
#include "cwTime.h"
#include "cwFile.h"
namespace cw
{
@ -16,6 +17,11 @@ namespace cw
event_t* base;
event_t* end;
unsigned maxLocId;
event_t** uid_mapA;
unsigned uid_mapN;
unsigned min_uid;
} score_t;
score_t* _handleToPtr(handle_t h)
@ -38,6 +44,198 @@ namespace cw
return rc;
}
unsigned _scan_to_end_of_field( const char* lineBuf, unsigned buf_idx, unsigned bufCharCnt )
{
for(; buf_idx < bufCharCnt; ++buf_idx )
{
if( lineBuf[buf_idx] == '"' )
{
for(++buf_idx; buf_idx < bufCharCnt; ++buf_idx)
if( lineBuf[buf_idx] == '"' )
break;
}
if( lineBuf[buf_idx] == ',')
break;
}
return buf_idx;
}
rc_t _parse_csv_double( const char* lineBuf, unsigned bfi, unsigned efi, double &valueRef )
{
errno = 0;
valueRef = strtod(lineBuf+bfi,nullptr);
if( errno != 0 )
return cwLogError(kOpFailRC,"CSV String to number conversion failed.");
return kOkRC;
}
rc_t _parse_csv_unsigned( const char* lineBuf, unsigned bfi, unsigned efi, unsigned &valueRef )
{
rc_t rc;
double v;
if((rc = _parse_csv_double(lineBuf,bfi,efi,v)) == kOkRC )
valueRef = (unsigned)v;
return rc;
}
rc_t _parse_csv_line( score_t* p, event_t* e, char* line_buf, unsigned lineBufCharCnt )
{
enum
{
kMeas_FIdx,
kIndex_FIdx,
kVoice_FIdx,
kLoc_FIdx,
kTick_FIdx,
kSec_FIdx,
kDur_FIdx,
kRval_FIdx,
kSPitch_FIdx,
kDMark_FIdx,
kDLevel_FIdx,
kStatus_FIdx,
kD0_FIdx,
kD1_FIdx,
kBar_FIdx,
kSection_FIdx,
kBpm_FIdx,
kGrace_FIdx,
kPedal_FIdx,
kMax_FIdx
};
rc_t rc = kOkRC;
unsigned bfi = 0;
unsigned efi = 0;
unsigned field_idx = 0;
for(field_idx=0; field_idx != kMax_FIdx; ++field_idx)
{
if((efi = _scan_to_end_of_field(line_buf,efi,lineBufCharCnt)) == kInvalidIdx )
{
rc = cwLogError( rc, "End of field scan failed");
goto errLabel;
}
if( bfi != efi )
{
switch( field_idx )
{
case kLoc_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->loc );
break;
case kSec_FIdx:
rc = _parse_csv_double( line_buf, bfi, efi, e->sec );
break;
case kStatus_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->status );
break;
case kD0_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->d0 );
break;
case kD1_FIdx:
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->d1 );
break;
default:
break;
}
}
bfi = efi + 1;
efi = efi + 1;
}
errLabel:
return rc;
}
rc_t _parse_csv( score_t* p, const char* fn )
{
rc_t rc;
file::handle_t fH;
unsigned line_count = 0;
char* lineBufPtr = nullptr;
unsigned lineBufCharCnt = 0;
event_t* e = nullptr;
if((rc = file::open( fH, fn, file::kReadFl )) != kOkRC )
{
rc = cwLogError( rc, "Piano score file open failed on '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
if((rc = file::lineCount(fH,&line_count)) != kOkRC )
{
rc = cwLogError( rc, "Line count query failed on '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
p->min_uid = kInvalidId;
p->uid_mapN = 0;
for(unsigned line=0; line<line_count; ++line)
if( line > 0 ) // skip column title line
{
if((rc = getLineAuto( fH, &lineBufPtr, &lineBufCharCnt )) != kOkRC )
{
rc = cwLogError( rc, "Line read failed on '%s' line number '%i'.",cwStringNullGuard(fn),line+1);
goto errLabel;
}
e = mem::allocZ<event_t>();
if((rc = _parse_csv_line( p, e, lineBufPtr, lineBufCharCnt )) != kOkRC )
{
mem::release(e);
rc = cwLogError( rc, "Line parse failed on '%s' line number '%i'.",cwStringNullGuard(fn),line+1);
goto errLabel;
}
// assign the UID
e->uid = line;
// link the event into the event list
if( p->end != nullptr )
p->end->link = e;
else
p->base = e;
p->end = e;
// track the max 'loc' id
if( e->loc > p->maxLocId )
p->maxLocId = e->loc;
if( p->min_uid == kInvalidId || e->uid < p->min_uid )
p->min_uid = e->uid;
p->uid_mapN += 1;
}
errLabel:
mem::release(lineBufPtr);
file::close(fH);
return rc;
}
rc_t _parse_event_list( score_t* p, const object_t* cfg )
{
rc_t rc;
@ -136,11 +334,13 @@ cw::rc_t cw::score::create( handle_t& hRef, const char* fn )
{
rc_t rc;
object_t* cfg = nullptr;
score_t* p = nullptr;
if((rc = destroy(hRef)) != kOkRC )
return rc;
// parse the cfg file
/*
if((rc = objectFromFile( fn, cfg )) != kOkRC )
{
rc = cwLogError(rc,"Score parse failed on file: '%s'.", fn);
@ -148,11 +348,23 @@ cw::rc_t cw::score::create( handle_t& hRef, const char* fn )
}
rc = create(hRef,cfg);
*/
p = mem::allocZ< score_t >();
rc = _parse_csv(p,fn);
errLabel:
hRef.set(p);
//errLabel:
if( cfg != nullptr )
cfg->free();
if( rc != kOkRC )
destroy(hRef);
return rc;
}
@ -255,6 +467,13 @@ bool cw::score::is_loc_valid( handle_t h, unsigned locId )
return locId < p->maxLocId;
}
const cw::score::event_t* cw::score::uid_to_event( handle_t h, unsigned uid )
{
score_t* p = _handleToPtr(h);
return nullptr;
}
cw::rc_t cw::score::test( const object_t* cfg )
{

View File

@ -40,6 +40,8 @@ namespace cw
unsigned loc_count( handle_t h );
bool is_loc_valid( handle_t h, unsigned locId );
const event_t* uid_to_event( handle_t h, unsigned uid );
// Format the event as a string for printing.
rc_t event_to_string( handle_t h, unsigned uid, char* buf, unsigned buf_byte_cnt );

View File

@ -31,6 +31,11 @@ namespace cw
unsigned next_frag_id;
frag_t* last_ts_frag;
double master_wet_in_gain;
double master_wet_out_gain;
double master_dry_gain;
double master_sync_delay_ms;
} preset_sel_t;
@ -205,6 +210,9 @@ namespace cw
}
bool _is_master_var_id( unsigned varId )
{ return varId > kBaseMasterVarId; }
template< typename T >
rc_t _set_value( handle_t h, unsigned fragId, unsigned varId, unsigned presetId, const T& value )
{
@ -212,9 +220,10 @@ namespace cw
preset_sel_t* p = _handleToPtr(h);
frag_t* f = nullptr;
// locate the requested fragment
if((rc = _find_frag(p,fragId,f)) != kOkRC )
goto errLabel;
// if this is not a 'master' variable then locate the requested fragment
if( !_is_master_var_id(varId) )
if((rc = _find_frag(p,fragId,f)) != kOkRC )
goto errLabel;
switch( varId )
{
@ -259,6 +268,22 @@ namespace cw
case kPlayBtnVarId:
break;
case kMasterWetInGainVarId:
p->master_wet_in_gain = value;
break;
case kMasterWetOutGainVarId:
p->master_wet_out_gain = value;
break;
case kMasterDryGainVarId:
p->master_dry_gain = value;
break;
case kMasterSyncDelayMsVarId:
p->master_sync_delay_ms = value;
break;
default:
rc = cwLogError(kInvalidIdRC,"There is no preset variable with var id:%i.",varId);
@ -280,9 +305,10 @@ namespace cw
preset_sel_t* p = _handleToPtr(h);
frag_t* f = nullptr;
// locate the requested fragment
if((rc = _find_frag(p,fragId,f)) != kOkRC )
goto errLabel;
// if this is not a 'master' variable then locate the requested fragment
if( !_is_master_var_id( varId ) )
if((rc = _find_frag(p,fragId,f)) != kOkRC )
goto errLabel;
switch( varId )
{
@ -335,6 +361,22 @@ namespace cw
case kPlayBtnVarId:
break;
case kMasterWetInGainVarId:
valueRef = p->master_wet_in_gain;
break;
case kMasterWetOutGainVarId:
valueRef = p->master_wet_out_gain;
break;
case kMasterDryGainVarId:
valueRef = p->master_dry_gain;
break;
case kMasterSyncDelayMsVarId:
valueRef = p->master_sync_delay_ms;
break;
default:
rc = cwLogError(kInvalidIdRC,"There is no preset variable with var id:%i.",varId);
goto errLabel;
@ -365,11 +407,15 @@ cw::rc_t cw::preset_sel::create( handle_t& hRef, const object_t* cfg )
p = mem::allocZ<preset_sel_t>();
// parse the cfg
if((rc = cfg->getv( "preset_labelL", labelL,
"default_gain", p->defaultGain,
"default_wet_dry_gain", p->defaultWetDryGain,
"default_fade_ms", p->defaultFadeOutMs,
"default_preset", default_preset_label)) != kOkRC )
if((rc = cfg->getv( "preset_labelL", labelL,
"default_gain", p->defaultGain,
"default_wet_dry_gain", p->defaultWetDryGain,
"default_fade_ms", p->defaultFadeOutMs,
"default_preset", default_preset_label,
"default_master_wet_in_gain", p->master_wet_in_gain,
"default_master_wet_out_gain", p->master_wet_out_gain,
"default_master_dry_gain", p->master_dry_gain,
"default_master_sync_delay_ms", p->master_sync_delay_ms)) != kOkRC )
{
rc = cwLogError(rc,"The preset configuration parse failed.");
goto errLabel;
@ -591,29 +637,27 @@ cw::rc_t cw::preset_sel::create_fragment( handle_t h, unsigned end_loc, time::sp
cw::rc_t cw::preset_sel::delete_fragment( handle_t h, unsigned fragId )
{
preset_sel_t* p = _handleToPtr(h);
frag_t* f0 = nullptr;
frag_t* f1 = p->fragL;
frag_t* f = p->fragL;
for(; f1!=nullptr; f1=f1->link)
{
if( f1->fragId == fragId )
for(; f!=nullptr; f=f->link)
if( f->fragId == fragId )
{
if( f0 == nullptr )
p->fragL = f1->link;
if( f->prev == nullptr )
p->fragL = f->link;
else
f0->link = f1->link;
f->prev->link = f->link;
if( f->link != nullptr )
f->link->prev = f->prev;
// release the fragment
mem::release(f1->presetA);
mem::release(f1);
mem::release(f->presetA);
mem::release(f);
return kOkRC;
}
f0 = f1;
}
return kOkRC;
return cwLogError(kInvalidArgRC,"The fragment '%i' could not be found to delete.",fragId);
}
bool cw::preset_sel::is_fragment_loc( handle_t h, unsigned loc )
@ -711,12 +755,24 @@ cw::rc_t cw::preset_sel::get_value( handle_t h, unsigned fragId, unsigned varId,
return rc;
}
void cw::preset_sel::track_timestamp_reset( handle_t h )
{
preset_sel_t* p = _handleToPtr(h);
p->last_ts_frag = nullptr;
}
bool cw::preset_sel::track_timestamp( handle_t h, const time::spec_t& ts, const cw::preset_sel::frag_t*& frag_Ref )
{
preset_sel_t* p = _handleToPtr(h);
frag_t* f = nullptr;
bool frag_changed_fl = false;
time::spec_t t0;
time::setZero(t0);
unsigned elapsedMs = time::elapsedMs(t0,ts);
double mins = elapsedMs / 60000.0;
// if this is the first call to 'track_timestamp()'.
if( p->last_ts_frag == nullptr )
f = _timestamp_to_frag(p,ts);
@ -799,8 +855,12 @@ cw::rc_t cw::preset_sel::write( handle_t h, const char* fn )
}
}
newPairObject("fragL", fragL_obj, root);
newPairObject("fragN", fragN, root);
newPairObject("fragL", fragL_obj, root);
newPairObject("fragN", fragN, root);
newPairObject("masterWetInGain", p->master_wet_in_gain, root );
newPairObject("masterWetOutGain", p->master_wet_out_gain, root );
newPairObject("masterDryGain", p->master_dry_gain, root );
newPairObject("masterSyncDelayMs", p->master_sync_delay_ms,root );
unsigned bytes_per_frag = 1024;
@ -848,8 +908,12 @@ cw::rc_t cw::preset_sel::read( handle_t h, const char* fn )
_destroy_all_frags(p);
// parse the root level
if((rc = root->getv( "fragN", fragN,
"fragL", fragL_obj )) != kOkRC )
if((rc = root->getv( "fragN", fragN,
"fragL", fragL_obj,
"masterWetInGain", p->master_wet_in_gain,
"masterWetOutGain", p->master_wet_out_gain,
"masterDryGain", p->master_dry_gain,
"masterSyncDelayMs",p->master_sync_delay_ms)) != kOkRC )
{
rc = cwLogError(rc,"Root preset select parse failed on '%s'.", cwStringNullGuard(fn));
goto errLabel;
@ -965,7 +1029,7 @@ cw::rc_t cw::preset_sel::report( handle_t h )
unsigned elapsedMs = time::elapsedMs(t0,f->endTimestamp);
double mins = elapsedMs / 60000.0;
cwLogInfo("%3i id:%3i end loc:%3i end min:%7.2f",i,f->fragId,f->endLoc, mins);
cwLogInfo("%3i id:%3i end loc:%3i end min:%f",i,f->fragId,f->endLoc, mins);
}
return rc;

View File

@ -57,6 +57,13 @@ namespace cw
kPresetSelectVarId, // select a preset to play
kPlayEnableVarId, // include in the segment to play
kDryFlVarId, // play this fragment dry
kBaseMasterVarId, // All 'master' variables have id's greater than kBaseMasterVarId
kMasterWetInGainVarId,
kMasterWetOutGainVarId,
kMasterDryGainVarId,
kMasterSyncDelayMsVarId
};
rc_t create( handle_t& hRef, const object_t* cfg );
@ -77,8 +84,11 @@ namespace cw
rc_t delete_fragment( handle_t h, unsigned fragId );
bool is_fragment_loc( handle_t h, unsigned loc );
// Return the fragment id of the 'selected' fragment.
unsigned ui_select_fragment_id( handle_t h );
// Set the 'select_flag' on this fragment and remove it from all others.
void ui_select_fragment( handle_t h, unsigned fragId, bool selectFl );
@ -97,6 +107,7 @@ namespace cw
// If 'ts' is past the last defined fragment then the last fragment is returned.
// If no fragments are defined 'frag_Ref' is set to nullptr.
// The return value is true when the value of frag_Ref changes from the previous call.
void track_timestamp_reset( handle_t h );
bool track_timestamp( handle_t h, const time::spec_t& ts, const cw::preset_sel::frag_t*& frag_Ref );
// Return the preset index marked to play on this fragment.

View File

@ -78,6 +78,19 @@ unsigned cw::time::elapsedMs( const spec_t& t0 )
return elapsedMs(t0,t1);
}
double cw::time::elapsedSecs( const spec_t& t0, const spec_t& t1 )
{
return elapsedMicros(t0,t1) / 1000000.0;
}
double cw::time::elapsedSecs( const spec_t& t0 )
{
spec_t t1;
get(t1);
return elapsedSecs(t0,t1);
}
unsigned cw::time::absElapsedMicros( const spec_t& t0, const spec_t& t1 )
{
if( isLTE(t0,t1) )

View File

@ -29,7 +29,11 @@ namespace cw
// Wrapper on elapsedMicros()
unsigned elapsedMs( const spec_t& t0, const spec_t& t1 );
unsigned elapsedMs( const spec_t& t0 );
// Wrapper on elapsedMicros()
double elapsedSecs( const spec_t& t0, const spec_t& t1 );
double elapsedSecs( const spec_t& t0 );
// Same as elapsedMicros() but the times are not assumed to be ordered.
// The function therefore begins by swapping t1 and t0 if t0 is after t1.
unsigned absElapsedMicros( const spec_t& t0, const spec_t& t1 );

102
cwUi.cpp
View File

@ -86,6 +86,9 @@ namespace cw
appIdMapRecd_t* appIdMap; // map of application parent/child/js id's
char* buf; // buf[bufN] output message formatting buffer
unsigned bufN; //
char* recvBuf;
unsigned recvBufN;
unsigned recvBufIdx;
unsigned* sessA; // sessA[ sessN ] array of wsSessId's
unsigned sessN;
@ -153,6 +156,7 @@ namespace cw
mem::release(p->sessA);
mem::release(p->eleA);
mem::release(p->buf);
mem::release(p->recvBuf);
mem::release(p);
@ -963,7 +967,7 @@ namespace cw
if( p->sendCbFunc != nullptr )
{
const char* mFmt = "{ \"op\":\"%s\", \"uuId\":%i, \"value\":%s }";
const int mbufN = 512;
const int mbufN = 1024;
char vbuf[vbufN];
char mbuf[mbufN];
@ -1082,6 +1086,9 @@ cw::rc_t cw::ui::create(
p->sendCbArg = sendCbArg;
p->buf = mem::allocZ<char>(fmtBufByteN);
p->bufN = fmtBufByteN;
p->recvBuf = mem::allocZ<char>(fmtBufByteN);
p->recvBufN = fmtBufByteN;
p->recvBufIdx = 0;
// create the root element
if((ele = _createBaseEle(p, nullptr, kRootAppId, kInvalidId, "uiDivId" )) == nullptr || ele->uuId != kRootUuId )
@ -1182,14 +1189,55 @@ cw::rc_t cw::ui::onDisconnect( handle_t h, unsigned wsSessId )
return kOkRC;
}
cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* msg, unsigned msgByteN )
cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* void_msg, unsigned msgByteN )
{
rc_t rc = kOkRC;
ui_t* p = _handleToPtr(h);
opId_t opId = _labelToOpId((const char*)msg);
opId_t opId = kInvalidOpId;
value_t value;
ele_t* ele;
const char* src_msg = (const char*)void_msg;
const char* msg = src_msg;
// if the incoming message is valid
if( msgByteN > 0 and src_msg != nullptr )
{
// if there is a partial msg in the recv buffer (recvBufIdx!=0)
// or the incoming message is a partial mesg - then buffer the message
// (Note: incoming messages that are not zero terminated are partial.")
if( p->recvBufIdx != 0 || src_msg[msgByteN-1] != 0 )
{
// verify the buffer is large enough to hold the msg
if(p->recvBufIdx + msgByteN > p->recvBufN )
{
rc = cwLogError(kOpFailRC,"The UI input buffer (%i) is too small.", p->recvBufN);
p->recvBufIdx = 0;
}
else
{
// update it with the incoming text
strncpy( p->recvBuf + p->recvBufIdx, src_msg, msgByteN );
p->recvBufIdx += msgByteN;
msg = p->recvBuf;
}
}
// if the incoming message is not zero terminated then it was a partial
// message it was buffered and there is nothing else to do.
if( src_msg[msgByteN-1] != 0)
return rc;
}
// the message is being processed so the buffer will end up empty
// (if it was being used)
p->recvBufIdx = 0;
// parse the 'opId' from the message
opId = _labelToOpId(msg);
switch( opId )
{
case kInitOpId:
@ -1517,12 +1565,13 @@ cw::rc_t cw::ui::setLogLine( handle_t h, unsigned uuId, const char* text )
rc = sendValueString(h,uuId,text);
else
{
int sn = textLength(text);
unsigned sn = textLength(text);
sn += n + 1;
char s[ sn ];
// alloc. a lot of extra space to cover the space need for the '\' escape character
char s[ sn*2 ];
unsigned i,j;
for( i=0,j=0; text[i]; ++i,++j)
for( i=0,j=0; text[i] && j<sn; ++i,++j)
{
char ch = text[i];
bool escape_fl = true;
@ -1542,12 +1591,15 @@ cw::rc_t cw::ui::setLogLine( handle_t h, unsigned uuId, const char* text )
if( escape_fl )
s[j++] = '\\';
s[j] = ch;
if( j < sn )
s[j] = ch;
}
s[sn-1] = 0;
//printf("%s %s\n",text,s);
rc = sendValueString(h,uuId,s);
@ -1781,11 +1833,12 @@ void cw::ui::report( handle_t h )
if(p->eleA[i] != nullptr )
{
const ele_t* e = p->eleA[i];
unsigned parUuId = e->phys_parent==NULL ? kInvalidId : e->phys_parent->uuId;
const char* parEleName = e->phys_parent==NULL || e->phys_parent->eleName == NULL ? "" : e->phys_parent->eleName;
printf("uu:%5i app:%5i %20s : parent uu:%5i app:%5i %20s ", e->uuId, e->appId, e->eleName == NULL ? "" : e->eleName, parUuId, e->logical_parent->appId, parEleName );
unsigned parUuId = e->phys_parent==NULL ? kInvalidId : e->phys_parent->uuId;
const char* parEleName = e->phys_parent==NULL || e->phys_parent->eleName == NULL ? "" : e->phys_parent->eleName;
unsigned logParentAppId = e->logical_parent==NULL ? kInvalidId : e->logical_parent->appId;
printf("uu:%5i app:%5i chan:%5i %20s : parent uu:%5i app:%5i %20s ", e->uuId, e->appId, e->chanId, e->eleName == NULL ? "" : e->eleName, parUuId, logParentAppId, parEleName );
for(unsigned i=0; i<e->attr->child_count(); ++i)
{
@ -1869,9 +1922,10 @@ namespace cw
cw::rc_t cw::ui::ws::parseArgs( const object_t& o, args_t& args, const char* object_label )
{
rc_t rc = kOkRC;
const object_t* op = &o;
char* uiCfgFn = nullptr;
rc_t rc = kOkRC;
const object_t* op = &o;
char* uiCfgFn = nullptr;
char* physRootDir = nullptr;
memset(&args,0,sizeof(args));
@ -1889,17 +1943,27 @@ cw::rc_t cw::ui::ws::parseArgs( const object_t& o, args_t& args, const char* ob
rc = cwLogError(rc,"'ui' cfg. parse failed.");
}
// expand the physical root directory
if((physRootDir = filesys::expandPath( args.physRootDir)) == nullptr )
{
rc = cwLogError(kInvalidArgRC,"The physical root directory of the UI cfg. is invalid.");
goto errLabel;
}
// if a default UI resource script was given then convert it into an object
if( uiCfgFn != nullptr )
{
char* fn = filesys::makeFn( args.physRootDir, uiCfgFn, nullptr, nullptr );
char* fn = filesys::makeFn( physRootDir, uiCfgFn, nullptr, nullptr );
if((rc = objectFromFile(fn,args.uiRsrc)) != kOkRC )
rc = cwLogError(rc,"An error occurred while parsing the UI resource script in '%s'.", cwStringNullGuard(uiCfgFn));
mem::release(fn);
}
errLabel:
mem::release(physRootDir);
return rc;
}
@ -2024,7 +2088,7 @@ cw::rc_t cw::ui::ws::exec( handle_t h )
cwLogError(rc,"The UI websock execution failed.");
// make the idle callback
ui::onReceive( p->uiH, kInvalidId, "idle", strlen("idle") );
ui::onReceive( p->uiH, kInvalidId, "idle", strlen("idle")+1 );
return rc;
}

7
cwUi.h
View File

@ -41,7 +41,12 @@ namespace cw
// A UI was disconnected
rc_t onDisconnect( handle_t h, unsigned wsSessId );
// Receive a msg from a remote UI
// Receive a msg from a remote UI.
//
// Note that individual messages are delinated with zero termination.
// Therefore a message which is not zero terminated is considered
// a partial message and will be buffered until the suffix of the message
// arrives.
rc_t onReceive( handle_t h, unsigned wsSessId, const void* msg, unsigned byteN );
// Locate an element whose parent uuid is 'parentUuId' with a child named 'eleName'.

View File

@ -54,7 +54,7 @@ namespace cw
void copy( T0* v0, const T1* v1, unsigned n )
{
for(unsigned i=0; i<n; ++i)
v0[i] = v1[i];
v0[i] = (T0)v1[i];
}
template< typename T0, typename T1 >
@ -151,7 +151,7 @@ namespace cw
void mul( T0* v0, const T1* v1, unsigned n )
{
for(unsigned i=0; i<n; ++i)
v0[i] *= v1[i];
v0[i] = v0[i] * (T1)v1[i];
}
template< typename T0, typename T1 >
@ -169,8 +169,8 @@ namespace cw
v0[i] *= scalar;
}
template< typename T0, typename T1 >
void mul( T0* y0, const T0* v0, const T1& scalar, unsigned n )
template< typename T0, typename T1, typename T2 >
void mul( T0* y0, const T1* v0, const T2& scalar, unsigned n )
{
for(unsigned i=0; i<n; ++i)
y0[i] = v0[i] * scalar;
@ -363,15 +363,15 @@ namespace cw
return init_idx;
}
template< typename T >
T* ampl_to_db( T* dbp, const T* sbp, unsigned dn, T minDb=-1000 )
template< typename T0, typename T1 >
T0* ampl_to_db( T0* dbp, const T1* sbp, unsigned dn, T0 minDb=-1000 )
{
T minVal = pow(10.0,minDb/20.0);
T* dp = dbp;
T* ep = dp + dn;
T0 minVal = pow(10.0,minDb/20.0);
T0* dp = dbp;
T0* ep = dp + dn;
for(; dp<ep; ++dp,++sbp)
*dp = *sbp<minVal ? minDb : 20.0 * log10(*sbp);
*dp = (T0)(*sbp<minVal ? minDb : 20.0 * log10(*sbp));
return dbp;
}

View File

@ -1226,7 +1226,7 @@ function ws_on_msg( jsonMsg )
function ws_on_open()
{
set_app_title( "Connected", "title_connected" );
_ws.send("init")
ws_send("init")
}
function ws_on_close()

View File

@ -10,11 +10,24 @@
row: {
button:{ name: quitBtnId, title:"Quit" },
button:{ name: ioReportBtnId, title:"IO Report" },
button:{ name: netPrintBtnId, title:"Print Network" }
button:{ name: reportBtnId, title:"App Report" },
button:{ name: loadBtnId, title:"Load" },
button:{ name: saveBtnId, title:"Save" },
},
row: {
check: { name: printMidiCheckId, title: "Print MIDI" },
check: { name: pianoMidiCheckId, title: "Piano MIDI" },
check: { name: samplerMidiCheckId, title: "Sampler MIDI" },
number: { name: syncDelayMsId, title: "Delay (ms)", min:0, max:1000, step:1, decpl:0 },
},
row: {
number: { name: wetInGainId, title:"Wet In Gain", min:0, max:100.0, step:0.01, decpl:3 },
number: { name: wetOutGainId, title:"Wet Out Gain", min:0, max:100.0, step:0.01, decpl:3 },
number: { name: dryGainId, title:"Dry Gain", min:0, max:100.0, step:0.01, decpl:3 },
},
row: {
button:{ name: startBtnId, title:"Start" },
@ -25,8 +38,8 @@
row: {
check:{ name: midiThruCheckId, title:"MIDI Thru" },
numb_disp: { name: curMidiEvtCntId, title:"Current:" },
numb_disp: { name: totalMidiEvtCntId, title:"Total:" },
numb_disp: { name: curMidiEvtCntId, title:"Current Loc:" },
numb_disp: { name: totalMidiEvtCntId, title:"Max Loc:" },
},
@ -36,6 +49,15 @@
button:{ name: deleteBtnId, title:"Delete", enable: false },
},
row: {
number:{ name: halfPedalDelayMsId, title:"DelayMs:", min:0, max:5000, step:1, decpl:0 },
number:{ name: halfPedalPedalVelId, title:"PVel:", min:0, max:127, step:1, decpl:0 },
number:{ name: halfPedalPitchId, title:"Pitch:", min:0, max:127, step:1, decpl:0 },
number:{ name: halfPedalVelId, title:"Vel:", min:0, max:127, step:1, decpl:0 },
number:{ name: halfPedalDurMsId, title:"DurMs:", min:0, max:5000, step:1, decpl:0 },
number:{ name: halfPedalDnDelayMsId, title:"DownMs:", min:0, max:5000, step:1, decpl:0 },
},
row: {
str_disp:{ name: statusId, title:"Status:", value: "" },
}