#include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.h" #include "cwMem.h" #include "cwText.h" #include "cwObject.h" #include "cwFileSys.h" #include "cwFile.h" #include "cwTime.h" #include "cwVectOps.h" #include "cwMtx.h" #include "cwTime.h" #include "cwMidiDecls.h" #include "cwTime.h" #include "cwMidiDecls.h" #include "cwDspTypes.h" #include "cwFlowDecl.h" #include "cwFlow.h" #include "cwFlowTypes.h" #include "cwIo.h" #include "cwIoFlowCtl.h" namespace cw { namespace io_flow_ctl { // An audio_dev_t record exists for each possible input or output device. typedef struct audio_dev_str { unsigned ioDevIdx; // device index in the io:: API unsigned ioDevId; // device id in the io:: API flow::abuf_t abuf; // src/dst buffer for incoming/outgoing (record/play) samples used by flow proc 'audio_in' and 'audio_out'. } audio_dev_t; typedef struct audio_group_str { double srate; unsigned dspFrameCnt; unsigned ioGroupIdx; audio_dev_t* iDeviceA; unsigned iDeviceN; audio_dev_t* oDeviceA; unsigned oDeviceN; } audio_group_t; typedef struct pgm_str { const char* label; const object_t* cfg; } pgm_t; typedef struct io_flow_ctl_str { const char* base_dir; object_t* proc_class_dict_cfg; object_t* udp_dict_cfg; io::handle_t ioH; flow::external_device_t* deviceA; // Array of generic device descriptions used by the ioFlow controller unsigned deviceN; // (This array must exist for the life of ioFlow controller) audio_group_t* audioGroupA; // Array of real time audio device control records. unsigned audioGroupN; // pgm_t* pgmA; // pgmA[ pgmN ] unsigned pgmN; unsigned pgm_idx; // current program index flow::handle_t flowH; // char* proj_dir; // current project directory bool done_fl; } io_flow_ctl_t; io_flow_ctl_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } void _destroy_device_setup( io_flow_ctl_t* p ) { mem::release(p->deviceA); p->deviceN = 0; for(unsigned gi=0; giaudioGroupN; ++gi) { audio_group_t* ag = p->audioGroupA + gi; for(unsigned di=0; diiDeviceN; ++di) mem::release( ag->iDeviceA[di].abuf.buf ); for(unsigned di=0; dioDeviceN; ++di) mem::release( ag->oDeviceA[di].abuf.buf ); mem::release( ag->iDeviceA); mem::release( ag->oDeviceA); } mem::release(p->audioGroupA); p->audioGroupN = 0; } rc_t _program_unload( io_flow_ctl_t* p ) { rc_t rc; if((rc = destroy(p->flowH)) != kOkRC ) { rc = cwLogError(rc,"Program unload failed."); goto errLabel; } _destroy_device_setup(p); mem::release(p->proj_dir); p->pgm_idx = kInvalidIdx; errLabel: return rc; } rc_t _destroy( io_flow_ctl_t* p ) { rc_t rc = kOkRC; destroy( p->flowH ); if( p->proc_class_dict_cfg != nullptr ) p->proc_class_dict_cfg->free(); if( p->udp_dict_cfg != nullptr ) p->udp_dict_cfg->free(); _program_unload(p); p->pgmN = 0; mem::release(p->pgmA); mem::release(p); return rc; } rc_t _validate_pgm_idx( io_flow_ctl_t* p, unsigned pgm_idx ) { rc_t rc = kOkRC; if( pgm_idx == kInvalidIdx || pgm_idx >= p->pgmN ) { rc = cwLogError(kInvalidArgRC,"The program index '%i' is invalid. Program count=%i.",pgm_idx,p->pgmN); goto errLabel; } errLabel: return rc; } rc_t _parse_cfg( io_flow_ctl_t* p, const object_t* cfg ) { rc_t rc = kOkRC; const char* proc_cfg_fname = nullptr; const char* udp_cfg_fname = nullptr; const char* io_cfg_fname = nullptr; const object_t* pgmL = nullptr; // parse the cfg parameters if((rc = cfg->readv("base_dir", kReqFl, p->base_dir, "proc_dict", kReqFl, proc_cfg_fname, "udp_dict", kReqFl, udp_cfg_fname, "io_dict", kOptFl, io_cfg_fname, "programs", kDictTId, pgmL)) != kOkRC ) { rc = cwLogError(rc,"'caw' system parameter processing failed."); goto errLabel; } // parse the proc dict. file if((rc = objectFromFile(proc_cfg_fname,p->proc_class_dict_cfg)) != kOkRC ) { rc = cwLogError(rc,"The flow proc dictionary could not be read from '%s'.",cwStringNullGuard(proc_cfg_fname)); goto errLabel; } // parse the udp dict file if((rc = objectFromFile(udp_cfg_fname,p->udp_dict_cfg)) != kOkRC ) { rc = cwLogError(rc,"The flow user-defined-proc dictionary could not be read from '%s'.",cwStringNullGuard(udp_cfg_fname)); goto errLabel; } p->pgmN = pgmL->child_count(); p->pgmA = mem::allocZ(p->pgmN); // find the parameters for the requested program for(unsigned i=0; ipgmN; i++) { const object_t* pgm = pgmL->child_ele(i); if( pgm->pair_label()==nullptr || pgm->pair_value()==nullptr || !pgm->pair_value()->is_dict() ) { rc = cwLogError(kSyntaxErrorRC,"The program at index %i has a syntax error.",i); goto errLabel; } p->pgmA[i].label = pgm->pair_label(); p->pgmA[i].cfg = pgm->pair_value(); } errLabel: return rc; } unsigned _calc_device_count(io_flow_ctl_t* p) { unsigned devN = 0; devN += socketCount(p->ioH); devN += serialDeviceCount(p->ioH); unsigned midiDevN = midiDeviceCount(p->ioH); for(unsigned i=0; iioH,i,true) + midiDevicePortCount(p->ioH,i,false); for(unsigned i=0; iaudioGroupN; ++i) devN += p->audioGroupA[i].iDeviceN + p->audioGroupA[i].oDeviceN; return devN; } void _setup_audio_device( io_flow_ctl_t* p,audio_dev_t* dev, unsigned inOrOutFl, unsigned ioDevIdx, unsigned dspFrameCnt ) { dev->ioDevIdx = ioDevIdx; dev->ioDevId = audioDeviceUserId( p->ioH, ioDevIdx ); dev->abuf.srate = audioDeviceSampleRate( p->ioH, ioDevIdx ); dev->abuf.chN = audioDeviceChannelCount( p->ioH, ioDevIdx, inOrOutFl ); dev->abuf.frameN = dspFrameCnt; dev->abuf.buf = mem::allocZ< flow::sample_t >( dev->abuf.chN * dev->abuf.frameN ); //printf("%i %s\n", dev->abuf.chN, audioDeviceLabel( p->ioH, ioDevIdx ) ); } rc_t _setup_audio_groups( io_flow_ctl_t* p, double srate, unsigned dspFrameN ) { rc_t rc = kOkRC; p->audioGroupN = audioGroupCount( p->ioH ); p->audioGroupA = mem::allocZ( p->audioGroupN ); for(unsigned gi=0; giioH); ++gi) { audio_group_t* ag = p->audioGroupA + gi; if((rc = audioGroupReconfigure(p->ioH, gi, srate, dspFrameN )) != kOkRC ) { rc = cwLogError(rc,"Audio group reconfiguration to srate=%f dspFrameN:%i failed.",srate,dspFrameN); goto errLabel; } ag->srate = audioGroupSampleRate( p->ioH, gi ); ag->dspFrameCnt = audioGroupDspFrameCount( p->ioH, gi ); ag->ioGroupIdx = gi; ag->iDeviceN = audioGroupDeviceCount( p->ioH, gi, io::kInFl ); ag->iDeviceA = mem::allocZ< audio_dev_t >( ag->iDeviceN ); for(unsigned gdi=0; gdiiDeviceN; ++gdi) _setup_audio_device( p, ag->iDeviceA + gdi, io::kInFl, audioGroupDeviceIndex( p->ioH, gi, io::kInFl, gdi), ag->dspFrameCnt ); ag->oDeviceN = audioGroupDeviceCount( p->ioH, gi, io::kOutFl ); ag->oDeviceA = mem::allocZ< audio_dev_t >( ag->oDeviceN ); for(unsigned gdi=0; gdioDeviceN; ++gdi) _setup_audio_device( p, ag->oDeviceA + gdi, io::kOutFl, audioGroupDeviceIndex( p->ioH, gi, io::kOutFl, gdi), ag->dspFrameCnt ); } errLabel: return rc; } rc_t _send_midi_triple( flow::external_device_t* dev, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 ) { return midiDeviceSend(((io_flow_ctl_t*)dev->reserved)->ioH, dev->ioDevIdx, dev->ioPortIdx, status |= ch, d0, d1); } void _setup_device_cfg( io_flow_ctl_t* p, flow::external_device_t* d, const char* devLabel, unsigned ioDevIdx, unsigned typeId, unsigned flags, const char* midiPortLabel=nullptr, unsigned midiPortIdx=kInvalidIdx ) { d->reserved = p; d->devLabel = devLabel; d->portLabel = midiPortLabel; d->typeId = typeId; d->flags = flags; d->ioDevIdx = ioDevIdx; d->ioPortIdx = midiPortIdx; } void _setup_midi_device_cfg( io_flow_ctl_t* p, flow::external_device_t* d, const char* devLabel, unsigned ioDevIdx, unsigned flags, unsigned ioMidiPortIdx ) { const char* midiPortLabel = io::midiDevicePortName(p->ioH,ioDevIdx, flags & flow::kInFl ? true : false,ioMidiPortIdx); _setup_device_cfg( p, d, devLabel, ioDevIdx, flow::kMidiDevTypeId, flags, midiPortLabel, ioMidiPortIdx ); d->u.m.maxMsgCnt = io::midiDeviceMaxBufferMsgCount(p->ioH); d->u.m.sendTripleFunc = _send_midi_triple; } void _setup_audio_device_cfg( io_flow_ctl_t* p, flow::external_device_t* d, audio_group_t* ag, audio_dev_t* ad, unsigned flags ) { _setup_device_cfg( p, d, io::audioDeviceLabel(p->ioH,ad->ioDevIdx), ad->ioDevIdx, flow::kAudioDevTypeId, flags ); // Each audio device is given a flow::abuf to hold incoming or outgoing audio. // This buffer also allows the 'audio_in' and 'audio_out' flow procs to configure themselves. d->u.a.abuf = &ad->abuf; } void _setup_generic_device_array( io_flow_ctl_t* p ) { unsigned i = 0; // allocate the generic device control records p->deviceN = _calc_device_count(p); p->deviceA = mem::allocZ( p->deviceN ); // get serial devices for(unsigned di=0; ideviceN && diioH); ++di,++i) _setup_device_cfg( p, p->deviceA + i, io::serialDeviceLabel(p->ioH,di), di, flow::kSerialDevTypeId, flow::kInFl | flow::kOutFl ); // get sockets for(unsigned di=0; ideviceN && diioH); ++di,++i) _setup_device_cfg( p, p->deviceA + i, io::socketLabel(p->ioH,di), di, flow::kSocketDevTypeId, flow::kInFl | flow::kOutFl ); // get midi devices for(unsigned di=0; ideviceN && diioH); ++di) { // input port setup for(unsigned pi=0; piioH,di,true); ++pi,++i) _setup_midi_device_cfg( p, p->deviceA + i, io::midiDeviceName(p->ioH,di), di, flow::kInFl, pi); // output port setup for(unsigned pi=0; piioH,di,false); ++pi,++i) _setup_midi_device_cfg( p, p->deviceA + i, io::midiDeviceName(p->ioH,di), di, flow::kOutFl, pi); } // get the audio devices for(unsigned gi=0; giaudioGroupN; ++gi) { audio_group_t* ag = p->audioGroupA + gi; for(unsigned di=0; ideviceN && diiDeviceN; ++di,++i) _setup_audio_device_cfg( p, p->deviceA + i, ag, ag->iDeviceA + di, flow::kInFl ); for(unsigned di=0; ideviceN && dioDeviceN; ++di,++i) _setup_audio_device_cfg( p, p->deviceA + i, ag, ag->oDeviceA + di, flow::kOutFl ); } assert( i == p->deviceN ); } rc_t _device_index_to_abuf( io_flow_ctl_t* p, unsigned ioGroupIdx, unsigned ioDevIdx, unsigned inOrOutFl, flow::abuf_t*& abuf_ref ) { rc_t rc = kOkRC; for(unsigned gi=0; giaudioGroupN; ++gi) if( p->audioGroupA[gi].ioGroupIdx == ioGroupIdx ) { audio_dev_t* adA = inOrOutFl == flow::kInFl ? p->audioGroupA[gi].iDeviceA : p->audioGroupA[gi].oDeviceA; unsigned adN = inOrOutFl == flow::kInFl ? p->audioGroupA[gi].iDeviceN : p->audioGroupA[gi].oDeviceN; for(unsigned di=0; dibuf + (i*dst_abuf->frameN); memcpy(dst,src,dst_abuf->frameN*sizeof(flow::sample_t)); } } void _zero_output_buffer( flow::abuf_t* dst_abuf ) { memset(dst_abuf->buf,0, dst_abuf->chN*dst_abuf->frameN*sizeof(flow::sample_t)); } void _fill_output_buffer( const flow::abuf_t* src_abuf, flow::sample_t** bufChArray, unsigned bufChArrayN ) { for(unsigned i=0; ichN; ++i) { const flow::sample_t* src = src_abuf->buf + (i*src_abuf->frameN); flow::sample_t* dst = bufChArray[i]; memcpy(dst,src,src_abuf->frameN*sizeof(flow::sample_t)); } } rc_t _audio_callback( io_flow_ctl_t* p, io::audio_msg_t& m ) { rc_t rc = kOkRC; flow::abuf_t* abuf = nullptr; // Get an array of incoming MIDI events which have occurred since the last call to 'io::midiDeviceBuffer()' unsigned midiBufMsgCnt = 0; const midi::ch_msg_t* midiBuf = midiDeviceBuffer(p->ioH,midiBufMsgCnt); if( p->done_fl ) { rc = cwLogError(kInvalidStateRC,"Cannot execute an already completed program."); goto errLabel; } // Give each MIDI input device a pointer to the incoming MIDI msgs for(unsigned i=0; ideviceN; ++i) if( p->deviceA[i].typeId == flow::kMidiDevTypeId && cwIsFlag(p->deviceA[i].flags,flow::kInFl) ) { p->deviceA[i].u.m.msgArray = midiBuf; p->deviceA[i].u.m.msgCnt = midiBufMsgCnt; } // if there is incoming (recorded) audio if( m.iBufChCnt > 0 ) { unsigned chIdx = 0; // for each input device in this group for(io::audio_group_dev_t* agd = m.iDevL; agd!=nullptr; agd=agd->link) { // get the abuf associated with each device in this group if((rc = _device_index_to_abuf( p, m.groupIndex, agd->devIdx, flow::kInFl, abuf )) != kOkRC ) goto errLabel; // fill the input audio buf from the the external audio device _fill_input_buffer( m.iBufArray + chIdx, agd->chCnt, abuf ); chIdx += agd->chCnt; } } // if there are empty output (playback) buffers if( m.oBufChCnt > 0 ) { // for each output device in this group for(io::audio_group_dev_t* agd=m.oDevL; agd!=nullptr; agd=agd->link) { // get the output audio buf associated with this external audio device if((rc = _device_index_to_abuf( p, m.groupIndex, agd->devIdx, flow::kOutFl, abuf )) != kOkRC ) goto errLabel; // zerot the output buffer _zero_output_buffer( abuf ); } } // update the flow network - this will generate audio into the output audio buffers if((rc = flow::exec_cycle(p->flowH)) != kOkRC ) { if( rc == kEofRC ) { p->done_fl = true; rc = kOkRC; } } // if there are empty output (playback) buffers if( m.oBufChCnt > 0 ) { unsigned chIdx = 0; // for each output device in this group for(io::audio_group_dev_t* agd=m.oDevL; agd!=nullptr; agd=agd->link) { // get the output audio buf associated with this external audio device if((rc = _device_index_to_abuf( p, m.groupIndex, agd->devIdx, flow::kOutFl, abuf )) != kOkRC ) goto errLabel; // copy the samples from the flow 'audio_out' buffers to the outgoing buffer passed from the device driver _fill_output_buffer( abuf, m.oBufArray + chIdx, agd->chCnt ); chIdx += agd->chCnt; } } errLabel: // Drop the MIDI messages that were processed on this call. midiDeviceClearBuffer(p->ioH,midiBufMsgCnt); return rc; } } } cw::rc_t cw::io_flow_ctl::create( handle_t& hRef, io::handle_t ioH, const object_t* flow_cfg ) { rc_t rc = kOkRC; if((rc = destroy(hRef)) != kOkRC ) return rc; io_flow_ctl_t* p = mem::allocZ(); p->pgm_idx = kInvalidIdx; p->ioH = ioH; if((rc = _parse_cfg(p,flow_cfg)) != kOkRC ) goto errLabel; hRef.set(p); errLabel: if( rc != kOkRC ) { rc = cwLogError(rc,"io_flow create failed."); _destroy(p); } return rc; } cw::rc_t cw::io_flow_ctl::destroy( handle_t& hRef ) { rc_t rc = kOkRC; io_flow_ctl_t* p = nullptr; if(!hRef.isValid()) return rc; p = _handleToPtr(hRef); if((rc = _destroy(p)) != kOkRC ) goto errLabel; hRef.clear(); errLabel: return rc; } unsigned cw::io_flow_ctl::program_count(handle_t h) { io_flow_ctl_t* p = _handleToPtr(h); return p->pgmN; } const char* cw::io_flow_ctl::program_title( handle_t h, unsigned pgm_idx ) { io_flow_ctl_t* p = _handleToPtr(h); const char* pgm_title = nullptr; if(_validate_pgm_idx(p,pgm_idx) != kOkRC ) goto errLabel; pgm_title = p->pgmA[pgm_idx].label; errLabel: return pgm_title; } unsigned cw::io_flow_ctl::program_index( handle_t h, const char* pgm_title ) { io_flow_ctl_t* p = _handleToPtr(h); for(unsigned i=0; ipgmN; ++i) if( textIsEqual(pgm_title,p->pgmA[i].label) ) return i; return kInvalidIdx; } cw::rc_t cw::io_flow_ctl::program_load( handle_t h, unsigned pgm_idx ) { rc_t rc = kOkRC; io_flow_ctl_t* p = _handleToPtr(h); if((rc = _validate_pgm_idx(p,pgm_idx)) != kOkRC ) goto errLabel; if((rc = _program_unload(p)) != kOkRC ) goto errLabel; // form the program project directory if((p->proj_dir = filesys::makeFn(p->base_dir,nullptr,nullptr,p->pgmA[pgm_idx].label,nullptr)) == nullptr ) { rc = cwLogError(kOpFailRC,"The project directory formation failed."); goto errLabel; } // create the project directory if it doesn't already exist if( !filesys::isDir(p->proj_dir) ) if((rc = filesys::makeDir(p->proj_dir)) != kOkRC ) goto errLabel; // configure the flow network if((rc = create( p->flowH, p->proc_class_dict_cfg, p->pgmA[ pgm_idx ].cfg, p->udp_dict_cfg, p->proj_dir )) != kOkRC ) { rc = cwLogError(rc,"Network configuration failed."); goto errLabel; } // allocate p->audioGroupA[] and create the audio input/output buffers associated with each audio device _setup_audio_groups(p, sample_rate(p->flowH), frames_per_cycle(p->flowH) ); // setup the control record for each external device known to the IO interface _setup_generic_device_array(p); p->pgm_idx = pgm_idx; p->done_fl = false; errLabel: return rc; } unsigned cw::io_flow_ctl::program_current_index( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); return p->pgm_idx; } bool cw::io_flow_ctl::is_program_nrt( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); bool nrt_fl = false; if( !p->flowH.isValid() ) { cwLogWarning("No program is loaded."); goto errLabel; } if(_validate_pgm_idx(p,p->pgm_idx) != kOkRC ) goto errLabel; nrt_fl = is_non_real_time(p->flowH); errLabel: return nrt_fl; } unsigned cw::io_flow_ctl::program_preset_count( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); if(!p->flowH.isValid() || _validate_pgm_idx(p,p->pgm_idx) != kOkRC ) return 0; return preset_count(p->flowH); } const char* cw::io_flow_ctl::program_preset_title( handle_t h, unsigned preset_idx ) { io_flow_ctl_t* p = _handleToPtr(h); if(!p->flowH.isValid() || _validate_pgm_idx(p,p->pgm_idx) != kOkRC ) return nullptr; return preset_label(p->flowH,preset_idx); } cw::rc_t cw::io_flow_ctl::program_initialize( handle_t h, unsigned preset_idx ) { rc_t rc = kOkRC; io_flow_ctl_t* p = _handleToPtr(h); if( p->pgm_idx == kInvalidIdx || p->done_fl || !p->flowH.isValid() ) { cwLogError(kInvalidStateRC,"A valid pre-initialized program is not loaded."); goto errLabel; } // create the flow network if((rc = initialize( p->flowH, p->deviceA, p->deviceN, preset_idx )) != kOkRC ) { rc = cwLogError(rc,"Network create failed."); goto errLabel; } errLabel: return rc; } cw::rc_t cw::io_flow_ctl::exec_nrt( handle_t h ) { rc_t rc = kOkRC; io_flow_ctl_t* p = _handleToPtr(h); if( p->pgm_idx == kInvalidIdx ) { rc = cwLogWarning("No program is loaded."); goto errLabel; } if( p->done_fl ) { rc = cwLogError(kInvalidStateRC,"Cannot execute an already completed program."); goto errLabel; } if((rc = exec( p->flowH )) != kOkRC ) { if(rc == kEofRC ) { p->done_fl = true; rc = kOkRC; } else { rc = cwLogError(rc,"%s execution failed.", cwStringNullGuard(p->pgmA[p->pgm_idx].label)); goto errLabel; } } errLabel: return rc; } cw::rc_t cw::io_flow_ctl::exec( handle_t h, const io::msg_t& msg ) { rc_t rc = kOkRC; io_flow_ctl_t* p = _handleToPtr(h); switch( msg.tid ) { case io::kAudioTId: if( msg.u.audio != nullptr ) rc = _audio_callback(p,*msg.u.audio); break; default: rc = kOkRC; } return rc; } bool cw::io_flow_ctl::is_executable( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); // A program must be loaded, initialized and execution cannot be complete return p->pgm_idx != kInvalidIdx && p->done_fl==false; } bool cw::io_flow_ctl::is_exec_complete( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); return p->done_fl; } void cw::io_flow_ctl::report( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); if( p->flowH.isValid() ) print_network(p->flowH); } void cw::io_flow_ctl::print_network( handle_t h ) { io_flow_ctl_t* p = _handleToPtr(h); if( p->flowH.isValid() ) print_network(p->flowH); }