From e763ed16071b07567db5cbdba0ffed8aaee1fbf5 Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 20 Jan 2021 13:12:35 -0500 Subject: [PATCH] cwIo.h/cpp, cwIoTest.h/cpp, html/ioTest.* : Many changes and additions to support first working Ui and audio subsystem. --- cwIo.cpp | 1220 ++++++++++++++++++++++++++++++++++++---- cwIo.h | 169 ++++-- cwIoTest.cpp | 250 ++++++-- cwIoTest.h | 2 +- html/ioTest/css/ui.css | 75 +++ html/ioTest/index.html | 25 + html/ioTest/js/ui.js | 818 +++++++++++++++++++++++++++ html/ioTest/ui.cfg | 66 +++ 8 files changed, 2422 insertions(+), 203 deletions(-) create mode 100644 html/ioTest/css/ui.css create mode 100644 html/ioTest/index.html create mode 100644 html/ioTest/js/ui.js create mode 100644 html/ioTest/ui.cfg diff --git a/cwIo.cpp b/cwIo.cpp index a7ff354..3cc49dc 100644 --- a/cwIo.cpp +++ b/cwIo.cpp @@ -2,6 +2,7 @@ #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" +#include "cwObject.h" #include "cwText.h" #include "cwTextBuf.h" @@ -14,6 +15,9 @@ #include "cwObject.h" #include "cwThread.h" +#include "cwThreadMach.h" +#include "cwMutex.h" + #include "cwSerialPort.h" #include "cwSerialPortSrv.h" @@ -21,12 +25,17 @@ #include "cwAudioBuf.h" #include "cwAudioDeviceAlsa.h" +#include "cwWebSock.h" +#include "cwUi.h" + namespace cw { namespace io { + struct io_str; + typedef struct serialPort_str { char* name; @@ -37,34 +46,58 @@ namespace cw serialPortSrv::handle_t serialH; } serialPort_t; - typedef struct audioCfg_str + typedef struct audioGroup_str { - unsigned enableFl; - char* name; - char* device; - double srate; - unsigned dspFrameCnt; - unsigned cycleCnt; - unsigned devIdx; - } audioCfg_t; - + bool enableFl; + audio_msg_t msg; + mutex::handle_t mutexH; + unsigned threadTimeOutMs; + struct io_str* p; + } audioGroup_t; + + typedef struct audioDev_str + { + bool enableFl; // + char* devName; // + unsigned devIdx; // + unsigned iCbCnt; // Count the audio driver in/out callbacks + unsigned oCbCnt; // + audioGroup_t* iGroup; // Audio group points for this device + audioGroup_t* oGroup; // + audio_group_dev_t* iagd; // audio group device record assoc'd with this + audio_group_dev_t* oagd; + } audioDev_t; + typedef struct io_str { - cbFunc_t cbFunc; - void* cbArg; + std::atomic quitFl; - thread::handle_t threadH; + cbFunc_t cbFunc; + void* cbArg; - serialPort_t* serialA; - unsigned serialN; + thread_mach::handle_t threadMachH; + + object_t* cfg; + + serialPort_t* serialA; + unsigned serialN; + + midi::device::handle_t midiH; - midi::device::handle_t midiH; - audio::device::handle_t audioH; audio::device::alsa::handle_t alsaH; + audio::buf::handle_t audioBufH; + unsigned audioThreadTimeOutMs; - unsigned audioCfgN; - audioCfg_t* audioCfgA; + audioDev_t* audioDevA; + unsigned audioDevN; + + audioGroup_t* audioGroupA; + unsigned audioGroupN; + + ui::ws::handle_t wsUiH; // ui::ws handle (invalid if no UI was specified) + ui::appIdMap_t* uiMapA; // Application supplied id's for the UI resource supplied with the cfg script via 'uiCfgFn'. + unsigned uiMapN; // } io_t; @@ -72,29 +105,157 @@ namespace cw io_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } + // Start or stop all the audio devices in p->audioDevA[] + rc_t _audioDeviceStartStop( io_t* p, bool startFl ) + { + rc_t rc = kOkRC; + + for(unsigned i=0; iaudioDevN; ++i) + if( p->audioDevA[i].enableFl ) + { + rc_t rc0 = kOkRC; + if( startFl ) + rc0 = audio::device::start( p->audioH, p->audioDevA[i].devIdx ); + else + rc0 = audio::device::stop( p->audioH, p->audioDevA[i].devIdx ); + + if(rc0 != kOkRC ) + rc = cwLogError(rc0,"The audio device: %s failed to %s.", cwStringNullGuard(p->audioDevA[i].devName), startFl ? "start" : "stop"); + + } + + return rc; + } + + + // Release all resource associated with a group-device record. + void _audioGroupDestroyDevs( audio_group_dev_t* agd ) + { + while( agd != nullptr ) + { + audio_group_dev_t* agd0 = agd->link; + mem::release(agd); + agd = agd0; + } + } + + // Release all resource associated with all audio group records + rc_t _audioGroupDestroyAll( io_t* p ) + { + rc_t rc = kOkRC; + + for(unsigned i=0; iaudioGroupN; ++i) + { + audioGroup_t* ag = p->audioGroupA + i; + _audioGroupDestroyDevs( ag->msg.iDevL ); + _audioGroupDestroyDevs( ag->msg.oDevL ); + mem::release(ag->msg.iBufArray); + mem::release(ag->msg.oBufArray); + + mutex::unlock( ag->mutexH ); + mutex::destroy( ag->mutexH ); + } + + mem::release(p->audioGroupA); + p->audioGroupN = 0; + return rc; + } + + rc_t _audioDestroy( io_t* p ) + { + rc_t rc = kOkRC; + + // stop each device - this will stop the callbacks to _audioDeviceCallback() + if((rc = _audioDeviceStartStop(p,false)) != kOkRC ) + { + rc = cwLogError(rc,"Audio device stop failed."); + goto errLabel; + } + + + if((rc = audio::device::alsa::destroy(p->alsaH)) != kOkRC ) + { + rc = cwLogError(rc,"ALSA sub-system shutdown failed."); + goto errLabel; + } + + + if((rc = audio::device::destroy(p->audioH)) != kOkRC ) + { + rc = cwLogError(rc,"Audio device sub-system shutdown failed."); + goto errLabel; + } + + + if((rc = audio::buf::destroy(p->audioBufH)) != kOkRC ) + { + rc = cwLogError(rc,"Audio buffer release failed."); + goto errLabel; + } + + if((rc = _audioGroupDestroyAll(p)) != kOkRC ) + { + rc = cwLogError(rc,"Audio group release failed."); + goto errLabel; + } + + mem::free(p->audioDevA); + p->audioDevN = 0; + + errLabel: + + if(rc != kOkRC ) + rc = cwLogError(rc,"Audio sub-system shutdown failed."); + + return rc; + + } + + rc_t _destroy( io_t* p ) { rc_t rc = kOkRC; - if((rc = thread::destroy(p->threadH)) != kOkRC ) + // stop thread callbacks + if((rc = thread_mach::destroy(p->threadMachH)) != kOkRC ) return rc; + for(unsigned i=0; iserialN; ++i) + serialPortSrv::destroy( p->serialA[i].serialH ); + + mem::free(p->serialA); + p->serialN = 0; + + // TODO: clean up the audio system more systematically + // by first stopping all the devices and then + // reversing the creating process. + + _audioDestroy(p); + + midi::device::destroy(p->midiH); + + + for(unsigned i=0; iuiMapN; ++i) + mem::free(const_cast(p->uiMapA[i].eleName)); + mem::release(p->uiMapA); + p->uiMapN = 0; + + ui::ws::destroy(p->wsUiH); + + // free the cfg object + if( p->cfg != nullptr ) + p->cfg->free(); + + + mem::release(p); return rc; } - bool _mainThreadFunc( void* arg ) - { - //io_t* p = static_cast(arg); - - - return true; - } - void _serialPortCb( void* arg, const void* byteA, unsigned byteN ) { - //io_t* p = static_cast(arg); + //io_t* p = reinterpret_cast(arg); } @@ -127,18 +288,18 @@ namespace cw switch( bits ) { - case 5: port->flags |= serialPort::kDataBits5Fl; - case 6: port->flags |= serialPort::kDataBits6Fl; - case 7: port->flags |= serialPort::kDataBits7Fl; - case 8: port->flags |= serialPort::kDataBits8Fl; + case 5: port->flags |= serialPort::kDataBits5Fl; break; + case 6: port->flags |= serialPort::kDataBits6Fl; break; + case 7: port->flags |= serialPort::kDataBits7Fl; break; + case 8: port->flags |= serialPort::kDataBits8Fl; break; default: rc = cwLogError(kSyntaxErrorRC,"Invalid serial data bits cfg:%i.",bits); } switch( stop ) { - case 1: port->flags |= serialPort::k1StopBitFl; - case 2: port->flags |= serialPort::k2StopBitFl; + case 1: port->flags |= serialPort::k1StopBitFl; break; + case 2: port->flags |= serialPort::k2StopBitFl; break; default: rc = cwLogError(kSyntaxErrorRC,"Invalid serial stop bits cfg:%i.",stop); } @@ -212,18 +373,18 @@ namespace cw { const midi::packet_t* pkt = pktArray + i; - //io_t* p = static_cast(pkt->cbDataPtr); + //io_t* p = reinterpret_cast(pkt->cbDataPtr); for(j=0; jmsgCnt; ++j) if( pkt->msgArray != NULL ) - printf("%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); + 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); else - printf("0x%x ",pkt->sysExMsg[j]); + printf("io midi cb: 0x%x ",pkt->sysExMsg[j]); } } - rc_t _midiPortCreate( io_t* p, const object_t*& c ) + rc_t _midiPortCreate( io_t* p, const object_t* c ) { rc_t rc = kOkRC; unsigned parserBufByteN = 1024; @@ -242,54 +403,392 @@ namespace cw return rc; } - void _audioDeviceCallback( void* cbArg, audio::device::audioPacket_t* inPktArray, unsigned inPktCnt, audio::device::audioPacket_t* outPktArray, unsigned outPktCnt ) + bool _audioGroupBufIsReady( io_t* p, audioGroup_t* ag, bool inputFl ) { - //io_t* p = (io_t*)cbArg; - + audio_group_dev_t* agd = inputFl ? ag->msg.iDevL : ag->msg.oDevL; + for(; agd!=nullptr; agd=agd->link) + if( !audio::buf::isDeviceReady( p->audioBufH, agd->devIdx, inputFl ? audio::buf::kInFl : audio::buf::kOutFl ) ) + return false; + + return true; } - rc_t _audioDeviceConfig( io_t* p, const object_t* c ) + // Get sample ponters or advance the pointers on the buffer associates with each device. + enum { kAudioGroupGetBuf, kAudioGroupAdvBuf }; + void _audioGroupProcSampleBufs( io_t* p, audioGroup_t* ag, unsigned processTypeId, unsigned inputFl ) { - rc_t rc = kOkRC; - unsigned meterMs = 50; - const object_t* node = nullptr; - const object_t* deviceL = nullptr; + sample_t** bufArray = inputFl ? ag->msg.iBufArray : ag->msg.oBufArray; + unsigned bufArrayChCnt = inputFl ? ag->msg.iBufChCnt : ag->msg.oBufChCnt; + audio_group_dev_t* agd = inputFl ? ag->msg.iDevL : ag->msg.oDevL; + unsigned audioBufFlags = inputFl ? audio::buf::kInFl : audio::buf::kOutFl; - // get the audio port node - if((node = c->find("audio")) == nullptr ) - return cwLogError(kSyntaxErrorRC,"Unable to locate the 'audio' configuration node."); - - // get the meterMs value - if((rc = node->get("meterMs", meterMs )) != kOkRC ) + unsigned chIdx = 0; + for(; agd!=nullptr && chIdx < bufArrayChCnt; agd=agd->link) { - rc = cwLogError(kSyntaxErrorRC,"Audio 'meterMs' parse failed."); + switch( processTypeId ) + { + case kAudioGroupGetBuf: + audio::buf::get( p->audioBufH, agd->devIdx, audioBufFlags, bufArray+chIdx, agd->chCnt ); + break; + + case kAudioGroupAdvBuf: + audio::buf::advance( p->audioBufH, agd->devIdx, audioBufFlags ); + break; + + default: + assert(0); + } + + chIdx += agd->chCnt; + } + } + + // This is the audio processing thread function. Block on the audio group condition var + // which is triggered when all the devices in the group are ready by _audioGroupNotifyIfReady(). + bool _audioGroupThreadFunc( void* arg ) + { + rc_t rc = kOkRC; + + audioGroup_t* ag = reinterpret_cast(arg); + + // block on the cond. var + if((rc = mutex::waitOnCondVar(ag->mutexH,false,ag->threadTimeOutMs)) != kOkRC ) + { + if( rc != kTimeOutRC ) + { + cwLogError(rc,"Audio thread Wait-on-condition-var failed."); + return false; + } + } + + // if the cond. var was signaled and ag->mutexH is locked + if( rc == kOkRC ) + { + msg_t msg; + msg.tid = kAudioTId; + msg.u.audio = &ag->msg; + + // While the all audio devices for this group are ready + while( _audioGroupBufIsReady( ag->p, ag, true ) && _audioGroupBufIsReady( ag->p, ag, false) ) + { + _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupGetBuf, true ); + _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupGetBuf, false ); + + ag->p->cbFunc(ag->p->cbArg,&msg); + + _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, true ); + _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false ); + } + } + return true; + } + + // Given a device index return the associated audioDev_t record. + audioDev_t* _audioDeviceIndexToRecd( io_t* p, unsigned devIdx ) + { + for(unsigned i=0; iaudioDevN; ++i) + if( p->audioDevA[i].devIdx == devIdx ) + return p->audioDevA + i; + + return nullptr; + } + + + // Add a audioGroup pointer to groupA[] and return the new count of elements in the array. + unsigned _audioDeviceUpdateGroupArray( audioGroup_t** groupA, unsigned groupN, unsigned curGroupN, audioGroup_t* ag ) + { + if( ag != nullptr ) + { + for(unsigned i=0; i= groupN ) + { + cwLogError(kAssertFailRC,"The group array was found to be too small during an audio device callback."); + goto errLabel; + } + + groupA[curGroupN++] = ag; + } + + errLabel: + return curGroupN; + } + + // If audioDev (devIdx) is ready then update audio_group_dev.readyCnt and store a pointer to it's associated group in groupA[]. + // Return the count of pointers stored in groupA[]. + unsigned _audioDeviceUpdateReadiness( io_t* p, unsigned devIdx, bool inputFl, audioGroup_t** groupA, unsigned groupN, unsigned curGroupN ) + { + audioDev_t* ad; + + // get the device record assoc'ed with this device + if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr ) + { + cwLogError(kAssertFailRC,"An unexpected audio device index was encountered in an audio device callback."); goto errLabel; } + // if an input packet was received on this device + if( inputFl ) + { + if( audio::buf::isDeviceReady( p->audioBufH, devIdx, audio::buf::kInFl) ) + { + // atomic incr - note that the ordering doesn't matter because the update does not control access to any other variables from another thread + std::atomic_store_explicit(&ad->iagd->readyCnt, ad->iagd->readyCnt++, std::memory_order_relaxed); + curGroupN = _audioDeviceUpdateGroupArray( groupA, groupN, curGroupN, ad->iGroup ); + ad->iagd->cbCnt += 1; // update the callback count for this device + } + } + else // if an output packet was received on this device + { + if( audio::buf::isDeviceReady( p->audioBufH, devIdx, audio::buf::kOutFl ) ) + { + std::atomic_store_explicit(&ad->oagd->readyCnt, ad->oagd->readyCnt++, std::memory_order_relaxed); // atomic incr + curGroupN = _audioDeviceUpdateGroupArray( groupA, groupN, curGroupN, ad->oGroup ); + ad->oagd->cbCnt += 1; + } + } + + errLabel: + // return the count of dev indexes in groupA[] + return curGroupN; + } + + + // Return true if all the devices in the linked list 'agd' are ready to source/sink data. + bool _audioGroupIsReady( audio_group_dev_t* agd ) + { + // are all devices in this group ready to provide/accept new audio data + for(; agd!=nullptr; agd=agd->link) + if( std::atomic_load_explicit(&agd->readyCnt, std::memory_order_acquire) > 0 ) // ACQUIRE + return false; + + return true; + } + + // Decrement the ready count on all devices in the linked list pointed to by 'agd'. + void _audioGroupDecrReadyCount( audio_group_dev_t* agd ) + { + for(; agd !=nullptr; agd=agd->link) + std::atomic_store_explicit(&agd->readyCnt,agd->readyCnt--, std::memory_order_release); // REALEASE + } + + // This function is called by the audio device driver callback _audioDeviceCallback(). + // If all devices in any of the groups contained in groupA[] are ready to source/sink + // audio data then this function triggers the condition var on the associated + // group thread to trigger audio processing on those devices. See _audioGroupThreadFunc(). + void _audioGroupNotifyIfReady( io_t* p, audioGroup_t** groupA, unsigned groupN ) + { + // for each device whose audio buffer state changed + for(unsigned i=0; imsg.iDevL ) && _audioGroupIsReady( ag->msg.oDevL ) ) + { + // we now know the group is ready and so the ready count maybe decremented on each device + _audioGroupDecrReadyCount( ag->msg.iDevL); + _audioGroupDecrReadyCount( ag->msg.oDevL); + + // notify the audio group thread that all devices are ready by signaling the condition var that it is blocked on + mutex::signalCondVar(ag->mutexH); + + } + } + + } + + // This function is called by the audio device drivers to when incoming audio arrives + // or when there is available space to write outgoing audio. + // If all in/out devices in a group are ready to be source/sink audio data then this function + // triggers the group thread condition var thereby causing an application callback + // to process the audio data. + void _audioDeviceCallback( void* cbArg, audio::device::audioPacket_t* inPktArray, unsigned inPktCnt, audio::device::audioPacket_t* outPktArray, unsigned outPktCnt ) + { + io_t* p = reinterpret_cast(cbArg); + + unsigned groupN = 2 * inPktCnt + outPktCnt; + audioGroup_t* groupA[ groupN ]; + unsigned curGroupN = 0; + + + // update the audio buffer + audio::buf::update( p->audioBufH, inPktArray, inPktCnt, outPktArray, outPktCnt ); + + // update the readiness of the input devices + for(unsigned i=0; i(); + + new_agd->name = ad->devName; + new_agd->devIdx = ad->devIdx; + new_agd->chCnt = chCnt; + + audio_group_dev_t*& agd = inputFl ? ag->msg.iDevL : ag->msg.oDevL; + + if( agd == nullptr ) + agd = new_agd; + else + { + for(; agd!=nullptr; agd=agd->link) + if( agd->link == nullptr ) + { + agd->link = new_agd; + break; + } + } + + // update the audio group channel count and set the pointers from + // the private audioDevice_t to the public audio_group_dev_t. + if( inputFl ) + { + ag->msg.iBufChCnt += chCnt; + ad->iagd = new_agd; + } + else + { + ag->msg.oBufChCnt += chCnt; + ad->oagd = new_agd; + } + + return rc; + } + + // Given an audio group id return the associated audio group. + audioGroup_t* _audioGroupFromId( io_t* p, unsigned groupId ) + { + for(unsigned i=0; iaudioGroupN; ++i) + if( p->audioGroupA[i].msg.groupId == groupId ) + return p->audioGroupA + i; + + return nullptr; + } + + + // Create the audio group records by parsing the cfg. audio.groupL[] list. + rc_t _audioDeviceParseAudioGroupList( io_t* p, const object_t* c ) + { + rc_t rc = kOkRC; + const object_t* groupL = nullptr; + + if((groupL = c->find("groupL")) == nullptr ) + return cwLogError(kSyntaxErrorRC,"Audio Group list 'groupL' not found."); + + p->audioGroupN = groupL->child_count(); + p->audioGroupA = mem::allocZ(p->audioGroupN); + + for(unsigned i=0; iaudioGroupN; ++i) + { + const object_t* node; + + if((node = groupL->child_ele(i)) != nullptr ) + { + if(( rc = node->getv( + "enableFl", p->audioGroupA[i].enableFl, + "id", p->audioGroupA[i].msg.groupId, + "srate", p->audioGroupA[i].msg.srate, + "dspFrameCnt", p->audioGroupA[i].msg.dspFrameCnt )) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing audio group cfg record at index:%i",i); + goto errLabel; + } + + // create the audio group thread mutex/cond var + if((rc = mutex::create(p->audioGroupA[i].mutexH)) != kOkRC ) + { + rc = cwLogError(rc,"Error creating audio group mutex."); + goto errLabel; + } + + if((rc = mutex::lock(p->audioGroupA[i].mutexH)) != kOkRC ) + { + rc = cwLogError(rc,"Error locking audio group mutex."); + goto errLabel; + } + + // create the audio group thread + if((rc = thread_mach::add(p->threadMachH,_audioGroupThreadFunc,p->audioGroupA+i)) != kOkRC ) + { + rc = cwLogError(rc,"Error creating audio group thread."); + goto errLabel; + } + + p->audioGroupA[i].p = p; + p->audioGroupA[i].threadTimeOutMs = p->audioThreadTimeOutMs; + + } + } + + errLabel: + return rc; + } + + + + // Create the audio device records by parsing the cfg audio.deviceL[] list. + rc_t _audioDeviceParseAudioDeviceList( io_t* p, const object_t* cfg ) + { + rc_t rc = kOkRC; + const object_t* deviceL_Node; + // get the audio device list - if((deviceL = node->find("deviceL")) == nullptr ) + if((deviceL_Node = cfg->find("deviceL")) == nullptr ) { rc = cwLogError(kSyntaxErrorRC,"Audio 'deviceL' failed."); goto errLabel; } // create an audio device cfg list - p->audioCfgN = deviceL->child_count(); - p->audioCfgA = mem::allocZ(p->audioCfgN); + p->audioDevN = deviceL_Node->child_count(); + p->audioDevA = mem::allocZ(p->audioDevN); // fill in the audio device cfg list - for(unsigned i=0; iaudioCfgN; ++i) + for(unsigned i=0; iaudioDevN; ++i) { - audioCfg_t* r = p->audioCfgA + i; - if((node = deviceL->child_ele(i)) == nullptr ) + audioDev_t* ad = p->audioDevA + i; + unsigned framesPerCycle = 0; + unsigned cycleCnt = 0; + + audioGroup_t* iag = nullptr; + audioGroup_t* oag = nullptr; + + double israte = 0; + double osrate = 0; + double srate = 0; + + unsigned iDspFrameCnt = 0; + unsigned oDspFrameCnt = 0; + unsigned dspFrameCnt = 0; + + unsigned inGroupId = kInvalidId; + unsigned outGroupId = kInvalidId; + + const object_t* node; + + if((node = deviceL_Node->child_ele(i)) != nullptr ) { if(( rc = node->getv( - "enableFl", r->enableFl, - "name", r->name, - "device", r->device, - "srate", r->srate, - "dspFrameCnt", r->dspFrameCnt, - "cycleCnt", r->cycleCnt )) != kOkRC ) + "enableFl", ad->enableFl, + "inGroupId", inGroupId, + "outGroupId", outGroupId, + "device", ad->devName, + "framesPerCycle", framesPerCycle, + "cycleCnt", cycleCnt )) != kOkRC ) { rc = cwLogError(rc,"Error parsing audio cfg record at index:%i",i); goto errLabel; @@ -297,21 +796,84 @@ namespace cw } // if the configuration is enabled - if( r->enableFl ) + if( ad->enableFl ) { // get the hardware device index - if((r->devIdx = audio::device::labelToIndex( p->audioH, r->device)) == kInvalidIdx ) + if((ad->devIdx = audio::device::labelToIndex( p->audioH, ad->devName)) == kInvalidIdx ) { - rc = cwLogError(rc,"Unable to locate the audio hardware device:'%s'.", r->device); + rc = cwLogError(rc,"Unable to locate the audio hardware device:'%s'.", cwStringNullGuard(ad->devName)); goto errLabel; } - // setup the device based on the configuration - if((rc = audio::device::setup(p->audioH,r->devIdx,r->srate,r->dspFrameCnt,_audioDeviceCallback,p)) != kOkRC ) + // get the device channel counts + unsigned iChCnt = audio::device::channelCount(p->audioH,ad->devIdx,true); + unsigned oChCnt = audio::device::channelCount(p->audioH,ad->devIdx,false); + + + // get the ingroup + if((iag = _audioGroupFromId(p, inGroupId )) != nullptr ) { - rc = cwLogError(rc,"Unable to setup the audio hardware device:'%s'.", r->device); + israte = iag->msg.srate; + iDspFrameCnt = iag->msg.dspFrameCnt; + } + + // get the outgroup + if((oag = _audioGroupFromId(p, outGroupId)) != nullptr ) + { + osrate = oag->msg.srate; + oDspFrameCnt = oag->msg.dspFrameCnt; + } + + // in-srate an out-srate must be equal or one must be 0 + if( osrate==0 || israte==0 || osrate==israte ) + { + // the true sample rate is the non-zero sample rate + srate = std::max(israte,osrate); + } + else + { + rc = cwLogError(kInvalidArgRC,"The device '%s' belongs to two groups (id:%i and id:%i) at different sample rates (%f != %f).", cwStringNullGuard(ad->devName), inGroupId, outGroupId, israte, osrate ); goto errLabel; - } + } + + // in-dspFrameCnt an out-dspFrameCnt must be equal or one must be 0 + if( oDspFrameCnt==0 || iDspFrameCnt==0 || oDspFrameCnt==iDspFrameCnt) + { + // the true sample rate is the non-zero sample rate + dspFrameCnt = std::max(iDspFrameCnt,oDspFrameCnt); + } + else + { + rc = cwLogError(kInvalidArgRC,"The device '%s' belongs to two groups (id:%i and id:%i) width different dspFrameCnt values (%i != %i).", cwStringNullGuard(ad->devName), inGroupId, outGroupId, iDspFrameCnt, oDspFrameCnt ); + goto errLabel; + } + + // setup the device based on the configuration + if((rc = audio::device::setup(p->audioH, ad->devIdx, srate, framesPerCycle, _audioDeviceCallback, p)) != kOkRC ) + { + rc = cwLogError(rc,"Unable to setup the audio hardware device:'%s'.", ad->devName); + goto errLabel; + } + + // initialize the audio bufer for this device + if((rc = audio::buf::setup( p->audioBufH, ad->devIdx, srate, dspFrameCnt, cycleCnt, iChCnt, framesPerCycle, oChCnt, framesPerCycle )) != kOkRC ) + { + rc = cwLogError(rc,"Audio device buffer channel setup failed."); + goto errLabel; + } + + if( iag != nullptr ) + if((rc = _audioGroupAddDevice( iag, true, ad, iChCnt )) != kOkRC ) + goto errLabel; + + if( oag != nullptr ) + if((rc = _audioGroupAddDevice( oag, false, ad, oChCnt )) != kOkRC ) + goto errLabel; + + // set the device group pointers + ad->iGroup = iag; + ad->oGroup = oag; + } } @@ -319,8 +881,76 @@ namespace cw return rc; } + // Allocate the sample ptr buffers for each audio group. + rc_t _audioGroupAllocBuffer( io_t* p ) + { + rc_t rc = kOkRC; + + for(unsigned i=0; iaudioGroupN; ++i) + { + audioGroup_t* ag = p->audioGroupA + i; - rc_t _audioDeviceCreate( io_t* p, const object_t*& c ) + if( ag->msg.iBufChCnt ) + ag->msg.iBufArray = mem::allocZ( ag->msg.iBufChCnt ); + + if( ag->msg.oBufChCnt ) + ag->msg.oBufArray = mem::allocZ( ag->msg.oBufChCnt ); + } + + return rc; + } + + rc_t _audioDeviceParseConfig( io_t* p, const object_t* cfg ) + { + rc_t rc = kOkRC; + unsigned meterMs = 50; + const object_t* node = nullptr; + + // get the audio port node + if((node = cfg->find("audio")) == nullptr ) + return cwLogError(kSyntaxErrorRC,"Unable to locate the 'audio' configuration node."); + + // get the meterMs value + if((rc = node->getv("meterMs", meterMs, "threadTimeOutMs", p->audioThreadTimeOutMs )) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Audio 'meterMs' or 'dspFrameCnt' parse failed."); + goto errLabel; + } + + // initialize the audio buffer + if((rc = audio::buf::create( p->audioBufH, audio::device::count(p->audioH), meterMs )) != kOkRC ) + { + rc = cwLogError(rc,"Audio device buffer failed."); + goto errLabel; + } + + // parse the audio group list + if((rc = _audioDeviceParseAudioGroupList( p, cfg )) != kOkRC ) + { + rc = cwLogError(rc,"Parse audio group list."); + goto errLabel; + } + + // parse the audio device list + if((rc = _audioDeviceParseAudioDeviceList( p, cfg )) != kOkRC ) + { + rc = cwLogError(rc,"Parse audio device list."); + goto errLabel; + } + + // create the audio buffer pointer arrays + if((rc = _audioGroupAllocBuffer(p)) != kOkRC ) + { + rc = cwLogError(rc,"Audio group buffer allocation failed."); + goto errLabel; + } + + errLabel: + return rc; + } + + + rc_t _audioDeviceCreate( io_t* p, const object_t* c ) { rc_t rc = kOkRC; audio::device::driver_t* audioDrv = nullptr; @@ -328,85 +958,163 @@ namespace cw // initialize the audio device interface if((rc = audio::device::create(p->audioH)) != kOkRC ) { - cwLogInfo("Initialize failed."); + rc = cwLogError(rc,"Initialize failed."); goto errLabel; } // initialize the ALSA device driver interface if((rc = audio::device::alsa::create(p->alsaH, audioDrv )) != kOkRC ) { - cwLogInfo("ALSA initialize failed."); + rc = cwLogError(rc,"ALSA initialize failed."); goto errLabel; } // register the ALSA device driver with the audio interface if((rc = audio::device::registerDriver( p->audioH, audioDrv )) != kOkRC ) { - cwLogInfo("ALSA driver registration failed."); + rc = cwLogError(rc,"ALSA driver registration failed."); goto errLabel; } // read the configuration information and setup the audio hardware - if((rc = _audioDeviceConfig( p, c )) != kOkRC ) + if((rc = _audioDeviceParseConfig( p, c )) != kOkRC ) { - cwLogInfo("Audio device configuration failed."); + rc = cwLogError(rc,"Audio device configuration failed."); goto errLabel; } - errLabel: return rc; } + + // This function is called by the websocket with messages comring from a remote UI. + rc_t _uiCallback( void* cbArg, unsigned wsSessId, ui::opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, const ui::value_t* v ) + { + io_t* p = (io_t*)cbArg; + msg_t r; + + r.tid = kUiTId; + r.u.ui = { .opId=opId, .wsSessId=wsSessId, .parentAppId=parentAppId, .uuId=uuId, .appId=appId, .value=v }; + + return p->cbFunc(p->cbArg,&r); + } + + rc_t _uiConfig( io_t* p, const object_t* c, const ui::appIdMap_t* mapA, unsigned mapN ) + { + rc_t rc = kOkRC; + const char* uiCfgLabel = "ui"; + ui::ws::args_t args; + + + // Duplicate the application id map + if( mapN > 0 ) + { + p->uiMapA = mem::allocZ( mapN ); + p->uiMapN = mapN; + for(unsigned i=0; iuiMapA[i] = mapA[i]; + p->uiMapA[i].eleName = mem::duplStr(mapA[i].eleName); + } + } + + + // if a UI cfg record was given + if( c->find(uiCfgLabel) != nullptr ) + { + + // parse the ui + if((rc = ui::ws::parseArgs( *c, args, uiCfgLabel )) == kOkRC ) + { + rc = ui::ws::create(p->wsUiH, args, p, _uiCallback, args.uiRsrc, p->uiMapA, p->uiMapN); + + ui::ws::releaseArgs(args); + + } + } + return rc; + + } + + rc_t _handleToWsUiHandle( handle_t h, ui::ws::handle_t& uiH_Ref ) + { + rc_t rc = kOkRC; + io_t* p = _handleToPtr(h); + if( p != nullptr && p->wsUiH.isValid()) + uiH_Ref = p->wsUiH; + else + rc = cwLogError(kInvalidStateRC,"Invalid ui::ws handle in io request."); + + return rc; + } + + rc_t _handleToUiHandle( handle_t h, ui::handle_t& uiHRef ) + { + rc_t rc; + ui::ws::handle_t wsUiH; + uiHRef.clear(); + if((rc = _handleToWsUiHandle(h, wsUiH)) != kOkRC ) + return rc; + + uiHRef = ui::ws::uiHandle(wsUiH); + + return uiHRef.isValid() ? rc : cwLogError(rc,"Invalid ui handle in io request."); + } + + } } -cw::rc_t cw::io::create( handle_t& h, const char* cfgStr, cbFunc_t cbFunc, void* cbArg, const char* cfgLabel ) +cw::rc_t cw::io::create( + handle_t& h, + const object_t* o, + cbFunc_t cbFunc, + void* cbArg, + const ui::appIdMap_t* mapA, + unsigned mapN, + const char* cfgLabel ) { rc_t rc; - object_t* obj_base = nullptr; - const object_t* o = nullptr; if((rc = destroy(h)) != kOkRC ) return rc; - + // create the io_t object io_t* p = mem::allocZ(); - // parse the configuration string - if((rc = objectFromString( cfgStr, obj_base)) != kOkRC ) - { - rc = cwLogError(rc,"Configuration parse failed."); - goto errLabel; - } + // duplicate the cfg object so that we can maintain pointers into its elements without + // any chance that they will be delted before the application completes + p->cfg = o->duplicate(); - // get the main io cfg object. - if((o = obj_base->find(cfgLabel)) == nullptr ) - { - rc = cwLogError(kSyntaxErrorRC,"Unable to locate the I/O cfg. label:%s.",cfgLabel); + // create the the thread machine + if((rc = thread_mach::create( p->threadMachH )) != kOkRC ) goto errLabel; - } - + // create the serial port device - if((rc = _serialPortCreate(p,o)) != kOkRC ) + if((rc = _serialPortCreate(p,p->cfg)) != kOkRC ) goto errLabel; // create the MIDI port device - if((rc = _midiPortCreate(p,o)) != kOkRC ) + if((rc = _midiPortCreate(p,p->cfg)) != kOkRC ) goto errLabel; // create the Audio device interface - if((rc = _audioDeviceCreate(p,o)) != kOkRC ) + if((rc = _audioDeviceCreate(p,p->cfg)) != kOkRC ) goto errLabel; - // create the the thread - if((rc = thread::create( p->threadH, _mainThreadFunc, p)) != kOkRC ) + // create the UI interface + if((rc = _uiConfig(p,p->cfg, mapA, mapN)) != kOkRC ) goto errLabel; + p->cbFunc = cbFunc; p->cbArg = cbArg; + p->quitFl.store(false); + + h.set(p); errLabel: if(rc != kOkRC ) @@ -436,17 +1144,54 @@ cw::rc_t cw::io::destroy( handle_t& h ) cw::rc_t cw::io::start( handle_t h ) { io_t* p = _handleToPtr(h); - return thread::pause( p->threadH, thread::kWaitFl ); + + _audioDeviceStartStop(p,true); + + return thread_mach::start( p->threadMachH ); } cw::rc_t cw::io::pause( handle_t h ) { io_t* p = _handleToPtr(h); - return thread::pause( p->threadH, thread::kPauseFl | thread::kWaitFl ); + return thread_mach::stop( p->threadMachH ); +} + +cw::rc_t cw::io::stop( handle_t h ) +{ + io_t* p = _handleToPtr(h); + p->quitFl.store(true); + return kOkRC; +} + +cw::rc_t cw::io::exec( handle_t h ) +{ + rc_t rc = kOkRC; + io_t* p = _handleToPtr(h); + if( p->wsUiH.isValid() ) + rc = ui::ws::exec(p->wsUiH ); + + // if the application quit flag is set then signal that the main thread needs to exit + + + return rc; +} + +bool cw::io::isShuttingDown( handle_t h ) +{ + io_t* p = _handleToPtr(h); + + return p->quitFl.load(); + + + //return thread_mach::is_shutdown(p->threadMachH); } +//---------------------------------------------------------------------------------------------------------- +// +// Serial +// unsigned cw::io::serialDeviceCount( handle_t h ) { @@ -478,6 +1223,10 @@ cw::rc_t cw::io::serialDeviceSend( handle_t h, unsigned devIdx, const void* byt } +//---------------------------------------------------------------------------------------------------------- +// +// MIDI +// unsigned cw::io::midiDeviceCount( handle_t h ) { @@ -523,47 +1272,272 @@ cw::rc_t cw::io::midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx, return rc; } +//---------------------------------------------------------------------------------------------------------- +// +// Audio +// unsigned cw::io::audioDeviceCount( handle_t h ) { io_t* p = _handleToPtr(h); - return p->audioCfgN; + return audio::device::count(p->audioH); } unsigned cw::io::audioDeviceLabelToIndex( handle_t h, const char* label ) { io_t* p = _handleToPtr(h); - for(unsigned i=0; iaudioCfgN; ++i) - if( strcmp(p->audioCfgA[i].name,label) == 0 ) - return i; - - return kInvalidIdx; + return audio::device::labelToIndex(p->audioH,label); } const char* cw::io::audioDeviceLabel( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); - assert( devIdx < p->audioCfgN ); - return p->audioCfgA[ devIdx ].name; + assert( devIdx < audioDeviceCount(h) ); + return audio::device::label(p->audioH,devIdx); } -cw::rc_t cw::io::audioDeviceStart( handle_t h, unsigned devIdx ) + + +//---------------------------------------------------------------------------------------------------------- +// +// Socket +// + + +//---------------------------------------------------------------------------------------------------------- +// +// WebSocket +// + + + +//---------------------------------------------------------------------------------------------------------- +// +// UI +// + +unsigned cw::io::uiFindElementAppId( handle_t h, unsigned parentUuId, const char* eleName ) { - io_t* p = _handleToPtr(h); - assert( devIdx < p->audioCfgN ); - return audio::device::start( p->audioH, p->audioCfgA[ devIdx ].devIdx ); + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + return ui::findElementAppId(uiH, parentUuId, eleName ); + return kInvalidId; } -cw::rc_t cw::io::audioDeviceStop( handle_t h, unsigned devIdx ) +unsigned cw::io::uiFindElementUuId( handle_t h, unsigned parentUuId, const char* eleName ) { - io_t* p = _handleToPtr(h); - assert( devIdx < p->audioCfgN ); - return audio::device::stop( p->audioH, p->audioCfgA[ devIdx ].devIdx ); + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + return ui::findElementUuId(uiH, parentUuId, eleName ); + return kInvalidId; } -bool cw::io::audioDeviceIsStarted( handle_t h, unsigned devIdx ) +const char* cw::io::uiFindElementName( handle_t h, unsigned uuId ) { - io_t* p = _handleToPtr(h); - assert( devIdx < p->audioCfgN ); - return audio::device::isStarted( p->audioH, p->audioCfgA[ devIdx ].devIdx ); + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + return ui::findElementName(uiH, uuId ); + return nullptr; } + +unsigned cw::io::uiFindElementAppId( handle_t h, unsigned uuId ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + return ui::findElementAppId(uiH, uuId ); + return kInvalidId; +} + +unsigned cw::io::uiFindElementUuId( handle_t h, const char* eleName ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + return ui::findElementUuId(uiH, eleName ); + return kInvalidId; +} + +cw::rc_t cw::io::uiCreateFromObject( handle_t h, const object_t* o, unsigned wsSessId, unsigned parentUuId, const char* eleName) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createFromObject(uiH,o,wsSessId,parentUuId,eleName); + return rc; +} + +cw::rc_t cw::io::uiCreateFromFile( handle_t h, const char* fn, unsigned wsSessId, unsigned parentUuId) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createFromFile(uiH,fn,wsSessId,parentUuId); + return rc; +} + +cw::rc_t cw::io::uiCreateFromText( handle_t h, const char* text, unsigned wsSessId, unsigned parentUuId) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createFromText(uiH,text,wsSessId,parentUuId); + return rc; +} + +cw::rc_t cw::io::uiCreateDiv( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createDiv(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateTitle( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createTitle(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateButton( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createButton(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateCheck( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createCheck(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateCheck( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, bool value ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createCheck(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,value); + return rc; +} + +cw::rc_t cw::io::uiCreateSelect( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createSelect(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateOption( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createOption(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateStr( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createStr(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiCreateStr( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, const char* value ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createStr(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,value); + return rc; +} + +cw::rc_t cw::io::uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, unsigned decPl ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createNumbDisplay(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,decPl); + return rc; +} + +cw::rc_t cw::io::uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, unsigned decPl, double value ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createNumbDisplay(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,value); + return rc; +} + +cw::rc_t cw::io::uiCreateNumb( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue, double stepValue, unsigned decPl ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createNumb(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,minValue,maxValue,stepValue,decPl); + return rc; +} + +cw::rc_t cw::io::uiCreateNumb( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue, double stepValue, unsigned decPl, double value ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createNumb(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,minValue,maxValue,stepValue,decPl,value); + return rc; +} + +cw::rc_t cw::io::uiCreateProg( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createProg(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,minValue,maxValue); + return rc; +} + +cw::rc_t cw::io::uiCreateProg( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue, double value ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createProg(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title,minValue,maxValue,value); + return rc; +} + +cw::rc_t cw::io::uiCreateText( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::createText(uiH,uuIdRef,wsSessId,parentUuId,eleName,appId,clas,title); + return rc; +} + +cw::rc_t cw::io::uiRegisterAppIdMap( handle_t h, const ui::appIdMap_t* map, unsigned mapN ) +{ + rc_t rc; + ui::handle_t uiH; + if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) + rc = ui::registerAppIdMap(uiH,map,mapN); + return rc; +} + diff --git a/cwIo.h b/cwIo.h index 364ba5e..703608b 100644 --- a/cwIo.h +++ b/cwIo.h @@ -6,6 +6,7 @@ #include "cwSerialPortDecls.h" #include "cwAudioDeviceDecls.h" #include "cwSocketDecls.h" +#include "cwUiDecls.h" namespace cw { @@ -20,7 +21,8 @@ namespace cw kMidiTId, kAudioTId, kSockTid, - kWebSockTId + kWebSockTId, + kUiTId }; typedef struct serial_msg_str @@ -37,23 +39,37 @@ namespace cw typedef audio::device::sample_t sample_t; + typedef struct audio_group_dev_str + { + const char* name; // Audio device name + unsigned devIdx; // Audio device index + unsigned chCnt; // Count of audio channels on this device + unsigned cbCnt; // Count of device driver callbacks + std::atomic_uint readyCnt;// Used internally do not read or write. + struct audio_group_dev_str* link; // + } audio_group_dev_t; + typedef struct audio_msg_str { - unsigned iDevIdx; - sample_t** iBufArray; - unsigned iBufChCnt; - time::spec_t* iTimeStampPtr; + unsigned groupId; // Unique group id + double srate; // Group sample rate. + unsigned dspFrameCnt; // Count of samples in each buffer pointed to by iBufArray[] and oBufArray[] - unsigned oDevIdx; - sample_t** oBufArray; - unsigned oBufChCnt; - time::spec_t* oTimeStampPtr; + sample_t** iBufArray; // Array of ptrs to buffers of size bufSmpCnt + unsigned iBufChCnt; // Count of elements in iBufArray[] + time::spec_t* iTimeStampPtr; // + audio_group_dev_t* iDevL; // Linked list of input devices which map directly to channels in iBufArray[] + + sample_t** oBufArray; // + unsigned oBufChCnt; // + time::spec_t* oTimeStampPtr; // + audio_group_dev_t* oDevL; // Linked list of output devices which map directly to channels in oBufArray[] } audio_msg_t; typedef struct socket_msg_str { - sock::cbOpId_t cbId; + sock::cbOpId_t cbId; unsigned userId; unsigned connId; const void* byteA; @@ -61,37 +77,63 @@ namespace cw const struct sockaddr_in* srcAddr; } socket_msg_t; + typedef struct ui_msg_str + { + ui::opId_t opId; + unsigned wsSessId; + unsigned parentAppId; + unsigned uuId; + unsigned appId; + const ui::value_t* value; + } ui_msg_t; + typedef struct msg_str { unsigned tid; union { - const serial_msg_t* serial; - const midi_msg_t* midi; - const audio_msg_t audio; - const socket_msg_t sock; + serial_msg_t* serial; + midi_msg_t* midi; + audio_msg_t* audio; + socket_msg_t* sock; + ui_msg_t ui; } u; } msg_t; - typedef void(*cbFunc_t)( void* arg, const msg_t* m ); + typedef rc_t (*cbFunc_t)( void* arg, const msg_t* m ); rc_t create( - handle_t& h, - const char* cfgStr, // configuration information as text - cbFunc_t cbFunc, - void* cbArg, - const char* cfgLabel="io" ); + handle_t& h, + const object_t* cfg, // configuration object + cbFunc_t cbFunc, + void* cbArg, + const ui::appIdMap_t* mapA = nullptr, + unsigned mapN = 0, + const char* cfgLabel = "io" ); rc_t destroy( handle_t& h ); rc_t start( handle_t h ); rc_t pause( handle_t h ); + rc_t stop( handle_t h ); + rc_t exec( handle_t h ); + bool isShuttingDown( handle_t h ); + + //---------------------------------------------------------------------------------------------------------- + // + // Serial + // unsigned serialDeviceCount( handle_t h ); const char* serialDeviceName( handle_t h, unsigned devIdx ); unsigned serialDeviceIndex( handle_t h, const char* name ); rc_t serialDeviceSend( handle_t h, unsigned devIdx, const void* byteA, unsigned byteN ); + //---------------------------------------------------------------------------------------------------------- + // + // MIDI + // + unsigned midiDeviceCount( handle_t h ); const char* midiDeviceName( handle_t h, unsigned devIdx ); unsigned midiDeviceIndex( handle_t h, const char* devName ); @@ -101,6 +143,11 @@ namespace cw rc_t midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 ); + //---------------------------------------------------------------------------------------------------------- + // + // Audio + // + unsigned audioDeviceCount( handle_t h ); unsigned audioDeviceLabelToIndex( handle_t h, const char* label ); const char* audioDeviceLabel( handle_t h, unsigned devIdx ); @@ -122,21 +169,28 @@ namespace cw sample_t audioDeviceChannelMeter( handle_t h, unsigned devIdx, unsigned chIdx, unsigned dirFl ); rc_t audioDeviceChannelSetGain( handle_t h, unsigned devIdx, unsigned chIdx, unsigned dirFl, double gain ); double audioDeviceChannelGain( handle_t h, unsigned devIdx, unsigned chIdx, unsigned dirFl ); - - rc_t audioDeviceStart( handle_t h, unsigned devIdx ); - rc_t audioDeviceStop( handle_t h, unsigned devIdx ); - bool audioDeviceIsStarted( handle_t h, unsigned devIdx ); + + unsigned audioGroupCount( handle_t h ); + const char* audioGroupLabel( handle_t h, unsigned groupIdx ); + unsigned audioGroupId( handle_t h, unsigned groupIdx ); + rc_t audioGroupSetId( handle_t h, unsigned groupIdx, unsigned groupId ); + //---------------------------------------------------------------------------------------------------------- + // + // Socket + // + rc_t socketSetup( handle_t h, unsigned timeOutMs, unsigned recvBufByteN, unsigned maxSocketN ); + rc_t socketCreate( - handle_t h, - unsigned userId, - short port, - unsigned flags, - const char* remoteAddr = nullptr, - sock::portNumber_t remotePort = sock::kInvalidPortNumber, - const char* localAddr = nullptr ); + handle_t h, + unsigned userId, + short port, + unsigned flags, + const char* remoteAddr = nullptr, + sock::portNumber_t remotePort = sock::kInvalidPortNumber, + const char* localAddr = nullptr ); rc_t socketDestroy( handle_t h, unsigned userId ); @@ -150,9 +204,58 @@ namespace cw rc_t socketSend( handle_t h, unsigned userId, const void* data, unsigned dataByteCnt, const char* remoteAddr, sock::portNumber_t port ); - - + //---------------------------------------------------------------------------------------------------------- + // + // UI + // + + + // Find id's associated with elements. + unsigned uiFindElementAppId( handle_t h, unsigned parentUuId, const char* eleName ); + unsigned uiFindElementUuId( handle_t h, unsigned parentUuId, const char* eleName ); + unsigned uiFindElementUuId( handle_t h, unsigned parentUuId, unsigned appId ); + const char* uiFindElementName( handle_t h, unsigned uuId ); + unsigned uiFindElementAppId( handle_t h, unsigned uuId ); + + // Return the uuid of the first matching 'eleName'. + unsigned uiFindElementUuId( handle_t h, const char* eleName ); + + rc_t uiCreateFromObject( handle_t h, const object_t* o, unsigned wsSessId, unsigned parentUuId=kInvalidId, const char* eleName=nullptr); + rc_t uiCreateFromFile( handle_t h, const char* fn, unsigned wsSessId, unsigned parentUuId=kInvalidId); + rc_t uiCreateFromText( handle_t h, const char* text, unsigned wsSessId, unsigned parentUuId=kInvalidId); + rc_t uiCreateDiv( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + rc_t uiCreateTitle( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + rc_t uiCreateButton( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + + // Create check: w/o value. The value will be read from the engine via the UI 'echo' event. + // Create check: w/ value. The value will be sent to the engine as the new value of the associated varaible. + rc_t uiCreateCheck( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + rc_t uiCreateCheck( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, bool value ); + + rc_t uiCreateSelect( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + rc_t uiCreateOption( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + + rc_t uiCreateStr( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + rc_t uiCreateStr( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, const char* value ); + + rc_t uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, unsigned decPl ); + rc_t uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, unsigned decPl, double value ); + + rc_t uiCreateNumb( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue, double stepValue, unsigned decPl ); + rc_t uiCreateNumb( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue, double stepValue, unsigned decPl, double value ); + + rc_t uiCreateProg( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue ); + rc_t uiCreateProg( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title, double minValue, double maxValue, double value ); + + rc_t uiCreateText( handle_t h, unsigned& uuIdRef, unsigned wsSessId, unsigned parentUuId, const char* eleName, unsigned appId, const char* clas, const char* title ); + + // Register parent/child/name app id's + rc_t uiRegisterAppIdMap( handle_t h, const ui::appIdMap_t* map, unsigned mapN ); + + // Send a value to the user interface + template< typename T > + rc_t uiValueToUI( handle_t h, unsigned wsSessId, unsigned uuId, const T& value ); } diff --git a/cwIoTest.cpp b/cwIoTest.cpp index 3f2bc73..8eae0c9 100644 --- a/cwIoTest.cpp +++ b/cwIoTest.cpp @@ -2,9 +2,11 @@ #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" +#include "cwObject.h" #include "cwTime.h" #include "cwMidiDecls.h" #include "cwMidi.h" +#include "cwUiDecls.h" #include "cwIo.h" #include "cwIoTest.h" @@ -12,65 +14,221 @@ namespace cw { namespace io { - void testCb( void* arg, const msg_t* m ) + // Application Id's for UI elements + enum { + // Resource Based elements + kPanelDivId, + kQuitBtnId, + kPanelBtn1Id, + kPanelCheck1Id, + kPanelBtn2Id, + kPanelCheck2Id, + kPanelFloaterId, + kSelId, + kOpt1Id, + kOpt2Id, + kOpt3Id + }; + + // Application Id's for the resource based UI elements. + ui::appIdMap_t mapA[] = + { + { ui::kRootAppId, kPanelDivId, "panelDivId" }, + { kPanelDivId, kQuitBtnId, "quitBtnId" }, + { kPanelDivId, kPanelBtn1Id, "myBtn1Id" }, + { kPanelDivId, kPanelCheck1Id, "myCheck1Id" }, + { kPanelDivId, kPanelBtn2Id, "myBtn2Id" }, + { kPanelDivId, kPanelCheck2Id, "myCheck2Id" }, + { kPanelDivId, kPanelFloaterId, "myFloater" }, + { kPanelDivId, kSelId, "mySel" }, + { kSelId, kOpt1Id, "myOpt1" }, + { kSelId, kOpt2Id, "myOpt2" }, + { kSelId, kOpt3Id, "myOpt3" }, + }; + + unsigned mapN = sizeof(mapA)/sizeof(mapA[0]); + + // Application object + typedef struct app_str + { + handle_t ioH; + } app_t; + + rc_t _onUiInit(app_t* app, const ui_msg_t& m ) + { + rc_t rc = kOkRC; + return rc; } + + rc_t _onUiValue(app_t* app, const ui_msg_t& m ) + { + rc_t rc = kOkRC; + + switch( m.appId ) + { + case kQuitBtnId: + io::stop( app->ioH ); + break; + } + return rc; + } + + rc_t _onUiEcho(app_t* app, const ui_msg_t& m ) + { + rc_t rc = kOkRC; + return rc; + } + + rc_t uiCb( app_t* app, const ui_msg_t& m ) + { + rc_t rc = kOkRC; + + switch( m.opId ) + { + case ui::kConnectOpId: + cwLogInfo("IO Test Connect: wsSessId:%i.",m.wsSessId); + break; + + case ui::kDisconnectOpId: + cwLogInfo("IO Test Disconnect: wsSessId:%i.",m.wsSessId); + break; + + case ui::kInitOpId: + _onUiInit(app,m); + break; + + case ui::kValueOpId: + _onUiValue( app, m ); + break; + + case ui::kEchoOpId: + _onUiEcho( app, m ); + break; + + case ui::kIdleOpId: + break; + + case ui::kInvalidOpId: + // fall through + default: + assert(0); + break; + + } + + return rc; + } + + rc_t audioCb( app_t* app, const audio_msg_t& m ) + { + rc_t rc = kOkRC; + + unsigned chN = std::min(m.iBufChCnt,m.oBufChCnt); + unsigned byteCnt = m.dspFrameCnt * sizeof(sample_t); + + // Copy the input to the output + for(unsigned i=0; i(arg); + + switch( m->tid ) + { + case kSerialTId: + break; + + case kMidiTId: + break; + + case kAudioTId: + if( m->u.audio != nullptr ) + rc = audioCb(app,*m->u.audio); + break; + + case kSockTid: + break; + + case kWebSockTId: + break; + + case kUiTId: + rc = uiCb(app,m->u.ui); + break; + + default: + assert(0); + + } + + return rc; + } + + void report( handle_t h ) + { + for(unsigned i=0; i + + + + UI Test App + + + + + + + + +
+

UI Test:

+

Disconnected

+
+ +
+
+ + + diff --git a/html/ioTest/js/ui.js b/html/ioTest/js/ui.js new file mode 100644 index 0000000..8c6f8c4 --- /dev/null +++ b/html/ioTest/js/ui.js @@ -0,0 +1,818 @@ +var _ws = null; +var _rootId = "0"; +var _nextEleId = 0; + +function set_app_title( suffix, className ) +{ + var ele = document.getElementById('connectTitleId'); + ele.innerHTML = suffix + ele.className = className +} + + +function uiOnError( msg, r) +{ + console.log("Error:" + msg); +} + +function uiGetParent( r ) +{ + parent_ele = document.getElementById(r.parent_id); + + if( parent_ele == null ) + { + uiOnError("Parent not found. parent_id:" + r.parent_id,r); + } + + return parent_ele; +} + +function uiCreateEle( r ) +{ + var parent_ele; + + if((parent_ele = uiGetParent(r)) != null ) + { + ele = document.createElement(r.ele_type) + ele.id = r.ele_id; + ele.className = r.value; + + parent_ele.appendChild(ele) + } +} + +function uiRemoveChildren( r ) +{ + ele = document.getElementById(r.ele_id) + + while (ele.firstChild) + { + ele.removeChild(ele.firstChild); + } +} + +function uiDivCreate( r ) +{ uiCreateEle(r) } + +function uiLabelCreate( r ) +{ + var parent_ele; + + if((parent_ele = uiGetParent(r)) != null ) + { + ele = document.createElement("label") + ele.htmlFor = r.ele_id + ele.innerHTML = r.value; + parent_ele.appendChild(ele) + } + +} + +function uiSelectCreate( r ) +{ + uiCreateEle(r) +} + +function uiSelectClear( r ) +{ uiRemoveChildren(r) } + +function uiSelectInsert( r ) +{ + var select_ele; + + if((select_ele = uiGetParent(r)) != null ) + { + var option = document.createElement('option'); + + option.id = r.ele_id; + option.innerHTML = r.value; + option.value = r.ele_id; + option.onclick = function() { uiOnSelectClick(this) } + + select_ele.appendChild(option) + } +} + +function uiSelectChoose( r ) +{ + var select_ele; + + if((select_ele = uiGetParent(r)) != null ) + { + if( select_ele.hasChildNodes()) + { + var children = select_ele.childNodes + for(var i=0; i 0) + { + label_ele = dom_create_ele("label"); + + label_ele.innerHTML = label; + + div_ele.appendChild(label_ele) + } + } + + return ui_create_ele( div_ele, ele_type, d, dfltEleClassName ); +} + +function ui_create_div( parent_ele, d ) +{ + var div_ele = ui_create_ele( parent_ele, "div", d, "uiDiv" ); + + if( div_ele != null ) + { + + if( d.title != null ) + { + var title = d.title.trim() + + if( title.length > 0 ) + { + var p_ele = dom_create_ele("p") + + p_ele.innerHTML = title + + div_ele.appendChild( p_ele ) + } + } + } + + return div_ele; +} + +function ui_create_panel_div( parent_ele, d ) +{ + d.type = "div" + var div_ele = ui_create_div( parent_ele, d ); + + if( !d.hasOwnProperty('className') ) + div_ele.className = "uiPanel" + + return div_ele +} + +function ui_create_row_div( parent_ele, d ) +{ + d.type = "div" + var div_ele = ui_create_div( parent_ele, d ); + + if( !d.hasOwnProperty('className') ) + div_ele.className = "uiRow" + + return div_ele +} + +function ui_create_col_div( parent_ele, d ) +{ + d.type = "div" + var div_ele = ui_create_div( parent_ele, d ); + + if( !d.hasOwnProperty('className') ) + div_ele.className = "uiCol" + + return div_ele +} + + +function ui_create_title( parent_ele, d ) +{ + var ele = ui_create_ele( parent_ele, "label", d, "uiTitle" ); + + if( ele != null ) + { + ele.innerHTML = d.title; + } + + return ele; +} + +function ui_create_button( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "button", null, d, "uiButton" ); + + if( ele != null ) + { + ele.innerHTML = d.title; + ele.onclick = function() { ui_send_int_value(this,1); } + } + + return ele; +} + +function ui_create_check( parent_ele, d ) +{ + var ele = ui_create_ctl( parent_ele, "input", d.title, d, "uiCheck" ) + + if( ele != null ) + { + ele.type = "checkbox"; + + ele.onclick = function() { ui_send_bool_value(this,dom_get_checkbox(this.id)); } + + if( !d.hasOwnProperty('value') ) + ui_send_echo(ele) + else + { + dom_set_checkbox(ele.id, d.value ); + ui_send_bool_value(ele,dom_get_checkbox(ele.id)) + } + + } + return ele; +} + +// +// Note: The value of a 'select' widget is always set by the 'appId' +// of the selected 'option'. Likewise the 'appId' of the selected +// option is returned as the value of the select widget. +// +function ui_on_select( ele ) +{ + ui_send_int_value(ele,ele.options[ ele.selectedIndex ].appId); +} + +function ui_select_set_from_option_app_id( sel_ele, option_appId ) +{ + var i; + for(i=0; i