//| 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 "cwObject.h" #include "cwText.h" #include "cwTextBuf.h" #include "cwIo.h" #include "cwMidi.h" #include "cwMidiDevice.h" #include "cwObject.h" #include "cwThread.h" #include "cwThreadMach.h" #include "cwMutex.h" #include "cwSerialPort.h" #include "cwSerialPortSrv.h" #include "cwAudioDevice.h" #include "cwAudioBuf.h" #include "cwAudioDeviceAlsa.h" #include "cwAudioDeviceFile.h" #include "cwSocket.h" #include "cwWebSock.h" #include "cwUi.h" #include "cwVectOps.h" namespace cw { namespace io { struct io_str; typedef struct thread_str { unsigned id; void* arg; bool asyncFl; struct io_str* p; struct thread_str* link; } thread_t; typedef struct timer_str { struct io_str* io; bool deletedFl; bool startedFl; char* label; unsigned id; unsigned index; unsigned periodMicroSec; bool asyncFl; time::spec_t nextTime; } timer_t; typedef struct serialPort_str { char* label; unsigned userId; bool asyncFl; char* device; unsigned baudRate; unsigned flags; serialPortSrv::handle_t serialH; } serialPort_t; typedef struct audioGroup_str { bool enableFl; bool asyncFl; audio_msg_t msg; mutex::handle_t mutexH; unsigned threadTimeOutMs; struct io_str* p; } audioGroup_t; typedef struct audioDev_str { bool activeFl; // True if this device was enabled by the user bool meterFl; // True if meters are enabled on this device const char* label; // User label unsigned userId; // User id char* devName; // System device name unsigned devIdx; // AudioDevice interface device index audioGroup_t* iGroup; // Audio group pointers for this device audioGroup_t* oGroup; // audio_group_dev_t* iagd; // Audio group device record assoc'd with this device audio_group_dev_t* oagd; // unsigned cycleCnt; unsigned framesPerCycle; struct audioDev_str* clockInList; // List of devices sync'd to this devices input clock struct audioDev_str* clockOutList; // List of devices sync'd to this devices output clock struct audioDev_str* clockLink; // links used by clockIn/OutList } audioDev_t; typedef struct socket_str { bool enableFl; bool asyncFl; char* label; unsigned sockA_index; unsigned userId; } socket_t; typedef struct io_str { std::atomic quitFl; std::atomic startedFl; time::spec_t t0; cbFunc_t cbFunc; void* cbArg; mutex::handle_t cbMutexH; unsigned cbMutexTimeOutMs; thread_mach::handle_t threadMachH; object_t* cfg; thread_t* threadL; timer_t* timerA; unsigned timerN; serialPort_t* serialA; unsigned serialN; serialPortSrv::handle_t serialPortSrvH; midi::device::handle_t midiH; bool midiAsyncFl; socket_t* sockA; unsigned sockN; sock::handle_t sockH; unsigned sockThreadTimeOutMs; audio::device::handle_t audioH; audio::device::alsa::handle_t alsaH; audio::device::file::handle_t audioDevFileH; audio::buf::handle_t audioBufH; unsigned audioThreadTimeOutMs; unsigned audioMeterDevEnabledN; unsigned audioMeterCbPeriodMs; time::spec_t audioMeterNextTime; 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; // bool uiAsyncFl; sample_t latency_meas_thresh_db; sample_t latency_meas_thresh_lin; bool latency_meas_enable_fl; latency_meas_result_t latency_meas_result; } io_t; //---------------------------------------------------------------------------------------------------------- // // io // io_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } // All callbacks to the application occur through this function rc_t _ioCallback( io_t* p, bool asyncFl, const msg_t* m, rc_t* app_rc_ref=nullptr ) { rc_t rc = kOkRC; bool unlockFl = false; bool isSynchronousFl = !asyncFl; bool isStartedFl = p->startedFl.load(); if( isStartedFl ) { // if this is a synchronous callback then lock the mutex if( isSynchronousFl ) { switch(rc = mutex::lock(p->cbMutexH,p->cbMutexTimeOutMs)) { case kOkRC: unlockFl = true; break; case kTimeOutRC: rc = cwLogError(rc,"io mutex callback mutex lock timed out."); break; default: rc = cwLogError(rc,"io mutex callback mutex lock failed."); } } // make the callback to the client if( rc == kOkRC ) { rc_t app_rc = p->cbFunc( p->cbArg, m ); if( app_rc_ref != nullptr ) *app_rc_ref = app_rc; } // if the mutex is locked if( unlockFl ) { if((rc = mutex::unlock(p->cbMutexH)) != kOkRC ) { // Time out is not a failure if( rc == kTimeOutRC ) rc = kOkRC; else rc = cwLogError(rc,"io mutex callback mutex unlock failed."); } } } return rc; } rc_t _ioParse( io_t* p, const object_t* cfg ) { rc_t rc = kOkRC; const object_t* ioCfg; if((ioCfg = cfg->find("io")) == nullptr ) { cwLogError(kInvalidArgRC,"The 'io' configuration block could not be found."); goto errLabel; } if((rc = ioCfg->getv("callbackMutexTimeOutMs",p->cbMutexTimeOutMs)) != kOkRC ) { cwLogError(rc,"Parsing of 'io' block configuration failed."); goto errLabel; } errLabel: return rc; } //---------------------------------------------------------------------------------------------------------- // // Thread // bool _threadFunc( void* arg ) { rc_t rc = kOkRC; thread_t* t = (thread_t*)arg; thread_msg_t tm = { .id=t->id, .arg=t->arg }; msg_t m; m.tid = kThreadTId; m.u.thread = &tm; if((rc = _ioCallback( t->p, t->asyncFl, &m )) != kOkRC ) cwLogError(rc,"Thread app callback failed."); return true; } void _threadRelease( io_t* p ) { thread_t* t0 = p->threadL; for(; t0!=nullptr; t0=t0->link) { thread_t* t1 = t0->link; mem::release(t0); t0 = t1; } } //---------------------------------------------------------------------------------------------------------- // // Timer // bool _timerThreadCb( void* arg ) { timer_t* t = (timer_t*)arg; time::spec_t t0; time::get(t0); int usec = time::diffMicros(t0,t->nextTime); if( usec > 0 ) sleepUs(usec); time::advanceMicros(t->nextTime,t->periodMicroSec); if( t->startedFl && !t->deletedFl ) { rc_t rc = kOkRC; msg_t m; timer_msg_t tm; tm.id = t->id; tm.index = t->index; m.tid = kTimerTId; m.u.timer = &tm; if((rc = _ioCallback( t->io, t->asyncFl, &m )) != kOkRC ) cwLogError(rc,"Timer app callback failed."); } return !t->deletedFl; } rc_t _timerCreate( io_t* p, const char* label, unsigned id, unsigned periodMicroSec, bool asyncFl ) { rc_t rc = kOkRC; timer_t* t = nullptr; unsigned timer_idx = kInvalidIdx; // look for a deleted timer for(unsigned i=0; itimerN; ++i) if( p->timerA[i].deletedFl ) { t = p->timerA + i; timer_idx = i; break; } // if no deleted timer was found if( t == nullptr ) { // reallocate the timer array with an additional slot timer_t* tA = mem::allocZ< timer_t >( p->timerN + 1 ); for(unsigned i=0; itimerN; ++i) tA[i] = p->timerA[i]; // keep a pointer to the empty slot t = tA + p->timerN; timer_idx = p->timerN; // update the timer array mem::release( p->timerA ); p->timerA = tA; p->timerN = p->timerN + 1; } assert( t != nullptr ); t->io = p; t->label = mem::duplStr(label); t->id = id; t->index = timer_idx; t->asyncFl = asyncFl; t->periodMicroSec = periodMicroSec; time::get(t->nextTime); if((rc = thread_mach::add(p->threadMachH,_timerThreadCb,t, label)) != kOkRC ) { rc = cwLogError(rc,"Timer thread assignment failed."); } return rc; } timer_t* _timerIndexToPtr( io_t* p, unsigned timerIdx ) { if( timerIdx >= p->timerN || p->timerA[ timerIdx ].deletedFl == true ) { cwLogError(kInvalidIdRC,"The timer index '%i' is invalid.", timerIdx ); return nullptr; } return p->timerA + timerIdx; } rc_t _timerStart( io_t* p, unsigned timerIdx, bool startFl ) { rc_t rc = kOkRC; timer_t* t = _timerIndexToPtr(p,timerIdx); if( t == nullptr ) rc = kInvalidIdRC; else p->timerA[ timerIdx ].startedFl = startFl; return rc; } //---------------------------------------------------------------------------------------------------------- // // Serial // void _serialPortCb( void* arg, unsigned serialCfgIdx, const void* byteA, unsigned byteN ) { io_t* p = (io_t*)arg; if( serialCfgIdx > p->serialN ) cwLogError(kAssertFailRC,"The serial cfg index %i is out of range %i in serial port callback.", serialCfgIdx, p->serialN ); else { rc_t rc = kOkRC; const serialPort_t* sp = p->serialA + serialCfgIdx; msg_t m; serial_msg_t sm; sm.label = sp->label; sm.userId = sp->userId; sm.dataA = byteA; sm.byteN = byteN; m.tid = kSerialTId; m.u.serial = &sm; if((rc = _ioCallback( p, sp->asyncFl, &m )) != kOkRC ) cwLogError(rc,"Serial port app callback failed."); } } rc_t _serialPortParseCfg( const object_t& e, serialPort_t* port, bool& enableFlRef ) { rc_t rc = kOkRC; char* parityLabel = nullptr; unsigned bits = 8; unsigned stop = 1; idLabelPair_t parityA[] = { { serialPort::kEvenParityFl, "even" }, { serialPort::kOddParityFl, "odd" }, { serialPort::kNoParityFl, "no" }, { 0, nullptr } }; if((rc = e.getv( "enableFl", enableFlRef, "asyncFl", port->asyncFl, "label", port->label, "device", port->device, "baud", port->baudRate, "bits", bits, "stop", stop, "parity", parityLabel )) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"Serial configuration parse failed."); } switch( bits ) { 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; break; case 2: port->flags |= serialPort::k2StopBitFl; break; default: rc = cwLogError(kSyntaxErrorRC,"Invalid serial stop bits cfg:%i.",stop); } unsigned i; for(i=0; parityA[i].label != nullptr; ++i) if( textCompare(parityLabel,parityA[i].label) == 0 ) { port->flags |= parityA[i].id; break; } if( parityA[i].label == nullptr ) rc = cwLogError(kSyntaxErrorRC,"Invalid parity cfg:'%s'.",cwStringNullGuard(parityLabel)); return rc; } rc_t _serialPortCreate( io_t* p, const object_t* c ) { rc_t rc = kOkRC; const object_t* cfg = nullptr; const object_t* port_array = nullptr; unsigned pollPeriodMs = 50; unsigned recvBufByteN = 512; bool enableFl = false; // get the serial port list node if((cfg = c->find("serial")) == nullptr) { cwLogWarning("No 'serial' configuration."); return kOkRC; } // the serial header values if((rc = cfg->getv("enableFl",enableFl, "pollPeriodMs", pollPeriodMs, "recvBufByteN", recvBufByteN, "array", port_array)) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"Serial cfg header parse failed."); goto errLabel; } p->serialN = port_array->child_count(); if( enableFl==false || p->serialN == 0 ) { p->serialN = 0; cwLogInfo("Serial port sub-system disabled."); goto errLabel; } p->serialA = mem::allocZ(p->serialN); // create the serial server if((rc = serialPortSrv::create(p->serialPortSrvH,pollPeriodMs,recvBufByteN)) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"Serial port server failed."); goto errLabel; } // for each serial port cfg for(unsigned i=0; iserialN; ++i) { const object_t* e = port_array->child_ele(i); serialPort_t* r = p->serialA + i; if( e == nullptr ) { rc = cwLogError(kSyntaxErrorRC,"Unable to access a 'serial' port configuration record at index:%i.",i); break; } else { bool enableFl = false; // parse the cfg record if((rc = _serialPortParseCfg(*e,r,enableFl)) != kOkRC ) { rc = cwLogError(rc,"Serial configuration parse failed on record index:%i.", i ); break; } if( enableFl ) { r->userId = i; // default the serial port userId to the index into serialA[] serialPort::handle_t spH = serialPortSrv::serialHandle(p->serialPortSrvH); // create the serial port object if((rc = serialPort::createPort( spH, i, r->device, r->baudRate, r->flags, _serialPortCb, p )) != kOkRC ) { rc = cwLogError(rc,"Serial port create failed on record index:%i.", i ); break; } } } } errLabel: return rc; } void _serialPortDestroy( io_t* p ) { serialPortSrv::destroy(p->serialPortSrvH); mem::release(p->serialA); p->serialN = 0; } rc_t _serialPortStart( io_t* p ) { rc_t rc = kOkRC; if( p->serialPortSrvH.isValid() ) // the service is only started if at least one serial port is enabled if( serialPort::portCount( serialPortSrv::serialHandle(p->serialPortSrvH) ) > 0 ) if((rc =serialPortSrv::start( p->serialPortSrvH )) != kOkRC ) rc = cwLogError(rc,"The serial port server start failed."); return rc; } //---------------------------------------------------------------------------------------------------------- // // MIDI // void _midiCallback( void* cbArg, const midi::packet_t* pktArray, unsigned pktCnt ) { unsigned i; for(i=0; i(cbArg); rc_t rc = kOkRC; mm.pkt = pkt; m.tid = kMidiTId; m.u.midi = &mm; if((rc = _ioCallback( p, p->midiAsyncFl, &m )) !=kOkRC ) cwLogError(rc,"MIDI app callback failed: async fl:%i",p->midiAsyncFl); /* for(unsigned j=0; jmsgCnt; ++j) if( pkt->msgArray != NULL ) 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("io midi cb: 0x%x ",pkt->sysExMsg[j]); */ } } rc_t _midiPortCreate( io_t* p, const object_t* c ) { rc_t rc = kOkRC; const object_t* cfg = nullptr; bool enableFl = false; // get the MIDI port cfg if((cfg = c->find("midi")) == nullptr) { cwLogWarning("No 'MIDI' configuration."); return kOkRC; } if((rc = cfg->getv( "enableFl", enableFl, "asyncFl", p->midiAsyncFl )) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"MIDI configuration parse failed."); } if( !enableFl ) cwLogInfo("MIDI device system disabled."); else { if((rc = create(p->midiH, _midiCallback, p, cfg)) != kOkRC ) return rc; } return rc; } //------------------------------------------------------------------------------------------------ // // Socket // socket_t* _socketIndexToRecd( io_t* p, unsigned sockIdx ) { if( sockIdx >= p->sockN ) cwLogError(kInvalidArgRC,"Invalid socket index (%i >= %i)", sockIdx, p->sockN); return p->sockA + sockIdx; } bool _socketThreadFunc( void* arg ) { rc_t rc = kOkRC; io_t* p = reinterpret_cast(arg); unsigned readByteN = 0; if((rc = receive_all(p->sockH, p->sockThreadTimeOutMs, readByteN)) != kOkRC ) { if( rc != kTimeOutRC ) cwLogWarning("Socket receive_all() failed."); } return true; } void _socketCallback( void* cbArg, sock::cbOpId_t cbId, unsigned sockArray_index, unsigned connId, const void* byteA, unsigned byteN, const struct sockaddr_in* srcAddr ) { io_t* p = reinterpret_cast(cbArg); if( sockArray_index >= p->sockN ) cwLogError(kInvalidArgRC,"The socket index '%i' outside range (0-%i).", sockArray_index, p->sockN ); else { socket_msg_t sm; sm.cbId = cbId; sm.sockIdx = sockArray_index, sm.userId = p->sockA[ sockArray_index ].userId; sm.connId = connId; sm.byteA = byteA; sm.byteN = byteN; sm.srcAddr = srcAddr; msg_t m; m.tid = kSockTId; m.u.sock = &sm; rc_t rc; if((rc = _ioCallback( p, p->sockA[ sockArray_index ].asyncFl, &m )) != kOkRC ) cwLogError(rc,"Socket app callback failed."); } } rc_t _socketParseAttrs( const object_t* attrL, unsigned& flagsRef ) { rc_t rc = kOkRC; const object_t* node = nullptr; idLabelPair_t attrA[] = { { .id=sock::kNonBlockingFl, .label="non_blocking" }, // Create a non-blocking socket. { .id=sock::kBlockingFl, .label="blocking" }, // Create a blocking socket. { .id=sock::kTcpFl, .label="tcp" }, // Create a TCP socket rather than a UDP socket. { .id=0, .label="udp" }, // { .id=sock::kBroadcastFl, .label="broadcast" }, // { .id=sock::kReuseAddrFl, .label="reuse_addr" }, // { .id=sock::kReusePortFl, .label="reuse_port" }, // { .id=sock::kMultiCastTtlFl, .label="multicast_ttl" }, // { .id=sock::kMultiCastLoopFl, .label="multicast_loop" }, // { .id=sock::kListenFl, .label="listen" }, // Use this socket to listen for incoming connections { .id=sock::kStreamFl, .label="stream" }, // Connected stream (vs. Datagram) { .id=sock::kTcpNoDelayFl, .label="tcp_no_delay" }, // Implements TCP_NODELAY { .id=0, .label=nullptr, } }; flagsRef = 0; for(unsigned j=0; jchild_count(); ++j) if((node = attrL->child_ele(j)) != nullptr && node->is_type( kStringTId ) ) { unsigned k; for(k=0; attrA[k].label != nullptr; ++k) if( textCompare(attrA[k].label, node->u.str) == 0 ) { flagsRef += attrA[k].id; break; } if( attrA[k].label == nullptr ) { rc = cwLogError(kInvalidArgRC,"The attribute label '%s'.",cwStringNullGuard(node->u.str)); } } return rc; } rc_t _socketParseConfig( io_t* p, const object_t* cfg ) { const object_t* node = nullptr; rc_t rc = kOkRC; unsigned maxSocketCnt = 10; unsigned recvBufByteCnt = 4096; const object_t* socketL = nullptr; bool enableFl = false; // get the socket configuration node if((node = cfg->find("socket")) == nullptr ) { cwLogWarning("No 'socket' configuration node."); return kOkRC; } // get the required socket arguments if(( rc = node->getv( "enableFl", enableFl, "maxSocketCnt", maxSocketCnt, "recvBufByteCnt", recvBufByteCnt, "threadTimeOutMs", p->sockThreadTimeOutMs, "socketL", socketL )) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"Unable to parse the 'socket' configuration node."); goto errLabel; } // THe max socket count must be at least as large as the number of defined sockets maxSocketCnt = std::max(p->sockN,maxSocketCnt); // create the socket control array p->sockN = socketL->child_count(); if( enableFl == false || p->sockN == 0 ) { p->sockN = 0; cwLogInfo("Socket system disabled."); goto errLabel; } p->sockA = mem::allocZ(p->sockN); // create the socket manager if((rc = sock::createMgr( p->sockH, recvBufByteCnt, maxSocketCnt )) != kOkRC ) { rc = cwLogError(rc,"Socket manager creation failed."); goto errLabel; } // parse each socket configuration for(unsigned i=0; isockN; ++i) { if((node = socketL->child_ele(i)) != nullptr ) { unsigned port = sock::kInvalidPortNumber; unsigned timeOutMs = 50; const object_t* attrL = nullptr; char* remoteAddr = nullptr; unsigned remotePort = sock::kInvalidPortNumber; char* localAddr = nullptr; unsigned flags = 0; // parse the required arguments if(( rc = node->getv( "enableFl", p->sockA[i].enableFl, "asyncFl", p->sockA[i].asyncFl, "label", p->sockA[i].label, "port", port, "timeOutMs", timeOutMs, "attrL", attrL )) != kOkRC ) { rc = cwLogError(rc,"Error parsing required socket cfg record at index:%i",i); goto errLabel; } // parse the optional arguments if((rc = node->getv_opt( "userId", p->sockA[i].userId, "remoteAddr", remoteAddr, "remotePort", remotePort, "localAddr", localAddr)) != kOkRC ) { rc = cwLogError(rc,"Error parsing optional socket cfg record at index:%i",i); goto errLabel; } // parse the socket attribute list if((rc = _socketParseAttrs( attrL, flags )) != kOkRC ) goto errLabel; // create the socket object if((rc = create( p->sockH, i, port, flags, timeOutMs, _socketCallback, p, remoteAddr, remotePort, localAddr )) != kOkRC ) { rc = cwLogError(rc,"Socket create failed."); goto errLabel; } } } // create the socket thread if( p->sockN > 0 ) if((rc = thread_mach::add(p->threadMachH,_socketThreadFunc,p,"io_socket")) != kOkRC ) { rc = cwLogError(rc,"Error creating socket thread."); goto errLabel; } errLabel: return rc; } //---------------------------------------------------------------------------------------------------------- // // Audio // // Start or stop all the audio devices in p->audioDevA[] rc_t _audioDeviceStartStop( io_t* p, bool startFl ) { rc_t rc = kOkRC; rc_t rc1 = kOkRC; for(unsigned i=0; iaudioDevN; ++i) if( p->audioDevA[i].activeFl ) { 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"); } if( p->audioDevFileH.isValid() ) { if( startFl ) rc1 = audio::device::file::start( p->audioDevFileH ); else rc1 = audio::device::file::stop( p->audioDevFileH ); if( rc1 != kOkRC ) rc1 = cwLogError(rc1,"Audio device file sub-system %s failed.", startFl ? "start" : "stop"); } return rcSelect(rc,rc1); } // 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->meterA); 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 ); // the mutex is expected to be locked at this point 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::file::destroy(p->audioDevFileH)) != kOkRC ) { rc = cwLogError(rc,"Audio device file 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; } for(unsigned i=0; iaudioDevN; ++i) mem::release( p->audioDevA[i].devName ); mem::free(p->audioDevA); p->audioDevN = 0; errLabel: if(rc != kOkRC ) rc = cwLogError(rc,"Audio sub-system shutdown failed."); return rc; } // Are either input or output meter enabled on this audio devices. // Set flags to kInFl or kOutput to select inut or output meters. // Set both flags to check if either input or output meters are enabled. bool _audioDeviceIsMeterEnabled( audioDev_t* ad, unsigned flags ) { return (cwIsFlag(flags,kInFl ) && ad->iagd!=nullptr && cwIsFlag(ad->iagd->flags,kMeterFl)) || (cwIsFlag(flags,kOutFl) && ad->oagd!=nullptr && cwIsFlag(ad->oagd->flags,kMeterFl)); } rc_t _audioGroupDeviceProcessMeter( io_t* p, audio_group_dev_t* agd, audioGroup_t* ag, unsigned audioBufFlags ) { rc_t rc = kOkRC; if( agd != nullptr && ag != nullptr && cwIsFlag(agd->flags,kMeterFl)) { rc_t app_rc = kOkRC; msg_t m; m.tid = kAudioMeterTId; m.u.audioGroupDev = agd; // get the current meter values from the audioBuf audio::buf::meter(p->audioBufH, agd->devIdx, audioBufFlags, agd->meterA, agd->chCnt ); // callback the application with the current meter values if((rc = _ioCallback( p, ag->asyncFl, &m, &app_rc )) != kOkRC ) cwLogError(rc,"Audio meter app callback failed."); rc = app_rc; } return rc; } rc_t _audioDeviceProcessMeters( io_t* p ) { rc_t rc = kOkRC; // if it is time to execute the next meter update if( time::isGTE(p->t0,p->audioMeterNextTime) ) { for(unsigned i=0; iaudioDevN; ++i) if( p->audioDevA[i].activeFl ) { audioDev_t* ad = p->audioDevA + i; rc_t rc0; // update the input meters if((rc0 = _audioGroupDeviceProcessMeter( p, ad->iagd, ad->iGroup, audio::buf::kInFl )) != kOkRC ) rc = rc0; // update the output meters if((rc0 = _audioGroupDeviceProcessMeter( p, ad->oagd, ad->oGroup, audio::buf::kOutFl )) != kOkRC ) rc = rc0; } // schedule the next meter update p->audioMeterNextTime = p->t0; time::advanceMs(p->audioMeterNextTime,p->audioMeterCbPeriodMs); } return rc; } bool _audioGroupBufIsReady( io_t* p, audioGroup_t* ag, bool inputFl ) { 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; } // Get sample pointers 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 ) { 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; unsigned chIdx = 0; for(; agd!=nullptr && chIdx < bufArrayChCnt; agd=agd->link) { 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; } } void _audio_latency_measure( io_t* p, const audio_msg_t& msg ) { if( p->latency_meas_enable_fl ) { if( msg.iBufChCnt && p->latency_meas_result.audio_in_ts.tv_nsec == 0 ) { sample_t rms = vop::rms(msg.iBufArray[0], msg.dspFrameCnt ); if( rms > p->latency_meas_thresh_lin ) time::get(p->latency_meas_result.audio_in_ts); if( rms > p->latency_meas_result.audio_in_rms_max ) p->latency_meas_result.audio_in_rms_max = rms; } if( msg.oBufChCnt && p->latency_meas_result.audio_out_ts.tv_nsec == 0 ) { sample_t rms = vop::rms(msg.oBufArray[0], msg.dspFrameCnt ); if( rms > p->latency_meas_thresh_lin ) time::get(p->latency_meas_result.audio_out_ts); if( rms > p->latency_meas_result.audio_out_rms_max ) p->latency_meas_result.audio_out_rms_max = rms; } p->latency_meas_enable_fl = p->latency_meas_result.audio_in_ts.tv_nsec == 0 || p->latency_meas_result.audio_out_ts.tv_nsec == 0; } } // 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 ); if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC ) cwLogError(rc,"Audio app callback failed %i.",ag->asyncFl); _audio_latency_measure( ag->p, ag->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, bool reportMissingFl=true ) { for(unsigned i=0; iaudioDevN; ++i) if( p->audioDevA[i].devIdx == devIdx ) return p->audioDevA + i; if( reportMissingFl) cwLogError(kInvalidArgRC,"A device with index %i could not be found.",devIdx); return nullptr; } // Given a device name return the associated audioDev_t record. audioDev_t* _audioDeviceNameToRecd( io_t* p, const char* devName, bool reportMissingFl=true) { for(unsigned i=0; iaudioDevN; ++i) if( textCompare(p->audioDevA[i].devName, devName ) == 0 ) return p->audioDevA + i; if( reportMissingFl ) cwLogError(kInvalidArgRC,"A device named '%s' was not found.", cwStringNullGuard(devName) ); return nullptr; } // Given a user label return the associated audioDev_t record. audioDev_t* _audioDeviceLabelToRecd( io_t* p, const char* label, bool reportMissingFl=true ) { for(unsigned i=0; iaudioDevN; ++i) if( textCompare(label,p->audioDevA[i].label) == 0 ) return p->audioDevA + i; if( reportMissingFl ) cwLogError(kInvalidArgRC,"An audio device with label '%s' could not be found.",cwStringNullGuard(label)); return nullptr; } rc_t _audioDeviceParams( io_t* p, unsigned devIdx, unsigned flags, audioDev_t*& adRef, unsigned& audioBufFlagsRef ) { rc_t rc = kOkRC; if((adRef = _audioDeviceIndexToRecd(p,devIdx)) == nullptr ) rc = kInvalidArgRC; else { audioBufFlagsRef = 0; if( cwIsFlag(flags,kInFl) ) audioBufFlagsRef += audio::buf::kInFl; if( cwIsFlag(flags,kOutFl) ) audioBufFlagsRef += audio::buf::kOutFl; if( cwIsFlag(flags,kEnableFl) ) audioBufFlagsRef += audio::buf::kEnableFl; } return rc; } rc_t _audioDeviceEnableMeter( io_t* p, unsigned devIdx, unsigned inOutEnaFlags ) { rc_t rc = kOkRC; audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOutEnaFlags, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Enable tone failed."); else { bool enaFl = inOutEnaFlags & kEnableFl; bool enaState0Fl = _audioDeviceIsMeterEnabled(ad, kInFl | kOutFl); audioBufFlags += audio::buf::kMeterFl; if( ad->iagd != nullptr && (inOutEnaFlags & kInFl) ) ad->iagd->flags = cwEnaFlag(ad->iagd->flags,kMeterFl,enaFl); if( ad->oagd != nullptr && (inOutEnaFlags & kOutFl) ) ad->oagd->flags = cwEnaFlag(ad->oagd->flags,kMeterFl,enaFl); audio::buf::setFlag( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags ); bool enaState1Fl= _audioDeviceIsMeterEnabled(ad, kInFl | kOutFl); if( enaState1Fl and !enaState0Fl ) p->audioMeterDevEnabledN += 1; else if( p->audioMeterDevEnabledN > 0 && !enaState1Fl && enaState0Fl ) p->audioMeterDevEnabledN -= 1; } if( rc != kOkRC ) rc = cwLogError(rc,"Enable meters failed."); return rc; } // Add an 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*& syncAdRef ) { audioDev_t* ad; syncAdRef = nullptr; // 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) && ad->iagd != nullptr ) { // 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+1, std::memory_order_relaxed); curGroupN = _audioDeviceUpdateGroupArray( groupA, groupN, curGroupN, ad->iGroup ); ad->iagd->cbCnt += 1; // update the callback count for this device syncAdRef = ad->clockInList; } } else // if an output packet was received on this device { if( audio::buf::isDeviceReady( p->audioBufH, devIdx, audio::buf::kOutFl ) && ad->oagd != nullptr ) { std::atomic_store_explicit(&ad->oagd->readyCnt, ad->oagd->readyCnt+1, std::memory_order_relaxed); // atomic incr curGroupN = _audioDeviceUpdateGroupArray( groupA, groupN, curGroupN, ad->oGroup ); ad->oagd->cbCnt += 1; syncAdRef = ad->clockOutList; } } 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); } } } void _audioDevSync( io_t* p, audioDev_t** syncDevA, unsigned syncDevN ) { for(unsigned i=0; iclockLink) if(audio::device::execute( p->audioH, ad->devIdx ) != kOkRC ) cwLogWarning("Synced audio device '%s' execution failed.",ad->label); } // This function is called by the audio device drivers 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; // These arrays are allocated with more space than they need - to avoid 0 length array when inPktCnt or outPktCnt is 0. audioDev_t* iSyncDevListA[ inPktCnt+outPktCnt ]; audioDev_t* oSyncDevListA[ inPktCnt+outPktCnt ]; // 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->label = ad->label; new_agd->userId = ad->userId; new_agd->devName= ad->devName; new_agd->devIdx = ad->devIdx; new_agd->flags = inputFl ? kInFl : kOutFl; new_agd->chCnt = chCnt; new_agd->chIdx = inputFl ? ag->msg.iBufChCnt : ag->msg.oBufChCnt; new_agd->meterA = mem::allocZ(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 userId, bool reportMissingFl=true ) { for(unsigned i=0; iaudioGroupN; ++i) if( p->audioGroupA[i].msg.userId == userId ) return p->audioGroupA + i; if( reportMissingFl ) cwLogError(kInvalidArgRC,"An audio group with user id %i could not be found.", userId ); return nullptr; } audioGroup_t* _audioGroupFromIndex( io_t* p, unsigned groupIdx, bool reportMissingFl=true ) { if( groupIdx < p->audioGroupN ) return p->audioGroupA + groupIdx; if( reportMissingFl ) cwLogError(kInvalidArgRC,"'%i' is not a valid audio group index.",groupIdx); return nullptr; } // Given an audio group id return the associated audio group. audioGroup_t* _audioGroupFromLabel( io_t* p, const char* label, bool reportMissingFl=true ) { for(unsigned i=0; iaudioGroupN; ++i) if( textCompare(p->audioGroupA[i].msg.label, label) == 0 ) return p->audioGroupA + i; if( reportMissingFl ) cwLogError(kInvalidArgRC,"An audio group with labe '%s' could not be found.", label ); 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, "asyncFl", p->audioGroupA[i].asyncFl, "label", p->audioGroupA[i].msg.label, "id", p->audioGroupA[i].msg.userId, "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; } // Lock the mutex so that it is already locked when it is used to block the audio thread // This avoids having to use logic in the thread callback to lock it on the first entry // while not locking it on all following entries. 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,"io_audio_group")) != kOkRC ) { rc = cwLogError(rc,"Error creating audio group thread."); goto errLabel; } p->audioGroupA[i].p = p; p->audioGroupA[i].threadTimeOutMs = p->audioThreadTimeOutMs; p->audioGroupA[i].msg.groupIndex = i; } } errLabel: return rc; } // Link dstAudioDev onto the list of devices that depend on srcAudioDev's clock. rc_t _audioDeviceConnectToClockSource( io_t* p, const char* srcAudioDevLabel, bool clockSrcInputFl, audioDev_t* dstAudioDev ) { rc_t rc = kOkRC; audioDev_t* srcAudioDev = nullptr; if((srcAudioDev = _audioDeviceLabelToRecd(p,srcAudioDevLabel)) == nullptr ) { rc = cwLogError(kInvalidArgRC,"The clock source '%s' could not be found for the audio device '%s'.",cwStringNullGuard(srcAudioDevLabel),cwStringNullGuard(dstAudioDev->label)); goto errLabel; } if( clockSrcInputFl ) { dstAudioDev->clockLink = srcAudioDev->clockInList; srcAudioDev->clockInList = dstAudioDev; } else { dstAudioDev->clockLink = srcAudioDev->clockOutList; srcAudioDev->clockOutList = dstAudioDev; } errLabel: return rc; } rc_t _audioDeviceConfigure( io_t* p, audioDev_t* ad, audioGroup_t* iag, audioGroup_t* oag, unsigned cycleCnt, unsigned framesPerCycle ) { rc_t rc = kOkRC; double israte = 0; double osrate = 0; double srate = 0; unsigned iDspFrameCnt = 0; unsigned oDspFrameCnt = 0; unsigned dspFrameCnt = 0; unsigned iChCnt = 0; unsigned oChCnt = 0; const char* inGroupLabel = iag==nullptr || iag->msg.label==nullptr ? "" : iag->msg.label; const char* outGroupLabel = oag==nullptr || oag->msg.label==nullptr ? "" : oag->msg.label; // get the ingroup if( iag != nullptr ) { israte = iag->msg.srate; iDspFrameCnt = iag->msg.dspFrameCnt; } // get the outgroup if( oag != nullptr ) { osrate = oag->msg.srate; oDspFrameCnt = oag->msg.dspFrameCnt; } // in-srate and 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 (%s and %s) at different sample rates (%f != %f).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), 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 (%s and %s) width different dspFrameCnt values (%i != %i).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), 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; } // get the device channel counts iChCnt = audio::device::channelCount(p->audioH,ad->devIdx,true); oChCnt = audio::device::channelCount(p->audioH,ad->devIdx,false); // 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; } 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 = cfg->find("deviceL")) == nullptr ) { rc = cwLogError(kSyntaxErrorRC,"Audio 'deviceL' failed."); goto errLabel; } // create an audio device cfg list p->audioDevN = audio::device::count(p->audioH); p->audioDevA = mem::allocZ(p->audioDevN); // Initial audioDev record setup for(unsigned i=0; iaudioDevN; ++i) { p->audioDevA[i].devName = mem::duplStr(audio::device::label(p->audioH,i)); p->audioDevA[i].devIdx = i; p->audioDevA[i].userId = kInvalidId; } // fill in the audio device cfg list for(unsigned i=0; ichild_count(); ++i) { audioDev_t* ad = nullptr; bool activeFl = false; bool meterFl = false; char* userLabel = nullptr; unsigned userId = kInvalidId; char* devName = nullptr; 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; */ char* inGroupLabel = nullptr; char* outGroupLabel = nullptr; const char* clockSrcDev = nullptr; bool clockSrcInputFl = false; const object_t* node; if((node = deviceL_Node->child_ele(i)) != nullptr ) { if(( rc = node->getv( "activeFl", activeFl, "label", userLabel, "device", devName, "framesPerCycle", framesPerCycle, "cycleCnt", cycleCnt )) != kOkRC ) { rc = cwLogError(rc,"Error parsing required audio cfg record at index:%i",i); goto errLabel; } if((rc = node->getv_opt( "userId", userId, "meterFl", meterFl, "inGroup", inGroupLabel, "outGroup", outGroupLabel, "clockSrcDev",clockSrcDev, "syncToClockSrcDevInputFl", clockSrcInputFl )) != kOkRC ) { rc = cwLogError(rc,"Error parsing optional audio cfg record at index:%i",i); goto errLabel; } } // if the configuration is enabled if( activeFl ) { // locate the record assoc'd with devName if((ad = _audioDeviceNameToRecd(p,devName)) == nullptr ) { rc = kInvalidArgRC; goto errLabel; } // get the hardware device index if((ad->devIdx = audio::device::labelToIndex( p->audioH, ad->devName)) == kInvalidIdx ) { rc = cwLogError(rc,"Unable to locate the audio hardware device:'%s'.", cwStringNullGuard(ad->devName)); goto errLabel; } if( inGroupLabel != nullptr ) iag = _audioGroupFromLabel(p, inGroupLabel ); // get the outgroup if( outGroupLabel != nullptr ) oag = _audioGroupFromLabel(p, outGroupLabel); if((rc = _audioDeviceConfigure(p, ad, iag, oag, cycleCnt, framesPerCycle )) != kOkRC ) goto errLabel; /* // get the ingroup if( inGroupLabel != nullptr ) if((iag = _audioGroupFromLabel(p, inGroupLabel )) != nullptr ) { israte = iag->msg.srate; iDspFrameCnt = iag->msg.dspFrameCnt; } // get the outgroup if( outGroupLabel != nullptr ) if((oag = _audioGroupFromLabel(p, outGroupLabel)) != nullptr ) { osrate = oag->msg.srate; oDspFrameCnt = oag->msg.dspFrameCnt; } // in-srate and 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 (%s and %s) at different sample rates (%f != %f).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), 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 (%s and %s) width different dspFrameCnt values (%i != %i).", cwStringNullGuard(ad->devName), cwStringNullGuard(inGroupLabel), cwStringNullGuard(outGroupLabel), 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; } // 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); // 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; } */ unsigned iChCnt = audio::device::channelCount(p->audioH,ad->devIdx,true); unsigned oChCnt = audio::device::channelCount(p->audioH,ad->devIdx,false); // if an input group was assigned to this device then create a assoc'd audio_group_dev_t if( iag != nullptr ) { if((rc = _audioGroupAddDevice( iag, true, ad, iChCnt )) != kOkRC ) goto errLabel; } // if an output group was assigned to this device then create a assoc'd audio_group_dev_t if( oag != nullptr ) { if((rc = _audioGroupAddDevice( oag, false, ad, oChCnt )) != kOkRC ) goto errLabel; } // if this device is dependent on another device as a clock if( clockSrcDev != nullptr ) { // ... then link it to the device if((rc = _audioDeviceConnectToClockSource( p, clockSrcDev, clockSrcInputFl, ad )) != kOkRC ) goto errLabel; } // set the device group pointers ad->activeFl = activeFl; ad->meterFl = meterFl; ad->label = userLabel; ad->userId = userId; ad->iGroup = iag; ad->oGroup = oag; ad->cycleCnt = cycleCnt; ad->framesPerCycle = framesPerCycle; } } errLabel: return rc; } rc_t _audioDeviceEnableMeters( io_t* p ) { rc_t rc = kOkRC; for(unsigned i=0; iaudioDevN; ++i) if( p->audioDevA[i].activeFl && p->audioDevA[i].meterFl ) if((rc = _audioDeviceEnableMeter(p, p->audioDevA[i].devIdx, kEnableFl | kInFl | kOutFl )) != kOkRC ) { cwLogError(rc,"Audio enable on device '%s' failed.",p->audioDevA[i].label); goto errLabel; } errLabel: 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; 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 _audioParseConfig( io_t* p, const object_t* node ) { rc_t rc = kOkRC; // get the meterMs value if((rc = node->getv("meterMs", p->audioMeterCbPeriodMs, "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), p->audioMeterCbPeriodMs )) != kOkRC ) { rc = cwLogError(rc,"Audio device buffer failed."); goto errLabel; } // parse the audio group list if((rc = _audioDeviceParseAudioGroupList( p, node )) != kOkRC ) { rc = cwLogError(rc,"Parse audio group list."); goto errLabel; } // parse the audio device list if((rc = _audioDeviceParseAudioDeviceList( p, node )) != 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; } // initialize enabled audio meters if((rc = _audioDeviceEnableMeters(p)) != kOkRC ) { rc = cwLogError(rc,"Audio device enabled failed."); goto errLabel; } errLabel: return rc; } rc_t _audioCreateDeviceFiles(io_t* p, const object_t* cfg, audio::device::driver_t*& audioDrvRef ) { rc_t rc = kOkRC; const object_t* flist = nullptr; audioDrvRef = nullptr; // locate the audio device file cfg. node if((rc = cfg->getv_opt( "files", flist)) != kOkRC ) { rc = cwLogError(rc,"Error obtaining audio device file cfg. node."); goto errLabel; } // if no audio device file cfg. node was given - then there are no audio device files if( flist == nullptr ) goto errLabel; // create the audio devicce file mgr if((rc = create( p->audioDevFileH, audioDrvRef )) != kOkRC ) { rc = cwLogError(rc,"The audio device file manager create failed."); goto errLabel; } for(unsigned i=0; ichild_count(); ++i) { const object_t* fnode = flist->child_ele(i); if( fnode == nullptr ) { cwLogWarning("The audio device file descriptor at index %i is empty.",i); } else { bool enableFl = false; const char* label = nullptr; const char* iFname = nullptr; bool iRwdOnStartFl = false; bool iCacheFl = true; bool iUseInternalClkFl = false; const char* oFname = nullptr; unsigned oChCnt = 0; unsigned oCacheBlockSec = 10; bool oRwdOnStartFl = false; bool oCacheFl = true; bool oUseInternalClkFl = false; if((rc = fnode->getv( "device_label",label)) != kOkRC || label == nullptr ) { cwLogError(rc,"The label parse failed on audio device descriptor at index %i.",i); goto errLabel; } if((rc = fnode->getv_opt("enableFl", enableFl, "in_fname", iFname, "in_rewind_on_start_fl",iRwdOnStartFl, "in_cache_fl",iCacheFl, "out_use_internal_clock_fl",iUseInternalClkFl, "out_fname", oFname, "out_rewind_on_start_fl",oRwdOnStartFl, "out_cache_fl",oCacheFl, "out_use_internal_clock_fl",oUseInternalClkFl, "out_cache_block_sec",oCacheBlockSec, "out_ch_count", oChCnt )) != kOkRC ) { cwLogError(rc,"The optional variables parse failed on audio device descriptor at index %i label:%s.",i,cwStringNullGuard(label)); goto errLabel; } if( enableFl ) { if( iFname != nullptr ) { unsigned iFlags = iRwdOnStartFl ? audio::device::file::kRewindOnStartFl : 0; iFlags += iCacheFl ? audio::device::file::kCacheFl : 0; iFlags += iUseInternalClkFl ? audio::device::file::kUseInternalClockFl : 0; if((rc = createInDevice( p->audioDevFileH, label, iFname, iFlags )) != kOkRC ) { cwLogError(rc,"Create failed on input audio device file '%s'.",cwStringNullGuard(label)); goto errLabel; } } if( oFname != nullptr ) { unsigned oFlags = oRwdOnStartFl ? audio::device::file::kRewindOnStartFl : 0; oFlags += oCacheFl ? audio::device::file::kCacheFl : 0; oFlags += oUseInternalClkFl ? audio::device::file::kUseInternalClockFl : 0; // bitsPerSample==0 indicates the output file should use a floating point sample format if((rc = createOutDevice( p->audioDevFileH, label, oFname, oFlags, oChCnt, 0, oCacheBlockSec )) != kOkRC ) { cwLogError(rc,"Create failed on output audio device file '%s'.",cwStringNullGuard(label)); goto errLabel; } } } } } errLabel: if( rc != kOkRC ) { audioDrvRef = nullptr; rc = cwLogError(rc,"Audio device file iniitaliztion failed."); } return rc; } rc_t _audioCreate( io_t* p, const object_t* c ) { rc_t rc = kOkRC; audio::device::driver_t* audioDrv = nullptr; const object_t* cfg = nullptr; bool enableFl = false; // get the audio port node if((cfg = c->find("audio")) == nullptr ) { cwLogWarning("No 'audio' configuration node."); goto errLabel; } if((rc = cfg->getv("enableFl",enableFl)) != kOkRC ) { rc = cwLogError(rc,"Error reading top level audio cfg."); goto errLabel; } if( !enableFl ) { cwLogInfo("Audio sub-system disabled."); goto errLabel; } // initialize the audio device interface if((rc = audio::device::create(p->audioH)) != kOkRC ) { rc = cwLogError(rc,"Initialize failed."); goto errLabel; } // initialize the ALSA device driver interface if((rc = audio::device::alsa::create(p->alsaH, audioDrv )) != kOkRC ) { 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 ) { rc = cwLogError(rc,"ALSA driver registration failed."); goto errLabel; } // create the audio device sub-system - audio device files must be created // before they can be referenced in _audioParseConfig(). if((rc = _audioCreateDeviceFiles(p,cfg,audioDrv)) != kOkRC ) { rc = cwLogError(rc,"The audio device file creation failed."); goto errLabel; } // register audio device file driver if( audioDrv != nullptr ) if((rc = audio::device::registerDriver( p->audioH, audioDrv )) != kOkRC ) { rc = cwLogError(rc,"The audio device file driver registration failed."); goto errLabel; } // read the configuration information and setup the audio hardware if((rc = _audioParseConfig( p, cfg )) != kOkRC ) { rc = cwLogError(rc,"Audio device configuration failed."); goto errLabel; } errLabel: if( rc != kOkRC && p->audioH.isValid() ) audio::device::report( p->audioH ); return rc; } //---------------------------------------------------------------------------------------------------------- // // UI // // This function is called by the websocket with messages coming from a remote UI. rc_t _uiCallback( void* cbArg, unsigned wsSessId, ui::opId_t opId, unsigned parentAppId, unsigned uuId, unsigned appId, unsigned chanId, const ui::value_t* v ) { io_t* p = (io_t*)cbArg; msg_t r; rc_t rc = kOkRC; rc_t app_rc = kOkRC; r.tid = kUiTId; r.u.ui = { .opId=opId, .wsSessId=wsSessId, .parentAppId=parentAppId, .uuId=uuId, .appId=appId, .chanId=chanId, .value=v }; if((rc = _ioCallback( p, p->uiAsyncFl, &r, &app_rc )) != kOkRC ) cwLogError(rc,"UI app callback failed."); return app_rc; } 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 = {}; const object_t* ui_cfg = nullptr; bool enableFl = false; // 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((ui_cfg = c->find(uiCfgLabel)) != nullptr ) { if((rc = ui_cfg->getv("enableFl",enableFl, "asyncFl",p->uiAsyncFl)) != kOkRC ) { rc = cwLogError(rc,"UI configuration parse failed."); goto errLabel; } if( !enableFl ) { cwLogInfo("UI sub-system disabled."); goto errLabel; } // 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); //ui::enableCache( ui::ws::uiHandle(p->wsUiH) ); } } errLabel: 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."); } //---------------------------------------------------------------------------------------------------------- // // IO // rc_t _destroy( io_t* p ) { rc_t rc = kOkRC; // stop thread callbacks if((rc = thread_mach::destroy(p->threadMachH)) != kOkRC ) return rc; for(unsigned i=0; itimerN; ++i) mem::release(p->timerA[i].label); mem::release(p->timerA); p->timerN = 0; destroy(p->cbMutexH); _serialPortDestroy(p); _audioDestroy(p); midi::device::destroy(p->midiH); sock::destroyMgr( p->sockH ); mem::release(p->sockA); 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; } } } //---------------------------------------------------------------------------------------------------------- // // IO // 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; if((rc = destroy(h)) != kOkRC ) return rc; // create the io_t object io_t* p = mem::allocZ(); // 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(); p->cbFunc = cbFunc; p->cbArg = cbArg; // parse the 'io' configuration block if((rc = _ioParse(p,o)) != kOkRC ) goto errLabel; // create the callback mutex if((rc = mutex::create( p->cbMutexH )) != kOkRC ) goto errLabel; // create the the thread machine if((rc = thread_mach::create( p->threadMachH )) != kOkRC ) goto errLabel; // create the serial port device if((rc = _serialPortCreate(p,p->cfg)) != kOkRC ) goto errLabel; // create the MIDI port device if((rc = _midiPortCreate(p,p->cfg)) != kOkRC ) goto errLabel; // create the Audio device interface if((rc = _audioCreate(p,p->cfg)) != kOkRC ) goto errLabel; // create the Socket manager if((rc= _socketParseConfig(p, p->cfg )) != kOkRC ) goto errLabel; // create the UI interface if((rc = _uiConfig(p,p->cfg, mapA, mapN)) != kOkRC ) goto errLabel; p->quitFl.store(false); p->startedFl.store(false); time::get(p->t0); h.set(p); errLabel: if(rc != kOkRC ) _destroy(p); return rc; } cw::rc_t cw::io::destroy( handle_t& h ) { rc_t rc = kOkRC; if( !h.isValid() ) return rc; io_t* p = _handleToPtr(h); if((rc = _destroy(p)) != kOkRC ) return rc; h.clear(); return rc; } cw::rc_t cw::io::start( handle_t h ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); if((rc = _audioDeviceStartStop(p,true)) != kOkRC ) goto errLabel; if((rc = _serialPortStart(p)) != kOkRC ) goto errLabel; if((rc = thread_mach::start( p->threadMachH )) != kOkRC ) { cwLogError(rc,"Thread machine start failed."); goto errLabel; } p->startedFl.store(true); errLabel: if( rc != kOkRC ) stop(h); return rc; } cw::rc_t cw::io::pause( handle_t h ) { io_t* p = _handleToPtr(h); rc_t rc; if((rc = thread_mach::stop( p->threadMachH )) != kOkRC ) { cwLogError(rc,"Thread machine stop failed."); goto errLabel; } p->startedFl.store(false); errLabel: return rc; } cw::rc_t cw::io::stop( handle_t h ) { io_t* p = _handleToPtr(h); p->quitFl.store(true); rc_t rc0 = thread_mach::stop(p->threadMachH ); // stop the audio devices rc_t rc1 = _audioDeviceStartStop(p,false); // clear the UI //if( p->wsUiH.isValid() ) // uiDestroyElement(h,ui::kRootUuId); rc_t rc = rcSelect(rc0,rc1); if(rc == kOkRC ) p->startedFl.store(false); return rc; } cw::rc_t cw::io::exec( handle_t h, unsigned timeOutMs, void* execCbArg ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); if( p->wsUiH.isValid() ) { ui::flushCache( ui::ws::uiHandle( p->wsUiH )); // Note this call blocks on the websocket handle: See cwUi.h:ws:exec() rc = ui::ws::exec( p->wsUiH, timeOutMs ); } time::get(p->t0); if( p->audioMeterDevEnabledN ) _audioDeviceProcessMeters(p); msg_t m; m.tid = kExecTId; m.u.exec.execArg = execCbArg; _ioCallback(p,false,&m); 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); } void cw::io::report( handle_t h ) { for(unsigned i=0; iaudioH ); midi::device::report(p->midiH); } void cw::io::realTimeReport( handle_t h ) { io_t* p = _handleToPtr(h); audio::device::realTimeReport(p->audioH); uiRealTimeReport(h); } //---------------------------------------------------------------------------------------------------------- // // Thread // cw::rc_t cw::io::threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg, const char* label ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); thread_t* t = mem::allocZ(1); t->id = id; t->asyncFl = asyncFl; t->arg = arg; t->p = p; t->link = p->threadL; p->threadL = t; if((rc = thread_mach::add( p->threadMachH, _threadFunc, t, label )) != kOkRC ) rc = cwLogError(rc,"Thread create failed."); return rc; } //---------------------------------------------------------------------------------------------------------- // // Timer // cw::rc_t cw::io::timerCreate( handle_t h, const char* label, unsigned id, unsigned periodMicroSec, bool asyncFl ) { io_t* p = _handleToPtr(h); return _timerCreate(p, label, id, periodMicroSec, asyncFl ); } cw::rc_t cw::io::timerDestroy( handle_t h, unsigned timerIdx ) { io_t* p = _handleToPtr(h); timer_t* t = _timerIndexToPtr( p, timerIdx ); if( t != nullptr ) { t->startedFl = false; t->deletedFl = true; } return t==nullptr ? kInvalidIdRC : kOkRC; } unsigned cw::io::timerCount( handle_t h ) { io_t* p = _handleToPtr(h); return p->timerN; } unsigned cw::io::timerLabelToIndex( handle_t h, const char* label ) { io_t* p = _handleToPtr(h); for(unsigned i=0; itimerN; ++i) if( !p->timerA[i].deletedFl && strcmp(label,p->timerA[i].label) == 0 ) return i; return kInvalidIdx; } unsigned cw::io::timerIdToIndex( handle_t h, unsigned timerId ) { io_t* p = _handleToPtr(h); for(unsigned i=0; itimerN; ++i) { timer_t* t = p->timerA + i; if( !t->deletedFl && t->id == timerId ) return i; } return kInvalidIdx; } const char* cw::io::timerLabel( handle_t h, unsigned timerIdx ) { io_t* p = _handleToPtr(h); timer_t* t = _timerIndexToPtr( p, timerIdx ); return t==nullptr ? nullptr : t->label; } unsigned cw::io::timerId( handle_t h, unsigned timerIdx ) { io_t* p = _handleToPtr(h); timer_t* t = _timerIndexToPtr( p, timerIdx ); return t==nullptr ? kInvalidId : t->id; } unsigned cw::io::timerPeriodMicroSec( handle_t h, unsigned timerIdx ) { io_t* p = _handleToPtr(h); timer_t* t = _timerIndexToPtr( p, timerIdx ); return t==nullptr ? 0 : t->periodMicroSec; } cw::rc_t cw::io::timerSetPeriodMicroSec( handle_t h, unsigned timerIdx, unsigned periodMicroSec ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); timer_t* t = _timerIndexToPtr(p, timerIdx); if( t == nullptr ) rc = kInvalidIdRC; else { p->timerA[ timerIdx ].periodMicroSec = periodMicroSec; } return rc; } cw::rc_t cw::io::timerSetNextTime( handle_t h, unsigned timerIdx, const time::spec_t& time ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); timer_t* t = _timerIndexToPtr(p, timerIdx); if( t == nullptr ) rc = kInvalidIdRC; else { p->timerA[ timerIdx ].nextTime = time; } return rc; } cw::rc_t cw::io::timerStart( handle_t h, unsigned timerIdx ) { io_t* p = _handleToPtr(h); return _timerStart( p, timerIdx, true ); } cw::rc_t cw::io::timerStop( handle_t h, unsigned timerIdx ) { io_t* p = _handleToPtr(h); return _timerStart( p, timerIdx, false ); } //---------------------------------------------------------------------------------------------------------- // // Serial // bool cw::io::serialIsEnabled( handle_t h ) { io_t* p = _handleToPtr(h); return p->serialN != 0; } unsigned cw::io::serialDeviceCount( handle_t h ) { io_t* p = _handleToPtr(h); return p->serialN; } unsigned cw::io::serialDeviceIndex( handle_t h, const char* label ) { io_t* p = _handleToPtr(h); for(unsigned i=0; iserialN; ++i) if( textCompare(label,p->serialA[i].label) == 0 ) return i; return kInvalidIdx; } const char* cw::io::serialDeviceLabel( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); return p->serialA[devIdx].label; } unsigned cw::io::serialDeviceId( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); return p->serialA[devIdx].userId; } void cw::io::serialDeviceSetId( handle_t h, unsigned devIdx, unsigned id ) { io_t* p = _handleToPtr(h); p->serialA[devIdx].userId = id; } cw::rc_t cw::io::serialDeviceSend( handle_t h, unsigned devIdx, const void* byteA, unsigned byteN ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); if( devIdx >= p->serialN ) { rc = cwLogError(kInvalidArgRC,"%i is an invalid serial device index.",devIdx); goto errLabel; } if((rc = send( p->serialPortSrvH, p->serialA[devIdx].userId, byteA, byteN )) != kOkRC ) { rc = cwLogError(rc,"Send on serial device index %i failed.",devIdx); goto errLabel; } errLabel: return rc; } //---------------------------------------------------------------------------------------------------------- // // MIDI // bool cw::io::midiIsEnabled( handle_t h ) { io_t* p = _handleToPtr(h); return p->midiH.isValid(); } unsigned cw::io::midiDeviceCount( handle_t h ) { io_t* p = _handleToPtr(h); if( !p->midiH.isValid() ) return 0; return midi::device::count(p->midiH); } const char* cw::io::midiDeviceName( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); return midi::device::name(p->midiH,devIdx); } unsigned cw::io::midiDeviceIndex( handle_t h, const char* devName ) { io_t* p = _handleToPtr(h); return midi::device::nameToIndex(p->midiH, devName); } unsigned cw::io::midiDevicePortCount( handle_t h, unsigned devIdx, bool inputFl ) { io_t* p = _handleToPtr(h); return midi::device::portCount(p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl ); } const char* cw::io::midiDevicePortName( handle_t h, unsigned devIdx, bool inputFl, unsigned portIdx ) { io_t* p = _handleToPtr(h); return midi::device::portName( p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl, portIdx ); } unsigned cw::io::midiDevicePortIndex( handle_t h, unsigned devIdx, bool inputFl, const char* portName ) { io_t* p = _handleToPtr(h); return midi::device::portNameToIndex( p->midiH, devIdx, inputFl ? midi::kInMpFl : midi::kOutMpFl, portName ); } cw::rc_t cw::io::midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 ) { io_t* p = _handleToPtr(h); return midi::device::send( p->midiH, devIdx, portIdx, status, d0, d1 ); } unsigned cw::io::midiDeviceMaxBufferMsgCount( handle_t h ) { io_t* p = _handleToPtr(h); return midi::device::maxBufferMsgCount(p->midiH ); } const cw::midi::ch_msg_t* cw::io::midiDeviceBuffer( handle_t h, unsigned& msgCntRef ) { io_t* p = _handleToPtr(h); return midi::device::getBuffer(p->midiH, msgCntRef ); } cw::rc_t cw::io::midiDeviceClearBuffer( handle_t h, unsigned msgCnt ) { io_t* p = _handleToPtr(h); return midi::device::clearBuffer(p->midiH, msgCnt ); } cw::rc_t cw::io::midiOpenMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname ) { return midi::device::openMidiFile( _handleToPtr(h)->midiH, devIdx, portIdx, fname ); } cw::rc_t cw::io::midiLoadMsgPacket( handle_t h, const midi::packet_t& pkt ) { return midi::device::loadMsgPacket( _handleToPtr(h)->midiH, pkt ); } unsigned cw::io::midiMsgCount( handle_t h, unsigned devIdx, unsigned portIdx ) { return midi::device::msgCount( _handleToPtr(h)->midiH, devIdx, portIdx ); } cw::rc_t cw::io::midiSeekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx ) { return midi::device::seekToMsg( _handleToPtr(h)->midiH, devIdx, portIdx, msgIdx ); } cw::rc_t cw::io::midiSetEndMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx ) { return midi::device::setEndMsg( _handleToPtr(h)->midiH, devIdx, portIdx, msgIdx ); } cw::rc_t cw::io::midiFileStart( handle_t h ) { return midi::device::start( _handleToPtr(h)->midiH ); } cw::rc_t cw::io::midiFileStop( handle_t h ) { return midi::device::stop( _handleToPtr(h)->midiH ); } cw::rc_t cw::io::midiFilePause( handle_t h, bool pauseFl ) { return midi::device::pause( _handleToPtr(h)->midiH, pauseFl ); } //---------------------------------------------------------------------------------------------------------- // // Audio // bool cw::io::audioIsEnabled( handle_t h ) { io_t* p = _handleToPtr(h); for(unsigned devIdx=0; devIdxaudioDevN; ++devIdx) { audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr && ad->activeFl ) return true; } return false; } unsigned cw::io::audioDeviceCount( handle_t h ) { io_t* p = _handleToPtr(h); return p->audioDevN; } unsigned cw::io::audioDeviceLabelToIndex( handle_t h, const char* label ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceLabelToRecd(p,label)) != nullptr ) return ad->devIdx; return kInvalidIdx; } const char* cw::io::audioDeviceLabel( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr ) return ad->label; return nullptr; } cw::rc_t cw::io::audioDeviceSetUserId( handle_t h, unsigned devIdx, unsigned userId ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr ) { ad->userId = userId; if( ad->iagd != nullptr ) ad->iagd->userId = userId; if( ad->oagd != nullptr ) ad->oagd->userId = userId; } return kInvalidArgRC; } bool cw::io::audioDeviceIsActive( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr ) return ad->activeFl; return false; } const char* cw::io::audioDeviceName( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr ) return nullptr; return ad->devName; } unsigned cw::io::audioDeviceUserId( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr ) return kInvalidId; return ad->userId; } cw::rc_t cw::io::audioDeviceEnable( handle_t h, unsigned devIdx, bool inputFl, bool enableFl ) { io_t* p = _handleToPtr(h); return audio::device::enable(p->audioH, devIdx, inputFl, enableFl ); } cw::rc_t cw::io::audioDeviceSeek( handle_t h, unsigned devIdx, bool inputFl, unsigned frameOffset ) { io_t* p = _handleToPtr(h); return audio::device::seek(p->audioH, devIdx, inputFl, frameOffset ); } double cw::io::audioDeviceSampleRate( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); return audio::device::sampleRate(p->audioH, devIdx ); } unsigned cw::io::audioDeviceFramesPerCycle( handle_t h, unsigned devIdx ) { io_t* p = _handleToPtr(h); audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p, devIdx )) == nullptr ) return audio::device::framesPerCycle(p->audioH, devIdx, ad->iGroup != nullptr ); return 0; } unsigned cw::io::audioDeviceChannelCount( handle_t h, unsigned devIdx, unsigned inOrOutFlag ) { io_t* p = _handleToPtr(h); return audio::device::channelCount(p->audioH, devIdx, inOrOutFlag & kInFl ); } cw::rc_t cw::io::audioDeviceEnableMeters( handle_t h, unsigned devIdx, unsigned inOutEnaFlags ) { io_t* p = _handleToPtr(h); return _audioDeviceEnableMeter(p, devIdx, inOutEnaFlags ); } const cw::io::sample_t* cw::io::audioDeviceMeters( handle_t h, unsigned devIdx, unsigned& chCntRef, unsigned inOrOutFlag ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); sample_t* meterA = nullptr; audioDev_t* ad; if((ad = _audioDeviceIndexToRecd(p,devIdx)) == nullptr ) rc = kInvalidArgRC; else { bool inputFl = inOrOutFlag & kInFl; audio_group_dev_t* agd = inputFl ? ad->iagd : ad->oagd; if( agd != nullptr ) { if( !cwIsFlag(agd->flags,kMeterFl) ) rc = cwLogError(kInvalidArgRC,"The %s meters on device %s are not enabled.", inputFl ? "input" : "output", cwStringNullGuard(ad->label)); else { meterA = agd->meterA; chCntRef = agd->chCnt; } } } if( rc != kOkRC ) rc = cwLogError(rc,"Get meters failed."); return meterA; } cw::rc_t cw::io::audioDeviceEnableTone( handle_t h, unsigned devIdx, unsigned inOutEnaFlags ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOutEnaFlags, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Enable tone failed."); else { audioBufFlags += audio::buf::kToneFl; audio::buf::setFlag( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags ); } return rc; } cw::rc_t cw::io::audioDeviceToneFlags( handle_t h, unsigned devIdx, unsigned inOrOutFlag, bool* toneFlA, unsigned chCnt ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Get tone flags failed."); else { audioBufFlags += audio::buf::kToneFl; audio::buf::toneFlags( p->audioBufH, devIdx, audioBufFlags, toneFlA, chCnt ); } return rc; } cw::rc_t cw::io::audioDeviceEnableMute( handle_t h, unsigned devIdx, unsigned inOutEnaFlags ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOutEnaFlags, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Enable mute failed."); else { audioBufFlags += audio::buf::kMuteFl; audio::buf::setFlag( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags ); } return rc; } cw::rc_t cw::io::audioDeviceMuteFlags( handle_t h, unsigned devIdx, unsigned inOrOutFlag, bool* muteFlA, unsigned chCnt ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Get mute flags failed."); else { audioBufFlags += audio::buf::kMuteFl; audio::buf::muteFlags( p->audioBufH, devIdx, audioBufFlags, muteFlA, chCnt ); } return rc; } cw::rc_t cw::io::audioDeviceSetGain( handle_t h, unsigned devIdx, unsigned inOrOutFlag, double gain ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Set gain failed."); else { audio::buf::setGain( p->audioBufH, devIdx, kInvalidIdx, audioBufFlags, gain ); } return rc; } cw::rc_t cw::io::audioDeviceGain( handle_t h, unsigned devIdx, unsigned inOrOutFlag, double* gainA, unsigned chCnt ) { rc_t rc = kOkRC; io_t* p = _handleToPtr(h); audioDev_t* ad = nullptr; unsigned audioBufFlags = 0; if((rc = _audioDeviceParams( p, devIdx, inOrOutFlag, ad, audioBufFlags )) != kOkRC ) rc = cwLogError(rc,"Get gain failed."); else { audioBufFlags += audio::buf::kMuteFl; audio::buf::gain( p->audioBufH, devIdx, audioBufFlags, gainA, chCnt ); } return rc; } unsigned cw::io::audioGroupCount( handle_t h ) { io_t* p = _handleToPtr(h); return p->audioGroupN; } unsigned cw::io::audioGroupLabelToIndex( handle_t h, const char* label ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromLabel(p,label)) == nullptr ) return kInvalidIdx; return ag->msg.groupIndex; } const char* cw::io::audioGroupLabel( handle_t h, unsigned groupIdx ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) return ag->msg.label; return nullptr; } bool cw::io::audioGroupIsEnabled( handle_t h, unsigned groupIdx ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) return ag->enableFl; return false; } unsigned cw::io::audioGroupUserId( handle_t h, unsigned groupIdx ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) return ag->msg.userId; return kInvalidIdx; } cw::rc_t cw::io::audioGroupSetUserId( handle_t h, unsigned groupIdx, unsigned userId ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) ag->msg.userId = userId; return kInvalidArgRC; } double cw::io::audioGroupSampleRate( handle_t h, unsigned groupIdx ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) return ag->msg.srate; return 0; } unsigned cw::io::audioGroupDspFrameCount( handle_t h, unsigned groupIdx ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) return ag->msg.dspFrameCnt; return 0; } cw::rc_t cw::io::audioGroupReconfigure( handle_t h, unsigned groupIdx, double srate, unsigned dspFrameN ) { rc_t rc = kOkRC; audioGroup_t* ag = nullptr; io_t* p = _handleToPtr(h); // locate the group record if((ag = _audioGroupFromIndex( p, groupIdx )) == nullptr ) goto errLabel; // if the parameters are not changing then there is nothing to do if( ag->msg.dspFrameCnt == dspFrameN && ag->msg.srate == srate ) goto errLabel; // change the parameters in the group record ag->msg.dspFrameCnt = dspFrameN; ag->msg.srate = srate; // stop the audio sub-system if((rc = _audioDeviceStartStop(p,false)) != kOkRC ) goto errLabel; // TODO: be sure the audio subsystem is really stopped // for each audio device for(unsigned i=0; iaudioDevN; ++i) { audioDev_t* ad = p->audioDevA + i; // if this devices in-group/out-group was reconfigured bool iGroupFl = ad->iGroup != nullptr && ad->iGroup->msg.groupIndex == groupIdx; bool oGroupFl = ad->oGroup != nullptr && ad->oGroup->msg.groupIndex == groupIdx; if( iGroupFl || oGroupFl ) { // reconfigure the device with the updated srate and framesPerCycle if((rc = _audioDeviceConfigure(p, ad, ad->oGroup, ad->oGroup, ad->cycleCnt, ad->framesPerCycle )) != kOkRC ) goto errLabel; cwLogInfo("The audio device: '%s' was reconfigured srate=%f dspFrameCnt:%i.",cwStringNullGuard(ad->label), srate,dspFrameN); } } // restart the audio sub-system if((rc = _audioDeviceStartStop(p,true)) != kOkRC ) goto errLabel; errLabel: if( rc != kOkRC ) rc = cwLogError(rc,"Audio group reconfiguration failed."); return rc; } unsigned cw::io::audioGroupDeviceCount( handle_t h, unsigned groupIdx, unsigned inOrOutFl ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); unsigned n = 0; if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) { audio_group_dev_t* agd = cwIsFlag(inOrOutFl,kInFl) ? ag->msg.iDevL : ag->msg.oDevL; for(; agd!=nullptr; agd=agd->link) ++n; } return n; } unsigned cw::io::audioGroupDeviceIndex( handle_t h, unsigned groupIdx, unsigned inOrOutFl, unsigned groupDevIdx ) { audioGroup_t* ag; io_t* p = _handleToPtr(h); unsigned n = 0; if((ag = _audioGroupFromIndex( p, groupIdx )) != nullptr ) { audio_group_dev_t* agd = cwIsFlag(inOrOutFl,kInFl) ? ag->msg.iDevL : ag->msg.oDevL; for(; agd!=nullptr; agd=agd->link) { if( n == groupDevIdx ) return agd->devIdx; ++n; } } cwLogError(kInvalidIdRC,"The audio group device index '%i' could found on group index: '%i' .",groupDevIdx,groupIdx); return kInvalidIdx; } //---------------------------------------------------------------------------------------------------------- // // Socket // bool cw::io::socketIsEnabled( handle_t h ) { io_t* p = _handleToPtr(h); return p->sockN != 0; } unsigned cw::io::socketCount( handle_t h ) { io_t* p = _handleToPtr(h); return p->sockN; } unsigned cw::io::socketLabelToIndex( handle_t h, const char* label ) { io_t* p = _handleToPtr(h); for(unsigned i=0; isockN; ++i) if( textCompare(p->sockA[i].label,label) == 0 ) return i; cwLogError(kInvalidArgRC,"'%s' is not a valid socket label.", cwStringNullGuard(label)); return kInvalidIdx; } unsigned cw::io::socketUserId( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return kInvalidId; return s->userId; } cw::rc_t cw::io::socketSetUserId( handle_t h, unsigned sockIdx, unsigned userId ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return kInvalidArgRC; s->userId = userId; return kOkRC; } const char* cw::io::socketLabel( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return nullptr; return s->label; } const char* cw::io::socketHostName( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return nullptr; return sock::hostName( p->sockH, s->userId ); } const char* cw::io::socketIpAddress( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return nullptr; return sock::ipAddress( p->sockH, s->userId ); } unsigned cw::io::socketInetAddress( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return 0; return sock::inetAddress( p->sockH, s->userId ); } cw::sock::portNumber_t cw::io::socketPort( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return sock::kInvalidPortNumber; return sock::port( p->sockH, s->userId ); } cw::rc_t cw::io::socketPeername( handle_t h, unsigned sockIdx, struct sockaddr_in* addr ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return kInvalidArgRC; return sock::peername( p->sockH, s->userId, addr ); } bool cw::io::socketIsConnected( handle_t h, unsigned sockIdx ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return sock::isConnected( p->sockH, s->userId ); return false; } cw::rc_t cw::io::socketSend( handle_t h, unsigned sockIdx, unsigned connId, const void* data, unsigned dataByteCnt ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return kInvalidArgRC; return sock::send(p->sockH, s->userId, connId, data, dataByteCnt ); } cw::rc_t cw::io::socketSend( handle_t h, unsigned sockIdx, const void* data, unsigned dataByteCnt, const struct sockaddr_in* remoteAddr ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return kInvalidArgRC; return sock::send(p->sockH, s->userId, data, dataByteCnt, remoteAddr ); } cw::rc_t cw::io::socketSend( handle_t h, unsigned sockIdx, const void* data, unsigned dataByteCnt, const char* remoteAddr, sock::portNumber_t remotePort ) { io_t* p = _handleToPtr(h); socket_t* s; if((s = _socketIndexToRecd(p,sockIdx)) == nullptr ) return kInvalidArgRC; return sock::send(p->sockH, s->userId, data, dataByteCnt, remoteAddr, remotePort ); } //---------------------------------------------------------------------------------------------------------- // // WebSocket // //---------------------------------------------------------------------------------------------------------- // // UI // bool cw::io::uiIsEnabled( handle_t h ) { io_t* p = _handleToPtr(h); return p->wsUiH.isValid(); } unsigned cw::io::parentAndNameToAppId( handle_t h, unsigned parentAppId, const char* eleName ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::parentAndNameToAppId(uiH,parentAppId,eleName); return kInvalidId; } unsigned cw::io::parentAndNameToUuId( handle_t h, unsigned parentAppId, const char* eleName ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::parentAndNameToUuId(uiH,parentAppId,eleName); return kInvalidId; } unsigned cw::io::parentAndAppIdToUuId( handle_t h, unsigned parentAppId, unsigned appId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::parentAndAppIdToUuId(uiH,parentAppId,appId); return kInvalidId; } 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, unsigned chanId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::findElementUuId(uiH, eleName, chanId ); return kInvalidId; } unsigned cw::io::uiFindElementUuId( handle_t h, unsigned appId, unsigned chanId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::findElementUuId(uiH, kInvalidId, appId, chanId ); return kInvalidId; } unsigned cw::io::uiFindElementUuId( handle_t h, unsigned parentUuId, const char* eleName, unsigned chanId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::findElementUuId(uiH, parentUuId, eleName, chanId ); return kInvalidId; } unsigned cw::io::uiFindElementUuId( handle_t h, unsigned parentUuId, unsigned appId, unsigned chanId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::findElementUuId(uiH, parentUuId, appId, chanId ); return kInvalidId; } cw::rc_t cw::io::uiCreateFromObject( handle_t h, const object_t* o, unsigned parentUuId, unsigned chanId, const char* eleName) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createFromObject(uiH,o,parentUuId,chanId,eleName); return rc; } cw::rc_t cw::io::uiCreateFromFile( handle_t h, const char* fn, unsigned parentUuId, unsigned chanId) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createFromFile(uiH,fn,parentUuId,chanId); return rc; } cw::rc_t cw::io::uiCreateFromText( handle_t h, const char* text, unsigned parentUuId, unsigned chanId) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createFromText(uiH,text,parentUuId, chanId); return rc; } cw::rc_t cw::io::uiCreateFromRsrc( handle_t h, const char* label, unsigned parentUuId, unsigned chanId) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createFromRsrc(uiH,label,parentUuId, chanId); return rc; } cw::rc_t cw::io::uiCreateDiv( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createDiv(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateLabel( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createLabel(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateButton( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createButton(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateCheck( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createCheck(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateCheck( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,value); return rc; } cw::rc_t cw::io::uiCreateSelect( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createSelect(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateOption( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createOption(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateStrDisplay( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createStrDisplay(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateStrDisplay( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title, const char* value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createStrDisplay(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title,value); return rc; } cw::rc_t cw::io::uiCreateStr( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createStr(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateStr( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,value); return rc; } cw::rc_t cw::io::uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,decPl); return rc; } cw::rc_t cw::io::uiCreateNumbDisplay(handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,value); return rc; } cw::rc_t cw::io::uiCreateNumb( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue,stepValue,decPl); return rc; } cw::rc_t cw::io::uiCreateNumb( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue,stepValue,decPl,value); return rc; } cw::rc_t cw::io::uiCreateProg( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue); return rc; } cw::rc_t cw::io::uiCreateProg( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, 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,parentUuId,eleName,appId,chanId,clas,title,minValue,maxValue,value); return rc; } cw::rc_t cw::io::uiCreateLog( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createLog(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateVList( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createVList(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiCreateHList( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::createHList(uiH,uuIdRef,parentUuId,eleName,appId,chanId,clas,title); return rc; } cw::rc_t cw::io::uiSetNumbRange( handle_t h, unsigned uuId, 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::setNumbRange(uiH,uuId,minValue,maxValue,stepValue,decPl,value); return rc; } cw::rc_t cw::io::uiSetProgRange( handle_t h, unsigned uuId, double minValue, double maxValue, double value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setProgRange(uiH,uuId,minValue,maxValue,value); return rc; } cw::rc_t cw::io::uiSetLogLine( handle_t h, unsigned uuId, const char* text ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setLogLine(uiH,uuId,text); return rc; } cw::rc_t cw::io::uiEmptyParent( handle_t h, unsigned uuId) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::emptyParent(uiH,uuId); return rc; } cw::rc_t cw::io::uiSetClickable( handle_t h, unsigned uuId, bool clickableFl ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setClickable(uiH,uuId,clickableFl); return rc; } cw::rc_t cw::io::uiClearClickable( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::clearClickable(uiH,uuId); return rc; } bool cw::io::uiIsClickable( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::isClickable(uiH,uuId); return false; } cw::rc_t cw::io::uiSetSelect( handle_t h, unsigned uuId, bool enableFl ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setSelect(uiH,uuId,enableFl); return rc; } cw::rc_t cw::io::uiClearSelect( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::clearSelect(uiH,uuId); return rc; } bool cw::io::uiIsSelected( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::isSelected(uiH,uuId); return false; } cw::rc_t cw::io::uiSetVisible( handle_t h, unsigned uuId, bool enableFl ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setVisible(uiH,uuId,enableFl); return rc; } cw::rc_t cw::io::uiClearVisible( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::clearVisible(uiH,uuId); return rc; } bool cw::io::uiIsVisible( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::isVisible(uiH,uuId); return false; } cw::rc_t cw::io::uiSetEnable( handle_t h, unsigned uuId, bool enableFl ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setEnable(uiH,uuId,enableFl); return rc; } cw::rc_t cw::io::uiClearEnable( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::clearEnable(uiH,uuId); return rc; } bool cw::io::uiIsEnabled( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) return ui::isEnabled(uiH,uuId); return false; } cw::rc_t cw::io::uiSetOrderKey( handle_t h, unsigned uuId, int orderKey ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setOrderKey(uiH,uuId,orderKey); return rc; } int cw::io::uiGetOrderKey( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; int orderKey = 0; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) orderKey = ui::getOrderKey(uiH,uuId); return orderKey; } cw::rc_t cw::io::uiSetScrollTop( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setScrollTop(uiH,uuId); return rc; } cw::rc_t cw::io::uiSetTitle( handle_t h, unsigned uuId, const char* title ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setTitle(uiH,uuId,title); return rc; } cw::rc_t cw::io::uiSetBlob( handle_t h, unsigned uuId, const void* blob, unsigned blobByteN ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::setBlob( uiH, uuId, blob, blobByteN ); return rc; } const void* cw::io::uiGetBlob( handle_t h, unsigned uuId, unsigned& blobByteN_Ref ) { ui::handle_t uiH; if( _handleToUiHandle(h,uiH) == kOkRC ) return ui::getBlob( uiH, uuId, blobByteN_Ref ); blobByteN_Ref = 0; return nullptr; } cw::rc_t cw::io::uiGetBlob( handle_t h, unsigned uuId, void* buf, unsigned& bufByteN_Ref ) { unsigned bN = 0; const void* b = uiGetBlob(h,uuId,bN); if( bN > bufByteN_Ref ) { bufByteN_Ref = 0; return cwLogError(kBufTooSmallRC,"UI blob buffer is too small."); } memcpy(buf,b,bN); bufByteN_Ref = bN; return kOkRC; } cw::rc_t cw::io::uiClearBlob( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::clearBlob( uiH, uuId ); 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; } cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, bool value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendValueBool(uiH, uuId, value ); return rc; } cw::rc_t cw::io::uiDestroyElement( handle_t h, unsigned uuId ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::destroyElement(uiH, uuId ); return rc; } cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, int value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendValueInt(uiH, uuId, value ); return rc; } cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, unsigned value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendValueUInt(uiH, uuId, value ); return rc; } cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, float value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendValueFloat(uiH, uuId, value ); return rc; } cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, double value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendValueDouble(uiH, uuId, value ); return rc; } cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, const char* value ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendValueString(uiH, uuId, value ); return rc; } cw::rc_t cw::io::uiSendMsg( handle_t h, const char* msg ) { rc_t rc; ui::handle_t uiH; if((rc = _handleToUiHandle(h,uiH)) == kOkRC ) rc = ui::sendMsg(uiH, msg); return rc; } void cw::io::latency_measure_setup(handle_t h) { io_t* p = _handleToPtr(h); p->latency_meas_enable_fl = true; p->latency_meas_thresh_db = -50; p->latency_meas_thresh_lin = pow(10.0,p->latency_meas_thresh_db/20.0); p->latency_meas_result.note_on_input_ts = {}; p->latency_meas_result.note_on_output_ts = {}; p->latency_meas_result.audio_in_ts = {}; p->latency_meas_result.audio_out_ts = {}; p->latency_meas_result.audio_in_rms_max = 0; p->latency_meas_result.audio_out_rms_max = 0; if( p->midiH.isValid() ) latency_measure_reset(p->midiH); } cw::io::latency_meas_result_t cw::io::latency_measure_result(handle_t h) { io_t* p = _handleToPtr(h); if( p->midiH.isValid() ) { midi::device::latency_meas_combined_result_t r = latency_measure_result(p->midiH); p->latency_meas_result.note_on_input_ts = r.alsa_dev.note_on_input_ts; p->latency_meas_result.note_on_output_ts = r.alsa_dev.note_on_output_ts; } return p->latency_meas_result; } void cw::io::latency_measure_report(handle_t h) { io_t* p = _handleToPtr(h); latency_meas_result_t r = latency_measure_result(h); unsigned t0,t1; if( r.note_on_input_ts.tv_nsec ) { if( r.note_on_output_ts.tv_nsec ) { t0 = time::elapsedMicros(r.note_on_input_ts,r.note_on_output_ts); printf("midi in - midi out: %6i\n",t0); } if( r.audio_in_ts.tv_nsec ) { t0 = time::elapsedMicros(r.note_on_output_ts,r.audio_in_ts); t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_in_ts); printf("midi out - audio in: %6i %6i\n",t0,t1); } if( r.audio_out_ts.tv_nsec ) { t0 = time::elapsedMicros(r.audio_in_ts,r.audio_out_ts); t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_out_ts); printf("audio in - audio out: %6i %6i\n",t0,t1); } } printf("Thresh: %f %f db\n",p->latency_meas_thresh_db,p->latency_meas_thresh_lin); if( r.audio_in_rms_max != 0 ) printf("audio in max:%f %f dB\n", r.audio_in_rms_max, 20.0*log10(r.audio_in_rms_max)); if( r.audio_out_rms_max != 0 ) printf("audio out max:%f %f dB\n", r.audio_out_rms_max, 20.0*log10(r.audio_out_rms_max)); } void cw::io::uiReport( handle_t h ) { ui::handle_t uiH; if(_handleToUiHandle(h,uiH) == kOkRC ) ui::report(uiH); } void cw::io::uiRealTimeReport( handle_t h ) { ui::ws::handle_t uiH; if(_handleToWsUiHandle(h,uiH) == kOkRC ) ui::ws::realTimeReport(uiH); }