//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.h" #include "cwMem.h" #include "cwText.h" #include "cwObject.h" #include "cwAudioFile.h" #include "cwVectOps.h" #include "cwMtx.h" #include "cwDspTypes.h" // real_t, sample_t #include "cwDspTransforms.h" #include "cwTime.h" #include "cwMidiDecls.h" #include "cwFlowDecl.h" #include "cwFlow.h" #include "cwFlowTypes.h" #include "cwFlowProc.h" #include "cwFlowCross.h" namespace cw { namespace flow_cross { enum { kInactiveStateId, kActiveStateId, kFadeInStateId, kFadeOutStateId, }; // Each duplicated network is represented by a flow_netword_t record in flow_cross_t.netA[]. typedef struct flow_network_str { dsp::recorder::obj_t* recorder; flow::external_device_t* deviceA; // deviceA[ deviceN ] - cloned exteranl device array unsigned deviceN; flow::handle_t flowH; unsigned stateId; // inactive, fade-in, fade-out double fadeGain; // 0 0->1 1->0 unsigned fadeSmpN; // unsigned net_idx; } flow_network_t; typedef struct flow_cross_str { unsigned cur_idx; // index of the network currently receiving parameter updates double srate; unsigned netN; flow_network_t* netA; flow::external_device_t* deviceA; unsigned deviceN; bool fadeInputFl; } flow_cross_t; flow_cross_t* _handleToPtr(handle_t h) { return handleToPtr(h); } void _destroy_network( flow_network_t* net ) { for(unsigned i=0; ideviceN; ++i) if( net->deviceA[i].typeId == flow::kAudioDevTypeId ) { abuf_destroy(net->deviceA[i].u.a.abuf); } mem::release(net->deviceA); flow::destroy(net->flowH); char fname[256]; snprintf(fname,256,"/home/kevin/temp/temp_%i.json",net->net_idx); dsp::recorder::write(net->recorder,fname); dsp::recorder::destroy( net->recorder ); } rc_t _destroy( flow_cross_t* p ) { rc_t rc = kOkRC; for(unsigned i=0; inetN; ++i) _destroy_network( p->netA + i ); mem::release(p->netA); mem::release(p); return rc; } flow::abuf_t* _clone_abuf( const flow::abuf_t* src ) { flow::abuf_t* b = mem::allocZ(); memcpy(b,src,sizeof(flow::abuf_t)); b->buf = mem::allocZ( b->frameN * b->chN ); return b; } flow::external_device_t* _clone_external_device_array( const flow::external_device_t* srcDevA, unsigned devN ) { flow::external_device_t* devA = mem::allocZ(devN); memcpy(devA,srcDevA,devN * sizeof(flow::external_device_t)); for(unsigned i=0; inetA + net_idx; dsp::recorder::create( net->recorder, srate, 10.0, 1 ); net->deviceA = _clone_external_device_array( deviceA, deviceN ); net->deviceN = 0; net->stateId = net_idx == 0 ? kActiveStateId : kInactiveStateId; net->net_idx = net_idx; if((rc = flow::create( net->flowH, &classCfg, &networkCfg, nullptr, nullptr )) == kOkRC ) { rc = cwLogError(rc,"Flow cross index %i network create failed.",net_idx); } if((rc = flow::initialize( net->flowH, net->deviceA, deviceN )) == kOkRC ) net->deviceN = deviceN; else { cwLogError(rc,"Flow cross index %i network initialize failed.",net_idx); goto errLabel; } errLabel: if(rc != kOkRC ) _destroy_network(net); return rc; } void _fade_audio( const flow::abuf_t* src, flow::abuf_t* dst, flow_network_t* net ) { unsigned frameN = std::min(dst->frameN,src->frameN); unsigned chN = std::min(dst->chN,src->chN); double dd = (double)frameN / net->fadeSmpN; // change in gain over frameN samples double bf = net->fadeGain; // starting gain double ef = 0; switch( net->stateId ) { case kFadeInStateId: ef = bf + dd; break; case kFadeOutStateId: ef = bf - dd; break; case kActiveStateId: ef = 1; break; case kInactiveStateId: ef = 0; break; default: assert(0); } // pin the ending gain in the range 0.0 to 1.0 ef = std::min(1.0,std::max(0.0,ef)); dd = ef - bf; // gain change flow::sample_t BUF[ chN * frameN ]; //memcpy(dst->buf,src->buf,frameN*chN*sizeof(flow::sample_t)); // apply gain slope to the src signal and sum the result into the output buffer for(unsigned j = 0; jbuf + (j*frameN); flow::sample_t* sbuf = src->buf + (j*frameN); flow::sample_t* rbuf = BUF + (j*frameN); for(unsigned i = 0; irecorder, BUF, chN, frameN ); net->fadeGain += dd; if( net->stateId == kFadeInStateId && ef == 1.0 ) net->stateId = kActiveStateId; if( net->stateId == kFadeOutStateId && ef == 0.0 ) net->stateId = kInactiveStateId; } // Copy audio from the actual external audio device to a cloned audio device void _update_midi_input( flow_cross_t* p, flow_network_t* net, unsigned devIdx ) { flow::midi_dev_cfg_t& src = p->deviceA[devIdx].u.m; // src MIDI device flow::midi_dev_cfg_t& dst = net->deviceA[devIdx].u.m; // dst MIDI device clone // redirect the MIDI msg list array to the clones dst.msgArray = src.msgArray; dst.msgCnt = src.msgCnt; } // Copy audio from the actual external audio device to a cloned audio device void _update_audio_input( flow_cross_t* p, flow_network_t* net, unsigned devIdx ) { flow::abuf_t* src = p->deviceA[devIdx].u.a.abuf; flow::abuf_t* dst = net->deviceA[devIdx].u.a.abuf; memcpy( dst->buf, src->buf, dst->chN * dst->frameN * sizeof(flow::sample_t)); //_fade_audio( src, dst, net ); } void _zero_audio_output( flow_cross_t* p, flow_network_t* net, unsigned devIdx ) { flow::abuf_t* dst = net->deviceA[devIdx].u.a.abuf; memset(dst->buf,0,dst->chN*dst->frameN*sizeof(flow::sample_t)); } void _update_audio_output( flow_cross_t* p, flow_network_t* net, unsigned devIdx ) { flow::abuf_t* src = net->deviceA[devIdx].u.a.abuf; flow::abuf_t* dst = p->deviceA[devIdx].u.a.abuf; assert( net->deviceA[ devIdx ].flags == flow::kOutFl && net->deviceA[ devIdx ].typeId==flow::kAudioDevTypeId); _fade_audio( src, dst, net ); } unsigned _get_flow_index( flow_cross_t* p, destId_t destId ) { unsigned flow_idx = kInvalidIdx; switch( destId ) { case kCurDestId: flow_idx = p->cur_idx; break; case kNextDestId: flow_idx = (p->cur_idx + 1) % p->netN; break; default: assert(0); } return flow_idx; } 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; flow_cross_t* p = _handleToPtr(h); 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; } template< typename T > rc_t _get_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, T& valueRef ) { rc_t rc = kOkRC; flow_cross_t* p = _handleToPtr(h); 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 = get_variable_value( p->netA[ flow_idx ].flowH, inst_label, var_label, chIdx, valueRef )) != kOkRC ) { cwLogError(rc,"Get variable value failed on cross-network index: %i.",flow_idx); goto errLabel; } errLabel: return rc; } } } cw::rc_t cw::flow_cross::create( handle_t& hRef, const object_t& classCfg, const object_t& networkCfg, double srate, unsigned crossN, flow::external_device_t* deviceA, unsigned deviceN) { rc_t rc; if((rc = destroy(hRef)) != kOkRC ) return rc; // There must be at least 2 networks to cross fade between if( crossN < 2 ) crossN = 2; flow_cross_t* p = mem::allocZ(); p->netA = mem::allocZ( crossN ); p->netN = 0; p->srate = srate; p->deviceA = deviceA; p->deviceN = deviceN; for(unsigned i=0; inetN += 1; else { rc = cwLogError(rc,"Sub-network index '%i' create failed.",i); goto errLabel; } hRef.set(p); errLabel: if( rc != kOkRC ) _destroy(p); return rc; } cw::rc_t cw::flow_cross::destroy( handle_t& hRef ) { rc_t rc = kOkRC; if( !hRef.isValid() ) return rc; flow_cross_t* p = _handleToPtr(hRef); if((rc = _destroy(p)) != kOkRC ) return rc; hRef.clear(); return rc; } unsigned cw::flow_cross::preset_cfg_flags( handle_t h ) { flow_cross_t* p = _handleToPtr(h); if( p->netN > 0 ) return preset_cfg_flags( p->netA[0].flowH ); return 0; } 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; for(unsigned j=0; jdeviceN; ++j) if( cwIsFlag(p->deviceA[j].flags, flow::kInFl ) ) { switch( p->deviceA[j].typeId) { case flow::kAudioDevTypeId: // 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 // copy audio from the actual audio device to the cloned audio devices _update_audio_input( p, p->netA + i, j ); break; case flow::kMidiDevTypeId: // update the cloned MIDI devices from the master device _update_midi_input( p, p->netA + i, j ); } } // 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 ); // update the network flow::exec_cycle( net->flowH ); // 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; } /* cw::rc_t cw::flow_cross::exec_cycle( handle_t h ) { rc_t rc = kOkRC; flow_cross_t* p = _handleToPtr(h); flow_network_t* net = p->netA; 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 ); return rc; } */ cw::rc_t cw::flow_cross::apply_preset( handle_t h, destId_t destId, const char* presetLabel ) { rc_t rc = kOkRC; flow_cross_t* p = _handleToPtr(h); unsigned flow_idx = _get_flow_index(p, destId ); if((rc = flow::apply_preset( p->netA[flow_idx].flowH, presetLabel )) != kOkRC ) rc = cwLogError(rc,"Preset application '%s' failed.",cwStringNullGuard(presetLabel)); return rc; } cw::rc_t cw::flow_cross::apply_preset( handle_t h, destId_t destId, const flow::multi_preset_selector_t& multi_preset_sel ) { rc_t rc = kOkRC; flow_cross_t* p = _handleToPtr(h); unsigned flow_idx = _get_flow_index(p, destId ); if((rc = flow::apply_preset( p->netA[flow_idx].flowH, multi_preset_sel )) != kOkRC ) rc = cwLogError(rc,"Muti-preset application failed."); return rc; } cw::rc_t cw::flow_cross::set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, bool value ) { return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); } cw::rc_t cw::flow_cross::set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, int value ) { return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); } cw::rc_t cw::flow_cross::set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, unsigned value ) { return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); } cw::rc_t cw::flow_cross::set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, float value ) { return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); } cw::rc_t cw::flow_cross::set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, double value ) { return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); } cw::rc_t cw::flow_cross::get_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, bool& valueRef ) { return _get_variable_value(h,destId,inst_label,var_label,chIdx,valueRef); } cw::rc_t cw::flow_cross::get_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, int& valueRef ) { return _get_variable_value(h,destId,inst_label,var_label,chIdx,valueRef); } cw::rc_t cw::flow_cross::get_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, unsigned& valueRef ) { return _get_variable_value(h,destId,inst_label,var_label,chIdx,valueRef); } cw::rc_t cw::flow_cross::get_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, float& valueRef ) { return _get_variable_value(h,destId,inst_label,var_label,chIdx,valueRef); } cw::rc_t cw::flow_cross::get_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, double& valueRef ) { return _get_variable_value(h,destId,inst_label,var_label,chIdx,valueRef); } cw::rc_t cw::flow_cross::begin_cross_fade( handle_t h, unsigned crossFadeMs ) { rc_t rc = kOkRC; flow_cross_t* p = _handleToPtr(h); p->netA[ p->cur_idx ].stateId = kFadeOutStateId; p->cur_idx = _get_flow_index( p, kNextDestId ); p->netA[ p->cur_idx ].stateId = kFadeInStateId; p->netA[ p->cur_idx ].fadeSmpN = (unsigned)(p->srate * crossFadeMs / 1000.0); return rc; } 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"); for(unsigned i=0; ideviceN; ++i) flow::print_external_device( p->deviceA + i ); for(unsigned i=0; inetN; ++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 ); } void cw::flow_cross::report( 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 ); for(unsigned i=0; inetN; ++i) { const char* label = i==cur_flow_idx ? "Current" : (i==next_flow_idx ? "Next" : ""); printf("%s %f : ",label,p->netA[i].fadeGain ); } printf("\n"); }