diff --git a/README.md b/README.md index 2b1ddf2..b16ebd8 100644 --- a/README.md +++ b/README.md @@ -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? diff --git a/cwAudioDeviceAlsa.cpp b/cwAudioDeviceAlsa.cpp index a528e3b..f369fc8 100644 --- a/cwAudioDeviceAlsa.cpp +++ b/cwAudioDeviceAlsa.cpp @@ -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; } diff --git a/cwAudioTransforms.h b/cwAudioTransforms.h index f753818..e99b2b3 100644 --- a/cwAudioTransforms.h +++ b/cwAudioTransforms.h @@ -785,16 +785,16 @@ namespace cw } template< typename T0, typename T1 > - void _cmSpecDist2Bump( struct obj_str* p, T0* x, unsigned binCnt, T1 thresh, T1 expo) + void _cmSpecDist2Bump( struct obj_str* 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 - void _cmSpecDist2BasicMode( struct obj_str* p, T0* X1m, unsigned binCnt, T1 thresh, T1 upr, T1 lwr ) + void _cmSpecDist2BasicMode( struct obj_str* p, double* X1m, unsigned binCnt, double thresh, double upr, double lwr ) { unsigned i=0; @@ -823,8 +823,8 @@ namespace cw for(i=0; i -150.0 ) { @@ -890,7 +890,6 @@ namespace cw return rc; } - } diff --git a/cwFileSys.cpp b/cwFileSys.cpp index 5d5b8e8..7946109 100644 --- a/cwFileSys.cpp +++ b/cwFileSys.cpp @@ -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) diff --git a/cwFlow.cpp b/cwFlow.cpp index 46414ee..fbb19e8 100644 --- a/cwFlow.cpp +++ b/cwFlow.cpp @@ -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); } } diff --git a/cwFlowCross.cpp b/cwFlowCross.cpp index 0385883..7332ac5 100644 --- a/cwFlowCross.cpp +++ b/cwFlowCross.cpp @@ -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; inetN; ++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; jdeviceN; ++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; jdeviceN; ++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; jdeviceN; ++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; jdeviceN; ++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; jdeviceN; ++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; jdeviceN; ++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; inetN; ++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 ); + +} + diff --git a/cwFlowCross.h b/cwFlowCross.h index 5b5e949..616d421 100644 --- a/cwFlowCross.h +++ b/cwFlowCross.h @@ -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 ); } } diff --git a/cwFlowProc.cpp b/cwFlowProc.cpp index 58ff1a9..11257e0 100644 --- a/cwFlowProc.cpp +++ b/cwFlowProc.cpp @@ -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(); 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; iext_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(); // 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; jframeN; ++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(abuf->chN); - + + if((rc = var_channel_count(ctx,"select",selChN)) != kOkRC ) + goto errLabel; + // register the gain for(unsigned i=0; ichN; ++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; jframeN; ++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 } } diff --git a/cwFlowTypes.cpp b/cwFlowTypes.cpp index 2d34137..824b72b 100644 --- a/cwFlowTypes.cpp +++ b/cwFlowTypes.cpp @@ -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( variable_t* var, bool val ) + void _var_setter( 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( variable_t* var, unsigned val ) + void _var_setter( 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( variable_t* var, int val ) + void _var_setter( 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( variable_t* var, float val ) + void _var_setter( 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( variable_t* var, double val ) + void _var_setter( 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( variable_t* var, const char* val ) + void _var_setter( 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( variable_t* var, abuf_t* val ) + void _var_setter( 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( variable_t* var, fbuf_t* val ) + void _var_setter( 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; ivalue == 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; ilocal_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 ) { diff --git a/cwFlowTypes.h b/cwFlowTypes.h index 2834922..3c98112 100644 --- a/cwFlowTypes.h +++ b/cwFlowTypes.h @@ -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 ); diff --git a/cwIo.cpp b/cwIo.cpp index 54c0006..3e00610 100644 --- a/cwIo.cpp +++ b/cwIo.cpp @@ -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); +} diff --git a/cwIo.h b/cwIo.h index 2980dc9..40e1067 100644 --- a/cwIo.h +++ b/cwIo.h @@ -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 ); } } diff --git a/cwIoFlow.cpp b/cwIoFlow.cpp index d918e56..1ddc590 100644 --- a/cwIoFlow.cpp +++ b/cwIoFlow.cpp @@ -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 ); } diff --git a/cwIoFlow.h b/cwIoFlow.h index 4a6eb9f..4a7fd8d 100644 --- a/cwIoFlow.h +++ b/cwIoFlow.h @@ -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 ); } } diff --git a/cwIoMidiRecordPlay.cpp b/cwIoMidiRecordPlay.cpp index e42436a..387962f 100644 --- a/cwIoMidiRecordPlay.cpp +++ b/cwIoMidiRecordPlay.cpp @@ -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; imidiDevN; ++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(p->midiDevN); + + printf("Midi record play devices:%i\n",p->midiDevN); + + for(unsigned i=0; imidiDevN; ++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(p->midiDevA[i].velTableN); + + + for(unsigned j=0; jmidiDevA[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( p->msgArrayN ); - p->midiOutDevLabel = mem::duplStr( p->midiOutDevLabel); - p->midiOutPortLabel = mem::duplStr( p->midiOutPortLabel); - + p->iMsgArray = mem::allocZ( 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; imidiDevN; ++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; iiMsgArrayInIdx; ++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; imsgArrayInIdx; ++i) + // set the 'microsec' value for each MIDI msg as an offset from the first message[] + for(unsigned i=0; iiMsgArrayInIdx; ++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; imidiDevN; ++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 ) { diff --git a/cwIoMidiRecordPlay.h b/cwIoMidiRecordPlay.h index 40cb777..2bdabc1 100644 --- a/cwIoMidiRecordPlay.h +++ b/cwIoMidiRecordPlay.h @@ -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 ); diff --git a/cwIoPresetSelApp.cpp b/cwIoPresetSelApp.cpp index 417343b..73e853d 100644 --- a/cwIoPresetSelApp.cpp +++ b/cwIoPresetSelApp.cpp @@ -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 ? "" : preset_label); + cwLogInfo("Apply preset: '%s'.", preset_idx==kInvalidIdx ? "" : 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(; ilocMapN; ++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)) { diff --git a/cwMidi.h b/cwMidi.h index 3df8af4..5f46564 100644 --- a/cwMidi.h +++ b/cwMidi.h @@ -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 ); diff --git a/cwMidiAlsa.cpp b/cwMidiAlsa.cpp index 3645964..18e8ef7 100644 --- a/cwMidiAlsa.cpp +++ b/cwMidiAlsa.cpp @@ -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"); diff --git a/cwPianoScore.cpp b/cwPianoScore.cpp index dc9f111..9493818 100644 --- a/cwPianoScore.cpp +++ b/cwPianoScore.cpp @@ -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 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(); + + 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 ) { diff --git a/cwPianoScore.h b/cwPianoScore.h index 87c54e5..8c02ee3 100644 --- a/cwPianoScore.h +++ b/cwPianoScore.h @@ -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 ); diff --git a/cwPresetSel.cpp b/cwPresetSel.cpp index 9313bb6..cb9c8d2 100644 --- a/cwPresetSel.cpp +++ b/cwPresetSel.cpp @@ -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(); // 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; diff --git a/cwPresetSel.h b/cwPresetSel.h index 23cd207..a633cdc 100644 --- a/cwPresetSel.h +++ b/cwPresetSel.h @@ -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. diff --git a/cwTime.cpp b/cwTime.cpp index 0b4ac4c..c056794 100644 --- a/cwTime.cpp +++ b/cwTime.cpp @@ -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) ) diff --git a/cwTime.h b/cwTime.h index 8b702d3..4e2f63f 100644 --- a/cwTime.h +++ b/cwTime.h @@ -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 ); diff --git a/cwUi.cpp b/cwUi.cpp index db1bb27..f9c06eb 100644 --- a/cwUi.cpp +++ b/cwUi.cpp @@ -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(fmtBufByteN); p->bufN = fmtBufByteN; + p->recvBuf = mem::allocZ(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] && jeleA[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; iattr->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; } diff --git a/cwUi.h b/cwUi.h index 0219c56..f049374 100644 --- a/cwUi.h +++ b/cwUi.h @@ -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'. diff --git a/cwVectOps.h b/cwVectOps.h index 3934792..55aa632 100644 --- a/cwVectOps.h +++ b/cwVectOps.h @@ -54,7 +54,7 @@ namespace cw void copy( T0* v0, const T1* v1, unsigned n ) { for(unsigned i=0; i @@ -151,7 +151,7 @@ namespace cw void mul( T0* v0, const T1* v1, unsigned n ) { for(unsigned i=0; i @@ -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 - 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