diff --git a/Makefile b/Makefile index 8541a99..3f50cb4 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,32 @@ HDR = cwCommon.h cwCommonImpl.h cwMem.h cwLog.h SRC = main.cpp cwCommonImpl.cpp cwMem.cpp cwLog.cpp -HDR += cwFileSys.h cwText.h cwFile.h cwLex.h cwNumericConvert.h -SRC += cwFileSys.cpp cwText.cpp cwFile.cpp cwLex.cpp +HDR += cwFileSys.h cwText.h cwFile.h cwTime.h cwLex.h cwNumericConvert.h +SRC += cwFileSys.cpp cwText.cpp cwFile.cpp cwTime.cpp cwLex.cpp -HDR += cwObject.h cwTextBuf.h cwThread.h +HDR += cwObject.h cwTextBuf.h cwThread.h cwMpScNbQueue.h SRC += cwObject.cpp cwTextBuf.cpp cwThread.cpp +HDR += cwWebSock.h cwWebSockSvr.h +SRC += cwWebSock.cpp cwWebSockSvr.cpp -LIBS = -lpthread +HDR += cwSerialPort.h cwSerialPortSrv.h +SRC += cwSerialPort.cpp cwSerialPortSrv.cpp +HDR += cwMidi.h cwMidiPort.h +SRC += cwMidi.cpp cwMidiPort.cpp cwMidiAlsa.cpp + +HDR += cwAudioBuf.h cwAudioPort.h cwAudioPortAlsa.h +SRC += cwAudioBuf.cpp cwAudioPort.cpp cwAudioPortAlsa.cpp + + +LIBS = -lpthread -lwebsockets -lasound + +WS_DIR = /home/kevin/sdk/libwebsockets/build/out +INC_PATH = $(WS_DIR)/include +LIB_PATH = $(WS_DIR)/lib cw_rt : $(SRC) $(HDR) - g++ -g --std=c++17 $(LIBS) -Wall -DcwLINUX -o $@ $(SRC) + g++ -g --std=c++17 $(LIBS) -Wall -L$(LIB_PATH) -I$(INC_PATH) -DcwLINUX -o $@ $(SRC) diff --git a/README.md b/README.md index a959260..a248a79 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ +- Clean up the cwObject namespace - add an 'object' namespace inside 'cw' +- Add underscore to the member variables of object_t. + +- - logDefaultFormatter() in cwLog.cpp uses stack allocated memory in a way that could easily be exploited. - lexIntMatcher() in cwLex.cpp doesn't handle 'e' notation correctly. See note in code. -- numeric_convert() in cwNumericConvert.h could be made more efficient using type_traits. \ No newline at end of file +- numeric_convert() in cwNumericConvert.h could be made more efficient using type_traits. + +- thread needs setters and getters for internal variables + +- change cwMpScNbQueue so that it does not require 'new'. + + diff --git a/cwAudioBuf.cpp b/cwAudioBuf.cpp new file mode 100644 index 0000000..cc29f14 --- /dev/null +++ b/cwAudioBuf.cpp @@ -0,0 +1,991 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwTime.h" +#include "cwTextBuf.h" +#include "cwAudioPort.h" +#include "cwAudioBuf.h" + +/* + This API is called by two types of threads: + audio device threads and the client thread. There + may be multiple device threads, however, there is only + one client thread. + + The audio device threads only call update(). + update() is never called by any other threads. + A call from the audio update threads targets specific channels + (cmApCh records). The variables within each channels that + it modifies are confined to: + on input channels: increments ii and increments fn (data is entering the ch. buffers) + on output channels: increments oi and decrements fn (data is leaving the ch. buffers) + + The client picks up incoming audio and provides outgoing audio via + get(). It then informs the buf that it has completed + the audio data transfer by calling advance(). + + advance() modifies the following internal variables: + on input channels: increments oi and decrements fn (data has left the ch buffer) + on output channels: increments ii and increments fn (data has enterned the ch. buffer) + + Based on the above scenario the channel ii and oi variables are always thread-safe + because they are only changed by a single thread. + + ii oi fn + ------ ----- ---- + input ch: audio client both + output ch: client audio both + + The fn variable however is not thread-safe and therefore care must be taken as + to how it is read and updated. + + + +*/ + +namespace cw +{ + namespace audio + { + namespace buf + { + + enum { kInApIdx=0, kOutApIdx=1, kIoApCnt=2 }; + + typedef struct + { + unsigned fl; // kChFl|kToneFl|kMeterFl ... + sample_t* b; // b[n] + unsigned ii; // next in + unsigned oi; // next out + std::atomic fn; // full cnt - count of samples currently in the buffer - incr'd by incoming, decr'd by outgoing + unsigned phs; // tone phase + double hz; // tone frequency + double gain; // channel gain + sample_t* m; // m[mn] meter sample sum + unsigned mn; // length of m[] + unsigned mi; // next ele of m[] to rcv sum + } cmApCh; + + typedef struct + { + unsigned chCnt; + cmApCh* chArray; + unsigned n; // length of b[] (multiple of dspFrameCnt) bufCnt*framesPerCycle + double srate; // device sample rate; + unsigned faultCnt; + unsigned framesPerCycle; + unsigned dspFrameCnt; + time::spec_t timeStamp; // base (starting) time stamp for this device + std::atomic ioFrameCnt; // count of frames input or output for this device + + } cmApIO; + + typedef struct + { + // ioArray[] always contains 2 elements - one for input the other for output. + cmApIO ioArray[kIoApCnt]; + } cmApDev; + + typedef struct + { + cmApDev* devArray; + unsigned devCnt; + unsigned meterMs; + + sample_t* zeroBuf; // buffer of zeros + unsigned zeroBufCnt; // max of all dspFrameCnt for all devices. + } cmApBuf; + + cmApBuf _theBuf; + + + sample_t _cmApMeterValue( const cmApCh* cp ) + { + double sum = 0; + unsigned i; + for(i=0; imn; ++i) + sum += cp->m[i]; + + return cp->mn==0 ? 0 : (sample_t)sqrt(sum/cp->mn); + } + + void _cmApSine( cmApCh* cp, sample_t* b0, unsigned n0, sample_t* b1, unsigned n1, unsigned stride, float srate ) + { + unsigned i; + + for(i=0; iphs) + b0[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate )); + + for(i=0; iphs) + b1[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate )); + } + + sample_t _cmApMeter( const sample_t* b, unsigned bn, unsigned stride ) + { + const sample_t* ep = b + bn; + sample_t sum = 0; + + for(; bb ); + memRelease( chPtr->m ); + } + + // n=buf sample cnt mn=meter buf smp cnt + void _cmApChInitialize( cmApCh* chPtr, unsigned n, unsigned mn ) + { + _cmApChFinalize(chPtr); + + chPtr->b = n==0 ? NULL : memAllocZ( n ); + chPtr->ii = 0; + chPtr->oi = 0; + chPtr->fn = 0; + chPtr->fl = (n!=0 ? kChFl : 0); + chPtr->hz = 1000; + chPtr->gain = 1.0; + chPtr->mn = mn; + chPtr->m = memAllocZ(mn); + chPtr->mi = 0; + } + + void _cmApIoFinalize( cmApIO* ioPtr ) + { + unsigned i; + for(i=0; ichCnt; ++i) + _cmApChFinalize( ioPtr->chArray + i ); + + memRelease(ioPtr->chArray); + ioPtr->chCnt = 0; + ioPtr->n = 0; + } + + void _cmApIoInitialize( cmApIO* ioPtr, double srate, unsigned framesPerCycle, unsigned chCnt, unsigned n, unsigned meterBufN, unsigned dspFrameCnt ) + { + unsigned i; + + _cmApIoFinalize(ioPtr); + + n += (n % dspFrameCnt); // force buffer size to be a multiple of dspFrameCnt + + ioPtr->chArray = chCnt==0 ? NULL : memAllocZ(chCnt ); + ioPtr->chCnt = chCnt; + ioPtr->n = n; + ioPtr->faultCnt = 0; + ioPtr->framesPerCycle = framesPerCycle; + ioPtr->srate = srate; + ioPtr->dspFrameCnt = dspFrameCnt; + ioPtr->timeStamp.tv_sec = 0; + ioPtr->timeStamp.tv_nsec = 0; + ioPtr->ioFrameCnt = 0; + + for(i=0; ichArray + i, n, meterBufN ); + + } + + void _cmApDevFinalize( cmApDev* dp ) + { + unsigned i; + for(i=0; iioArray+i); + } + + void _cmApDevInitialize( cmApDev* dp, double srate, unsigned iFpC, unsigned iChCnt, unsigned iBufN, unsigned oFpC, unsigned oChCnt, unsigned oBufN, unsigned meterBufN, unsigned dspFrameCnt ) + { + unsigned i; + + _cmApDevFinalize(dp); + + for(i=0; iioArray+i, srate, fpc, chCnt, bufN, meterBufN, dspFrameCnt ); + + } + + } + +void _theBufCalcTimeStamp( double srate, const time::spec_t* baseTimeStamp, unsigned frmCnt, time::spec_t* retTimeStamp ) +{ + if( retTimeStamp==NULL ) + return; + + double secs = frmCnt / srate; + unsigned int_secs = floor(secs); + double frac_secs = secs - int_secs; + + retTimeStamp->tv_nsec = floor(baseTimeStamp->tv_nsec + frac_secs * 1000000000); + retTimeStamp->tv_sec = baseTimeStamp->tv_sec + int_secs; + + if( retTimeStamp->tv_nsec > 1000000000 ) + { + retTimeStamp->tv_nsec -= 1000000000; + retTimeStamp->tv_sec += 1; + } +} + + + } + } +} + +cw::rc_t cw::audio::buf::initialize( unsigned devCnt, unsigned meterMs ) +{ + rc_t rc; + + if((rc = finalize()) != kOkRC ) + return rc; + + _theBuf.devArray = memAllocZ(devCnt ); + _theBuf.devCnt = devCnt; + setMeterMs(meterMs); + return kOkRC; +} + +cw::rc_t cw::audio::buf::finalize() +{ + unsigned i; + for(i=0; i<_theBuf.devCnt; ++i) + _cmApDevFinalize(_theBuf.devArray + i); + + memRelease( _theBuf.devArray ); + memRelease( _theBuf.zeroBuf ); + + _theBuf.devCnt = 0; + + return kOkRC; +} + +cw::rc_t cw::audio::buf::setup( + unsigned devIdx, + double srate, + unsigned dspFrameCnt, + unsigned bufCnt, + unsigned inChCnt, + unsigned inFramesPerCycle, + unsigned outChCnt, + unsigned outFramesPerCycle) +{ + cmApDev* devPtr = _theBuf.devArray + devIdx; + unsigned iBufN = bufCnt * inFramesPerCycle; + unsigned oBufN = bufCnt * outFramesPerCycle; + unsigned meterBufN = std::max(1.0,floor(srate * _theBuf.meterMs / (1000.0 * outFramesPerCycle))); + + _cmApDevInitialize( devPtr, srate, inFramesPerCycle, inChCnt, iBufN, outFramesPerCycle, outChCnt, oBufN, meterBufN, dspFrameCnt ); + + if( inFramesPerCycle > _theBuf.zeroBufCnt || outFramesPerCycle > _theBuf.zeroBufCnt ) + { + _theBuf.zeroBufCnt = std::max(inFramesPerCycle,outFramesPerCycle); + _theBuf.zeroBuf = memResizeZ(_theBuf.zeroBuf,_theBuf.zeroBufCnt); + } + + return kOkRC; +} + +cw::rc_t cw::audio::buf::primeOutput( unsigned devIdx, unsigned audioCycleCnt ) +{ + cmApIO* iop = _theBuf.devArray[devIdx].ioArray + kOutApIdx; + unsigned i; + + for(i=0; ichCnt; ++i) + { + cmApCh* cp = iop->chArray + i; + unsigned bn = iop->n * sizeof(sample_t); + memset(cp->b,0,bn); + cp->oi = 0; + cp->ii = iop->framesPerCycle * audioCycleCnt; + cp->fn = iop->framesPerCycle * audioCycleCnt; + } + + return kOkRC; +} + +void cw::audio::buf::onPortEnable( unsigned devIdx, bool enableFl ) +{ + if( devIdx == kInvalidIdx || enableFl==false) + return; + + cmApIO* iop = _theBuf.devArray[devIdx].ioArray + kOutApIdx; + iop->timeStamp.tv_sec = 0; + iop->timeStamp.tv_nsec = 0; + iop->ioFrameCnt = 0; + + iop = _theBuf.devArray[devIdx].ioArray + kInApIdx; + iop->timeStamp.tv_sec = 0; + iop->timeStamp.tv_nsec = 0; + iop->ioFrameCnt = 0; + + +} + +cw::rc_t cw::audio::buf::update( + device::audioPacket_t* inPktArray, + unsigned inPktCnt, + device::audioPacket_t* outPktArray, + unsigned outPktCnt ) +{ + unsigned i,j; + + // copy samples from the packet to the buffer + if( inPktArray != NULL ) + { + for(i=0; idevIdx].ioArray + kInApIdx; // dest io recd + + // if the base time stamp has not yet been set - then set it + if( ip->timeStamp.tv_sec==0 && ip->timeStamp.tv_nsec==0 ) + ip->timeStamp = pp->timeStamp; + + // for each source packet channel and enabled dest channel + for(j=0; jchCnt; ++j) + { + cmApCh* cp = ip->chArray + pp->begChIdx +j; // dest ch + unsigned n0 = ip->n - cp->ii; // first dest segment + unsigned n1 = 0; // second dest segment + + cwAssert(pp->begChIdx + j < ip->chCnt ); + + // if the incoming samples would overflow the buffer then ignore them + if( cp->fn + pp->audioFramesCnt > ip->n ) + { + ++ip->faultCnt; // record input overflow + continue; + } + + // if the incoming samples would go off the end of the buffer then + // copy in the samples in two segments (one at the end and another at begin of dest channel) + if( n0 < pp->audioFramesCnt ) + n1 = pp->audioFramesCnt-n0; + else + n0 = pp->audioFramesCnt; + + bool enaFl = cwIsFlag(cp->fl,kChFl) && cwIsFlag(cp->fl,kMuteFl)==false; + const sample_t* sp = enaFl ? ((sample_t*)pp->audioBytesPtr) + j : _theBuf.zeroBuf; + unsigned ssn = enaFl ? pp->chCnt : 1; // stride (packet samples are interleaved) + sample_t* dp = cp->b + cp->ii; + const sample_t* ep = dp + n0; + + + // update the meter + if( cwIsFlag(cp->fl,kMeterFl) ) + { + cp->m[cp->mi] = _cmApMeter(sp,pp->audioFramesCnt,pp->chCnt); + cp->mi = (cp->mi + 1) % cp->mn; + } + + // if the test tone is enabled on this input channel + if( enaFl && cwIsFlag(cp->fl,kToneFl) ) + { + _cmApSine(cp, dp, n0, cp->b, n1, 1, ip->srate ); + } + else // otherwise copy samples from the packet to the buffer + { + // copy the first segment + for(; dp < ep; sp += ssn ) + *dp++ = cp->gain * *sp; + + // if there is a second segment + if( n1 > 0 ) + { + // copy the second segment + dp = cp->b; + ep = dp + n1; + for(; dpgain * *sp; + } + } + + + // advance the input channel buffer + cp->ii = n1>0 ? n1 : cp->ii + n0; + //cp->fn += pp->audioFramesCnt; + //cmThUIntIncr(&cp->fn,pp->audioFramesCnt); + std::atomic_fetch_add(&cp->fn,pp->audioFramesCnt); + + } + } + } + + // copy samples from the buffer to the packet + if( outPktArray != NULL ) + { + for(i=0; idevIdx].ioArray + kOutApIdx; // dest io recd + + // if the base timestamp has not yet been set then set it. + if( op->timeStamp.tv_sec==0 && op->timeStamp.tv_nsec==0 ) + op->timeStamp = pp->timeStamp; + + // for each dest packet channel and enabled source channel + for(j=0; jchCnt; ++j) + { + cmApCh* cp = op->chArray + pp->begChIdx + j; // dest ch + unsigned n0 = op->n - cp->oi; // first src segment + unsigned n1 = 0; // second src segment + volatile unsigned fn = cp->fn; // store fn because it may be changed by the client thread + + // if the outgoing samples will underflow the buffer + if( pp->audioFramesCnt > fn ) + { + ++op->faultCnt; // record an output underflow + + // if the buffer is empty - zero the packet and return + if( fn == 0 ) + { + memset( pp->audioBytesPtr, 0, pp->audioFramesCnt*sizeof(sample_t)); + continue; + } + + // ... otherwise decrease the count of returned samples + pp->audioFramesCnt = fn; + + } + + // if the outgong segments would go off the end of the buffer then + // arrange to wrap to the begining of the buffer + if( n0 < pp->audioFramesCnt ) + n1 = pp->audioFramesCnt-n0; + else + n0 = pp->audioFramesCnt; + + sample_t* dp = ((sample_t*)pp->audioBytesPtr) + j; + bool enaFl = cwIsFlag(cp->fl,kChFl) && cwIsFlag(cp->fl,kMuteFl)==false; + + // if the tone is enabled on this channel + if( enaFl && cwIsFlag(cp->fl,kToneFl) ) + { + _cmApSine(cp, dp, n0, dp + n0*pp->chCnt, n1, pp->chCnt, op->srate ); + } + else // otherwise copy samples from the output buffer to the packet + { + const sample_t* sp = enaFl ? cp->b + cp->oi : _theBuf.zeroBuf; + const sample_t* ep = sp + n0; + + // copy the first segment + for(; sp < ep; dp += pp->chCnt ) + *dp = cp->gain * *sp++; + + // if there is a second segment + if( n1 > 0 ) + { + // copy the second segment + sp = enaFl ? cp->b : _theBuf.zeroBuf; + ep = sp + n1; + for(; spchCnt ) + *dp = cp->gain * *sp++; + + } + } + + // update the meter + if( cwIsFlag(cp->fl,kMeterFl) ) + { + cp->m[cp->mi] = _cmApMeter(((sample_t*)pp->audioBytesPtr)+j,pp->audioFramesCnt,pp->chCnt); + cp->mi = (cp->mi + 1) % cp->mn; + } + + // advance the output channel buffer + cp->oi = n1>0 ? n1 : cp->oi + n0; + //cp->fn -= pp->audioFramesCnt; + //cmThUIntDecr(&cp->fn,pp->audioFramesCnt); + std::atomic_fetch_sub(&cp->fn,pp->audioFramesCnt); + + } + } + } + return kOkRC; +} + +unsigned cw::audio::buf::meterMs() +{ return _theBuf.meterMs; } + +void cw::audio::buf::setMeterMs( unsigned meterMs ) +{ _theBuf.meterMs = std::min(1000u,std::max(10u,meterMs)); } + +unsigned cw::audio::buf::channelCount( unsigned devIdx, unsigned flags ) +{ + if( devIdx == kInvalidIdx ) + return 0; + + unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx; + return _theBuf.devArray[devIdx].ioArray[ idx ].chCnt; +} + +void cw::audio::buf::setFlag( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ + if( devIdx == kInvalidIdx ) + return; + + unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx; + bool enableFl = flags & kEnableFl ? true : false; + unsigned i = chIdx != kInvalidIdx ? chIdx : 0; + unsigned n = chIdx != kInvalidIdx ? chIdx+1 : _theBuf.devArray[devIdx].ioArray[idx].chCnt; + + for(; ifl = cwEnaFlag(cp->fl, flags & (kChFl|kToneFl|kMeterFl|kMuteFl|kPassFl), enableFl ); + } + +} + +bool cw::audio::buf::isFlag( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ + if( devIdx == kInvalidIdx ) + return false; + + unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx; + return cwIsFlag(_theBuf.devArray[devIdx].ioArray[idx].chArray[chIdx].fl,flags); +} + + +void cw::audio::buf::enableChannel( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ setFlag(devIdx,chIdx,flags | kChFl); } + +bool cw::audio::buf::isChannelEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return isFlag(devIdx, chIdx, flags | kChFl); } + +void cw::audio::buf::enableTone( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ setFlag(devIdx,chIdx,flags | kToneFl); } + +bool cw::audio::buf::isToneEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return isFlag(devIdx,chIdx,flags | kToneFl); } + +void cw::audio::buf::enableMute( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ setFlag(devIdx,chIdx,flags | kMuteFl); } + +bool cw::audio::buf::isMuteEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return isFlag(devIdx,chIdx,flags | kMuteFl); } + +void cw::audio::buf::enablePass( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ setFlag(devIdx,chIdx,flags | kPassFl); } + +bool cw::audio::buf::isPassEnabled( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return isFlag(devIdx,chIdx,flags | kPassFl); } + +void cw::audio::buf::enableMeter( unsigned devIdx, unsigned chIdx, unsigned flags ) +{ setFlag(devIdx,chIdx,flags | kMeterFl); } + +bool cw::audio::buf::isMeterEnabled(unsigned devIdx, unsigned chIdx, unsigned flags ) +{ return isFlag(devIdx,chIdx,flags | kMeterFl); } + +cw::audio::buf::sample_t cw::audio::buf::meter(unsigned devIdx, unsigned chIdx, unsigned flags ) +{ + if( devIdx == kInvalidIdx ) + return 0; + + unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx; + const cmApCh* cp = _theBuf.devArray[devIdx].ioArray[idx].chArray + chIdx; + return _cmApMeterValue(cp); +} + +void cw::audio::buf::setGain( unsigned devIdx, unsigned chIdx, unsigned flags, double gain ) +{ + if( devIdx == kInvalidIdx ) + return; + + unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx; + unsigned i = chIdx != kInvalidIdx ? chIdx : 0; + unsigned n = i + (chIdx != kInvalidIdx ? 1 : _theBuf.devArray[devIdx].ioArray[idx].chCnt); + + for(; ichCnt, meterCnt ); + unsigned i; + + if( faultCntPtr != NULL ) + *faultCntPtr = iop->faultCnt; + + for(i=0; ichArray + i); + return chCnt; +} + + +bool cw::audio::buf::isDeviceReady( unsigned devIdx, unsigned flags ) +{ + //bool iFl = true; + //bool oFl = true; + unsigned i = 0; + + if( devIdx == kInvalidIdx ) + return false; + + if( flags & kInFl ) + { + const cmApIO* ioPtr = _theBuf.devArray[devIdx].ioArray + kInApIdx; + for(i=0; ichCnt; ++i) + if( ioPtr->chArray[i].fn < ioPtr->dspFrameCnt ) + return false; + + //iFl = ioPtr->fn > ioPtr->dspFrameCnt; + } + + if( flags & kOutFl ) + { + const cmApIO* ioPtr = _theBuf.devArray[devIdx].ioArray + kOutApIdx; + + for(i=0; ichCnt; ++i) + if( (ioPtr->n - ioPtr->chArray[i].fn) < ioPtr->dspFrameCnt ) + return false; + + + //oFl = (ioPtr->n - ioPtr->fn) > ioPtr->dspFrameCnt; + } + + return true; + //return iFl & oFl; +} + + +// Note that his function returns audio samples but does NOT +// change any internal states. +void cw::audio::buf::get( unsigned devIdx, unsigned flags, sample_t* bufArray[], unsigned bufChCnt ) +{ + unsigned i; + if( devIdx == kInvalidIdx ) + { + for(i=0; ichCnt ? bufChCnt : ioPtr->chCnt; + //unsigned offs = flags & kInFl ? ioPtr->oi : ioPtr->ii; + cmApCh* cp = ioPtr->chArray; + + for(i=0; ioi : cp->ii; + bufArray[i] = cwIsFlag(cp->fl,kChFl) ? cp->b + offs : NULL; + } + +} + +void cw::audio::buf::getIO( unsigned iDevIdx, sample_t* iBufArray[], unsigned iBufChCnt, time::spec_t* iTimeStampPtr, unsigned oDevIdx, sample_t* oBufArray[], unsigned oBufChCnt, time::spec_t* oTimeStampPtr ) +{ + get( iDevIdx, kInFl, iBufArray, iBufChCnt ); + get( oDevIdx, kOutFl,oBufArray, oBufChCnt ); + + unsigned i = 0; + + if( iDevIdx != kInvalidIdx && oDevIdx != kInvalidIdx ) + { + const cmApIO* ip = _theBuf.devArray[iDevIdx].ioArray + kInApIdx; + const cmApIO* op = _theBuf.devArray[oDevIdx].ioArray + kOutApIdx; + unsigned minChCnt = std::min(iBufChCnt,oBufChCnt); + unsigned frmCnt = std::min(ip->dspFrameCnt,op->dspFrameCnt); + unsigned byteCnt = frmCnt * sizeof(sample_t); + + _theBufCalcTimeStamp(ip->srate, &ip->timeStamp, ip->ioFrameCnt, iTimeStampPtr ); + _theBufCalcTimeStamp(op->srate, &op->timeStamp, op->ioFrameCnt, oTimeStampPtr ); + + for(i=0; ichArray + i; + cmApCh* icp = ip->chArray + i; + + if( oBufArray[i] != NULL ) + { + // if either the input or output channel is marked for pass-through + if( cwAllFlags(ocp->fl,kPassFl) || cwAllFlags(icp->fl,kPassFl) ) + { + memcpy( oBufArray[i], iBufArray[i], byteCnt ); + + // set the output buffer to NULL to prevent it being over written by the client + oBufArray[i] = NULL; + } + else + { + // zero the output buffer + memset(oBufArray[i],0,byteCnt); + } + } + } + } + + if( oDevIdx != kInvalidIdx ) + { + const cmApIO* op = _theBuf.devArray[oDevIdx].ioArray + kOutApIdx; + unsigned byteCnt = op->dspFrameCnt * sizeof(sample_t); + + _theBufCalcTimeStamp(op->srate, &op->timeStamp, op->ioFrameCnt, oTimeStampPtr ); + + for(; ichCnt; ++i) + { + cmApCh* cp = ioPtr->chArray + i; + cp->oi = (cp->oi + ioPtr->dspFrameCnt) % ioPtr->n; + //cmThUIntDecr(&cp->fn,ioPtr->dspFrameCnt); + std::atomic_fetch_sub(&cp->fn,ioPtr->dspFrameCnt); + + + } + + // count the number of samples input from this device + if( ioPtr->timeStamp.tv_sec!=0 && ioPtr->timeStamp.tv_nsec!=0 ) + { + //cmThUIntIncr(&ioPtr->ioFrameCnt,ioPtr->dspFrameCnt); + std::atomic_fetch_add(&ioPtr->ioFrameCnt, ioPtr->dspFrameCnt); + } + } + + if( flags & kOutFl ) + { + cmApIO* ioPtr = _theBuf.devArray[devIdx].ioArray + kOutApIdx; + for(i=0; ichCnt; ++i) + { + cmApCh* cp = ioPtr->chArray + i; + cp->ii = (cp->ii + ioPtr->dspFrameCnt) % ioPtr->n; + //cmThUIntIncr(&cp->fn,ioPtr->dspFrameCnt); + std::atomic_fetch_add(&cp->fn,ioPtr->dspFrameCnt); + + } + + // count the number of samples output from this device + if( ioPtr->timeStamp.tv_sec!=0 && ioPtr->timeStamp.tv_nsec!=0 ) + { + //cmThUIntIncr(&ioPtr->ioFrameCnt,ioPtr->dspFrameCnt); + std::atomic_fetch_add(&ioPtr->ioFrameCnt,ioPtr->dspFrameCnt); + + } + } +} + + +void cw::audio::buf::inputToOutput( unsigned iDevIdx, unsigned oDevIdx ) +{ + if( iDevIdx == kInvalidIdx || oDevIdx == kInvalidIdx ) + return; + + unsigned iChCnt = channelCount( iDevIdx, kInFl ); + unsigned oChCnt = channelCount( oDevIdx, kOutFl ); + unsigned chCnt = iChCnt < oChCnt ? iChCnt : oChCnt; + + unsigned i; + + sample_t* iBufPtrArray[ iChCnt ]; + sample_t* oBufPtrArray[ oChCnt ]; + + + while( isDeviceReady( iDevIdx, kInFl ) && isDeviceReady( oDevIdx, kOutFl ) ) + { + get( iDevIdx, kInFl, iBufPtrArray, iChCnt ); + get( oDevIdx, kOutFl, oBufPtrArray, oChCnt ); + + // Warning: buffer pointers to disabled channels will be set to NULL + + for(i=0; idspFrameCnt == op->dspFrameCnt ); + + unsigned byteCnt = ip->dspFrameCnt * sizeof(sample_t); + + if( oBufPtrArray[i] != NULL ) + { + // the input channel is not disabled + if( iBufPtrArray[i]!=NULL ) + memcpy(oBufPtrArray[i],iBufPtrArray[i],byteCnt); + else + // the input channel is disabled but the output is not - so fill the output with zeros + memset(oBufPtrArray[i],0,byteCnt); + } + } + + advance( iDevIdx, kInFl ); + advance( oDevIdx, kOutFl ); + } + +} + +void cw::audio::buf::report( textBuf::handle_t tbH ) +{ + unsigned i,j,k; + for(i=0; i<_theBuf.devCnt; ++i) + { + textBuf::print(tbH,"%i ",i); + + for(j=0; jchCnt; ++k) + { + cmApCh* cp = ip->chArray + i; + ii += cp->ii; + oi += cp->oi; + fn += cp->fn; + } + + textBuf::print(tbH,"%s - i:%7i o:%7i f:%7i n:%7i err %s:%7i ", + j==0?"IN":"OUT", + ii,oi,fn,ip->n, (j==0?"over":"under"), ip->faultCnt); + + } + + textBuf::print(tbH,"\n"); + } +} + +/// [cwAudioBufExample] + +void cw::audio::buf::test() +{ + unsigned devIdx = 0; + unsigned devCnt = 1 ; + unsigned dspFrameCnt = 10; + unsigned cycleCnt = 3; + unsigned framesPerCycle = 25; + unsigned inChCnt = 2; + unsigned outChCnt = inChCnt; + unsigned sigN = cycleCnt*framesPerCycle*inChCnt; + double srate = 44100.0; + unsigned meterMs = 50; + + unsigned bufChCnt = inChCnt; + sample_t* inBufArray[ bufChCnt ]; + sample_t* outBufArray[ bufChCnt ]; + sample_t iSig[ sigN ]; + sample_t oSig[ sigN ]; + sample_t* os = oSig; + device::audioPacket_t pkt; + unsigned i,j; + + // create a simulated signal + for(i=0; idrvCnt; ++i) + if( _ap->drvArray[i].begDevIdx != kInvalidIdx ) + if( (_ap->drvArray[i].begDevIdx <= devIdx) && (devIdx <= _ap->drvArray[i].endDevIdx) ) + { + *drvPtrPtr = _ap->drvArray + i; + *devIdxPtr = devIdx - _ap->drvArray[i].begDevIdx; + return kOkRC; + } + + return cwLogError(kInvalidIdRC,"The audio port device index %i is not valid.",devIdx); + } + } + } +} + +cw::rc_t cw::audio::device::initialize() +{ + rc_t rc = kOkRC; + if((rc = finalize()) != kOkRC ) + return rc; + + _ap = memAllocZ(1); + + _ap->drvCnt = 1; + _ap->drvArray = memAllocZ(_ap->drvCnt); + cmApDriver_t* dp = _ap->drvArray; + +#ifdef cwOSX + dp->initialize = cmApOsxInitialize; + dp->finalize = cmApOsxFinalize; + dp->deviceCount = cmApOsxDeviceCount; + dp->deviceLabel = cmApOsxDeviceLabel; + dp->deviceChannelCount = cmApOsxDeviceChannelCount; + dp->deviceSampleRate = cmApOsxDeviceSampleRate; + dp->deviceFramesPerCycle = cmApOsxDeviceFramesPerCycle; + dp->deviceSetup = cmApOsxDeviceSetup; + dp->deviceStart = cmApOsxDeviceStart; + dp->deviceStop = cmApOsxDeviceStop; + dp->deviceIsStarted = cmApOsxDeviceIsStarted; +#endif + +#ifdef cwLINUX + dp->initialize = alsa::initialize; + dp->finalize = alsa::finalize; + dp->deviceCount = alsa::deviceCount; + dp->deviceLabel = alsa::deviceLabel; + dp->deviceChannelCount = alsa::deviceChannelCount; + dp->deviceSampleRate = alsa::deviceSampleRate; + dp->deviceFramesPerCycle = alsa::deviceFramesPerCycle; + dp->deviceSetup = alsa::deviceSetup; + dp->deviceStart = alsa::deviceStart; + dp->deviceStop = alsa::deviceStop; + dp->deviceIsStarted = alsa::deviceIsStarted; +#endif + + /* + dp = _ap->drvArray + 1; + + dp->initialize = cmApFileInitialize; + dp->finalize = cmApFileFinalize; + dp->deviceCount = cmApFileDeviceCount; + dp->deviceLabel = cmApFileDeviceLabel; + dp->deviceChannelCount = cmApFileDeviceChannelCount; + dp->deviceSampleRate = cmApFileDeviceSampleRate; + dp->deviceFramesPerCycle = cmApFileDeviceFramesPerCycle; + dp->deviceSetup = cmApFileDeviceSetup; + dp->deviceStart = cmApFileDeviceStart; + dp->deviceStop = cmApFileDeviceStop; + dp->deviceIsStarted = cmApFileDeviceIsStarted; + + dp = _ap->drvArray + 2; + + dp->initialize = cmApAggInitialize; + dp->finalize = cmApAggFinalize; + dp->deviceCount = cmApAggDeviceCount; + dp->deviceLabel = cmApAggDeviceLabel; + dp->deviceChannelCount = cmApAggDeviceChannelCount; + dp->deviceSampleRate = cmApAggDeviceSampleRate; + dp->deviceFramesPerCycle = cmApAggDeviceFramesPerCycle; + dp->deviceSetup = cmApAggDeviceSetup; + dp->deviceStart = cmApAggDeviceStart; + dp->deviceStop = cmApAggDeviceStop; + dp->deviceIsStarted = cmApAggDeviceIsStarted; + + dp = _ap->drvArray + 3; + + dp->initialize = cmApNrtInitialize; + dp->finalize = cmApNrtFinalize; + dp->deviceCount = cmApNrtDeviceCount; + dp->deviceLabel = cmApNrtDeviceLabel; + dp->deviceChannelCount = cmApNrtDeviceChannelCount; + dp->deviceSampleRate = cmApNrtDeviceSampleRate; + dp->deviceFramesPerCycle = cmApNrtDeviceFramesPerCycle; + dp->deviceSetup = cmApNrtDeviceSetup; + dp->deviceStart = cmApNrtDeviceStart; + dp->deviceStop = cmApNrtDeviceStop; + dp->deviceIsStarted = cmApNrtDeviceIsStarted; + */ + + _ap->devCnt = 0; + + unsigned i; + for(i=0; i<_ap->drvCnt; ++i) + { + unsigned dn; + rc_t rc0; + + _ap->drvArray[i].begDevIdx = kInvalidIdx; + _ap->drvArray[i].endDevIdx = kInvalidIdx; + + if((rc0 = _ap->drvArray[i].initialize(_ap->devCnt)) != kOkRC ) + { + rc = rc0; + continue; + } + + if((dn = _ap->drvArray[i].deviceCount()) > 0) + { + _ap->drvArray[i].begDevIdx = _ap->devCnt; + _ap->drvArray[i].endDevIdx = _ap->devCnt + dn - 1; + _ap->devCnt += dn; + } + } + + if( rc != kOkRC ) + finalize(); + + return rc; +} + +cw::rc_t cw::audio::device::finalize() +{ + rc_t rc=kOkRC; + rc_t rc0 = kOkRC; + unsigned i; + + if( _ap == NULL ) + return kOkRC; + + for(i=0; i<_ap->drvCnt; ++i) + { + if((rc0 = _ap->drvArray[i].finalize()) != kOkRC ) + rc = rc0; + } + + memRelease(_ap->drvArray); + memRelease(_ap); + return rc; +} + + +unsigned cw::audio::device::deviceCount() +{ return _ap->devCnt; } + +const char* cw::audio::device::deviceLabel( unsigned devIdx ) +{ + cmApDriver_t* dp = NULL; + unsigned di = kInvalidIdx; + rc_t rc; + + if( devIdx == kInvalidIdx ) + return NULL; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return cwStringNullGuard(NULL); + + return dp->deviceLabel(di); +} + +unsigned cw::audio::device::deviceLabelToIndex( const char* label ) +{ + unsigned n = deviceCount(); + unsigned i; + for(i=0; ideviceChannelCount(di,inputFl); +} + +double cw::audio::device::deviceSampleRate( unsigned devIdx ) +{ + cmApDriver_t* dp = NULL; + unsigned di = kInvalidIdx; + rc_t rc; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return rc; + + return dp->deviceSampleRate(di); +} + +unsigned cw::audio::device::deviceFramesPerCycle( unsigned devIdx, bool inputFl ) +{ + cmApDriver_t* dp = NULL; + unsigned di = kInvalidIdx; + rc_t rc; + + if( devIdx == kInvalidIdx ) + return 0; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return rc; + + return dp->deviceFramesPerCycle(di,inputFl); +} + +cw::rc_t cw::audio::device::deviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + cbFunc_t cbFunc, + void* cbArg ) +{ + cmApDriver_t* dp; + unsigned di; + rc_t rc; + + if( devIdx == kInvalidIdx ) + return kOkRC; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return rc; + + return dp->deviceSetup(di,srate,framesPerCycle,cbFunc,cbArg); +} + +cw::rc_t cw::audio::device::deviceStart( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + rc_t rc; + + if( devIdx == kInvalidIdx ) + return kOkRC; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return rc; + + return dp->deviceStart(di); +} + +cw::rc_t cw::audio::device::deviceStop( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + rc_t rc; + + if( devIdx == kInvalidIdx ) + return kOkRC; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return rc; + + return dp->deviceStop(di); +} + +bool cw::audio::device::deviceIsStarted( unsigned devIdx ) +{ + cmApDriver_t* dp; + unsigned di; + rc_t rc; + + if( devIdx == kInvalidIdx ) + return false; + + if((rc = _cmApIndexToDev(devIdx,&dp,&di)) != kOkRC ) + return rc; + + return dp->deviceIsStarted(di); +} + + + +void cw::audio::device::report() +{ + unsigned i,j,k; + for(j=0,k=0; j<_ap->drvCnt; ++j) + { + cmApDriver_t* drvPtr = _ap->drvArray + j; + unsigned n = drvPtr->deviceCount(); + + for(i=0; ideviceSampleRate(i), + drvPtr->deviceChannelCount(i,true), drvPtr->deviceFramesPerCycle(i,true), + drvPtr->deviceChannelCount(i,false), drvPtr->deviceFramesPerCycle(i,false), + drvPtr->deviceLabel(i)); + + } + } + + //cmApAlsaDeviceReport(rpt); +} + +namespace cw +{ + namespace audio + { + namespace device + { + /// [cmAudioPortExample] + + // See cmApPortTest() below for the main point of entry. + + // Data structure used to hold the parameters for cpApPortTest() + // and the user defined data record passed to the host from the + // audio port callback functions. + typedef struct + { + unsigned bufCnt; // 2=double buffering 3=triple buffering + unsigned chIdx; // first test channel + unsigned chCnt; // count of channels to test + unsigned framesPerCycle; // DSP frames per cycle + unsigned bufFrmCnt; // count of DSP frames used by the audio buffer (bufCnt * framesPerCycle) + unsigned bufSmpCnt; // count of samples used by the audio buffer (chCnt * bufFrmCnt) + unsigned inDevIdx; // input device index + unsigned outDevIdx; // output device index + double srate; // audio sample rate + unsigned meterMs; // audio meter buffer length + + // param's and state for cmApSynthSine() + unsigned phase; // sine synth phase + double frqHz; // sine synth frequency in Hz + + // buffer and state for cmApCopyIn/Out() + sample_t* buf; // buf[bufSmpCnt] - circular interleaved audio buffer + unsigned bufInIdx; // next input buffer index + unsigned bufOutIdx; // next output buffer index + unsigned bufFullCnt; // count of full samples + + // debugging log data arrays + unsigned logCnt; // count of elements in log[] and ilong[] + char* log; // log[logCnt] + unsigned* ilog; // ilog[logCnt] + unsigned logIdx; // current log index + + unsigned cbCnt; // count the callback + } cmApPortTestRecd; + + +#ifdef NOT_DEF + // The application can request any block of channels from the device. The packets are provided with the starting + // device channel and channel count. This function converts device channels and channel counts to buffer + // channel indexes and counts. + // + // Example: + // input output + // i,n i n + // App: 0,4 0 1 2 3 -> 2 2 + // Pkt 2,8 2 3 4 5 6 7 8 -> 0 2 + // + // The return value is the count of application requested channels located in this packet. + // + // input: *appChIdxPtr and appChCnt describe a block of device channels requested by the application. + // *pktChIdxPtr and pktChCnt describe a block of device channels provided to the application + // + // output:*appChIdxPtr and describe a block of app buffer channels which will send/recv samples. + // *pktChIdxPtr and describe a block of pkt buffer channels which will send/recv samples + // + unsigned _cmApDeviceToBuffer( unsigned* appChIdxPtr, unsigned appChCnt, unsigned* pktChIdxPtr, unsigned pktChCnt ) + { + unsigned abi = *appChIdxPtr; + unsigned aei = abi+appChCnt-1; + + unsigned pbi = *pktChIdxPtr; + unsigned pei = pbi+pktChCnt-1; + + // if the ch's rqstd by the app do not overlap with this packet - return false. + if( aei < pbi || abi > pei ) + return 0; + + // if the ch's rqstd by the app overlap with the beginning of the pkt channel block + if( abi < pbi ) + { + appChCnt -= pbi - abi; + *appChIdxPtr = pbi - abi; + *pktChIdxPtr = 0; + } + else + { + // the rqstd ch's begin inside the pkt channel block + pktChCnt -= abi - pbi; + *pktChIdxPtr = abi - pbi; + *appChIdxPtr = 0; + } + + // if the pkt channels extend beyond the rqstd ch block + if( aei < pei ) + pktChCnt -= pei - aei; + else + appChCnt -= aei - pei; // the rqstd ch's extend beyond or coincide with the pkt block + + // the returned channel count must always be the same for both the rqstd and pkt + return cmMin(appChCnt,pktChCnt); + + } + + + // synthesize a sine signal into an interleaved audio buffer + unsigned _cmApSynthSine( cmApPortTestRecd* r, float* p, unsigned chIdx, unsigned chCnt, unsigned frmCnt, unsigned phs, double hz ) + { + long ph = 0; + unsigned i; + unsigned bufIdx = r->chIdx; + unsigned bufChCnt; + + if( (bufChCnt = _cmApDeviceToBuffer( &bufIdx, r->chCnt, &chIdx, chCnt )) == 0) + return phs; + + + //if( r->cbCnt < 50 ) + // printf("ch:%i cnt:%i ch:%i cnt:%i bi:%i bcn:%i\n",r->chIdx,r->chCnt,chIdx,chCnt,bufIdx,bufChCnt); + + + for(i=bufIdx; israte )); + } + } + + return ph; + } + + // Copy the audio samples in the interleaved audio buffer sp[srcChCnt*srcFrameCnt] + // to the internal record buffer. + void _cmApCopyIn( cmApPortTestRecd* r, const sample_t* sp, unsigned srcChIdx, unsigned srcChCnt, unsigned srcFrameCnt ) + { + unsigned i,j; + + unsigned chCnt = cmMin(r->chCnt,srcChCnt); + + for(i=0; ibuf[ r->bufInIdx + j ] = sp[ (i*srcChCnt) + j ]; + + for(; jchCnt; ++j) + r->buf[ r->bufInIdx + j ] = 0; + + r->bufInIdx = (r->bufInIdx+r->chCnt) % r->bufFrmCnt; + } + + //r->bufFullCnt = (r->bufFullCnt + srcFrameCnt) % r->bufFrmCnt; + r->bufFullCnt += srcFrameCnt; + } + + // Copy audio samples out of the internal record buffer into dp[dstChCnt*dstFrameCnt]. + void _cmApCopyOut( cmApPortTestRecd* r, sample_t* dp, unsigned dstChIdx, unsigned dstChCnt, unsigned dstFrameCnt ) + { + // if there are not enough samples available to fill the destination buffer then zero the dst buf. + if( r->bufFullCnt < dstFrameCnt ) + { + printf("Empty Output Buffer\n"); + memset( dp, 0, dstFrameCnt*dstChCnt*sizeof(sample_t) ); + } + else + { + unsigned i,j; + unsigned chCnt = cmMin(dstChCnt, r->chCnt); + + // for each output frame + for(i=0; ibuf[ r->bufOutIdx + j ]; + + // zero any output ch's for which there is no internal buf channel + for(; jbufOutIdx = (r->bufOutIdx + r->chCnt) % r->bufFrmCnt; + } + + r->bufFullCnt -= dstFrameCnt; + } + } + + // Audio port callback function called from the audio device thread. + void _cmApPortCb( cmApAudioPacket_t* inPktArray, unsigned inPktCnt, cmApAudioPacket_t* outPktArray, unsigned outPktCnt ) + { + unsigned i; + + // for each incoming audio packet + for(i=0; iinDevIdx ) + { + // copy the incoming audio into an internal buffer where it can be picked up by _cpApCopyOut(). + _cmApCopyIn( r, (sample_t*)inPktArray[i].audioBytesPtr, inPktArray[i].begChIdx, inPktArray[i].chCnt, inPktArray[i].audioFramesCnt ); + } + ++r->cbCnt; + + //printf("i %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); + } + + unsigned hold_phase = 0; + + // for each outgoing audio packet + for(i=0; ioutDevIdx ) + { + // zero the output buffer + memset(outPktArray[i].audioBytesPtr,0,outPktArray[i].chCnt * outPktArray[i].audioFramesCnt * sizeof(sample_t) ); + + // if the synth is enabled + if( r->synthFl ) + { + unsigned tmp_phase = _cmApSynthSine( r, outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt, r->phase, r->frqHz ); + + // the phase will only change on packets that are actually used + if( tmp_phase != r->phase ) + hold_phase = tmp_phase; + } + else + { + // copy the any audio in the internal record buffer to the playback device + _cmApCopyOut( r, (sample_t*)outPktArray[i].audioBytesPtr, outPktArray[i].begChIdx, outPktArray[i].chCnt, outPktArray[i].audioFramesCnt ); + } + } + + r->phase = hold_phase; + + //printf("o %4i in:%4i out:%4i\n",r->bufFullCnt,r->bufInIdx,r->bufOutIdx); + // count callbacks + ++r->cbCnt; + } + } +#endif + + // print the usage message for cmAudioPortTest.c + void _cmApPrintUsage() + { + char msg[] = + "cmApPortTest() command switches\n" + "-r -c -b -f -i -o -t -p -h \n" + "\n" + "-r = sample rate\n" + "-a = first channel\n" + "-c = audio channels\n" + "-b = count of buffers\n" + "-f = count of samples per buffer\n" + "-i = input device index\n" + "-o = output device index\n" + "-p = print report but do not start audio devices\n" + "-h = print this usage message\n"; + + cwLogInfo(msg); + } + + // Get a command line option. + int _cmApGetOpt( int argc, const char* argv[], const char* label, int defaultVal, bool boolFl ) + { + int i = 0; + for(; i // usleep + +namespace cw +{ + namespace audio + { + namespace device + { + namespace alsa + { +#define NAME_CHAR_CNT (255) +#define DESC_CHAR_CNT (255) +#define INIT_DEV_CNT (5) + + //#define IMPULSE_FN "/home/kevin/temp/recd0.txt" + + enum { kDfltPeriodsPerBuf = 2, kPollfdsArrayCnt=2 }; + + enum { kInFl=0x01, kOutFl=0x02 }; + + struct cmApRoot_str; + + + typedef struct devRecd_str + { + struct cmApRoot_str* rootPtr; + unsigned devIdx; + char nameStr[ NAME_CHAR_CNT ]; + char descStr[ DESC_CHAR_CNT ]; + unsigned flags; + + unsigned framesPerCycle; // samples per sub-buffer + unsigned periodsPerBuf; // sub-buffers per buffer + snd_async_handler_t* ahandler; + unsigned srate; // device sample rate + + unsigned iChCnt; // ch count + unsigned oChCnt; + + unsigned iBits; // bits per sample + unsigned oBits; + + bool iSignFl; // sample type is signed + bool oSignFl; + + bool iSwapFl; // swap the sample bytes + bool oSwapFl; + + unsigned iSigBits; // significant bits in each sample beginning + unsigned oSigBits; // with the most sig. bit. + + + device::sample_t* iBuf; // iBuf[ iFpc * iChCnt ] + device::sample_t* oBuf; // oBuf[ oFpc * oChCnt ] + + unsigned oBufCnt; // count of buffers written + +#ifdef IMPULSE_FN + int* recdBuf; + unsigned recdN; + unsigned recdIdx; +#endif + + unsigned iFpC; // buffer frames per cycle (in ALSA this is call period_size) + unsigned oFpC; + + snd_pcm_t* iPcmH; // device handle + snd_pcm_t* oPcmH; + + unsigned iCbCnt; // callback count + unsigned oCbCnt; + + unsigned iErrCnt; // error count + unsigned oErrCnt; + + device::cbFunc_t cbPtr; // user callback + void* userCbPtr; + + } cmApDevRecd_t; + + typedef struct cmApPoll_str + { + cmApDevRecd_t* devPtr; + bool inputFl; + unsigned fdsCnt; + } cmApPollfdsDesc_t; + + typedef struct cmApRoot_str + { + cmApDevRecd_t* devArray = nullptr; // array of device records + unsigned devCnt = 0; // count of actual dev recds in devArray[] + unsigned devAllocCnt = 0; // count of dev recds allocated in devArray[] + bool asyncFl = false; // true=use async callback false=use polling thread + + thread::handle_t thH; // polling thread + + unsigned pollfdsAllocCnt = 0; // 2*devCnt (max possible in+out handles) + struct pollfd* pollfds = nullptr; // pollfds[ pollfdsAllocCnt ] + cmApPollfdsDesc_t *pollfdsDesc = nullptr; // pollfdsDesc[ pollfdsAllocCnt ] + unsigned pollfdsCnt = 0; // count of active recds in pollfds[] and pollfdsDesc[] + + } cmApRoot_t; + + cmApRoot_t _cmApRoot; + + enum + { + kReadErrRId, + kWriteErrRId + }; + + void recdSetup(){} + void recdPush( unsigned typeId, unsigned devIdx, bool inputFl, unsigned arg, unsigned arg1 ){} + void recdStart( const cmApDevRecd_t* drp, bool inputFl ){} + void recdCb( const cmApDevRecd_t* drp, bool inputFl, unsigned frmCnt ){} + void recdSysErr( bool inputFl, int err ){} + void recdAppErr( const cmApDevRecd_t* drp, bool inputFl, int app, int err ){} + void recdRecover( const cmApDevRecd_t* drp, bool inputFl, int err, int line ){} + void recdPrint(){} + void recdFree(){} + + + rc_t _alsaErrorImpl( int alsaRC, const char* func, const char* fn, int lineNumb, const char* fmt, ... ) + { + rc_t rc = kOkRC; + va_list vl0; + va_start(vl0,fmt); + + if( alsaRC == 0 ) + { + rc = logMsg( logGlobalHandle(), kError_LogLevel, func, fn, lineNumb, 0, kOpFailRC, fmt, vl0 ); + } + else + { + va_list vl1; + va_copy(vl1,vl0); + + int n = vsnprintf(NULL,0,fmt,vl0); + char msg0[n+1]; + int m = vsnprintf(msg0,n+1,fmt,vl1); + + cwAssert(n==m) + va_end(vl1); + + const char* fmt1 = "%s ALSA Error:%s"; + n = snprintf(NULL,0,fmt1,msg0,snd_strerror(alsaRC)); + char msg1[n+1]; + m = snprintf(msg1,n+1,fmt1,msg0,snd_strerror(alsaRC)); + + rc = logMsg( logGlobalHandle(), kError_LogLevel, func, fn, lineNumb, 0, kOpFailRC, "%s", msg1 ); + } + + va_end(vl0); + + return rc; + } + +#define _alsaError( alsaRC, fmt, ...) _alsaErrorImpl( alsaRC, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__ ) + + rc_t _alsaSetupErrorImpl( int alsaRC, bool inputFl, const cmApDevRecd_t* drp, const char* func, const char* fn, int lineNumb, const char* fmt, ... ) + { + va_list vl0,vl1; + va_start(vl0,fmt); + va_copy(vl1,vl0); + int n = vsnprintf(NULL,0,fmt,vl0); + char msg[n+1]; + int m = vsnprintf(msg,n+1,fmt,vl1); + cwAssert(m==n); + return _alsaErrorImpl( alsaRC, func, fn, lineNumb, "%s for %s '%s' : '%s'.",msg,inputFl ? "INPUT" : "OUTPUT", drp->nameStr, drp->descStr ); + } + +#define _alsaSetupError( alsaRC, inputFl, drp, fmt, ...) _alsaSetupErrorImpl( alsaRC, inputFl, drp, __FUNCTION__, __FILE__, __LINE__, fmt, ##__VA_ARGS__ ) + + const char* _cmApPcmStateToString( snd_pcm_state_t state ) + { + switch( state ) + { + case SND_PCM_STATE_OPEN: return "open"; + case SND_PCM_STATE_SETUP: return "setup"; + case SND_PCM_STATE_PREPARED: return "prepared"; + case SND_PCM_STATE_RUNNING: return "running"; + case SND_PCM_STATE_XRUN: return "xrun"; + case SND_PCM_STATE_DRAINING: return "draining"; + case SND_PCM_STATE_PAUSED: return "paused"; + case SND_PCM_STATE_SUSPENDED: return "suspended"; + case SND_PCM_STATE_DISCONNECTED: return "disconnected"; + case SND_PCM_STATE_PRIVATE1: return "private1"; + + } + return ""; + } + + void _cmApDevRtReport( cmApDevRecd_t* drp ) + { + cwLogInfo("cb i:%i o:%i err i:%i o:%i",drp->iCbCnt,drp->oCbCnt,drp->iErrCnt,drp->oErrCnt); + + if( drp->iPcmH != NULL ) + cwLogInfo(" state i:%s",_cmApPcmStateToString(snd_pcm_state(drp->iPcmH))); + + if( drp->oPcmH != NULL ) + cwLogInfo(" o:%s",_cmApPcmStateToString(snd_pcm_state(drp->oPcmH))); + + + } + + void _cmApDevReportFormats( snd_pcm_hw_params_t* hwParams ) + { + snd_pcm_format_mask_t* mask; + + snd_pcm_format_t fmt[] = + { + SND_PCM_FORMAT_S8, + SND_PCM_FORMAT_U8, + SND_PCM_FORMAT_S16_LE, + SND_PCM_FORMAT_S16_BE, + SND_PCM_FORMAT_U16_LE, + SND_PCM_FORMAT_U16_BE, + SND_PCM_FORMAT_S24_LE, + SND_PCM_FORMAT_S24_BE, + SND_PCM_FORMAT_U24_LE, + SND_PCM_FORMAT_U24_BE, + SND_PCM_FORMAT_S32_LE, + SND_PCM_FORMAT_S32_BE, + SND_PCM_FORMAT_U32_LE, + SND_PCM_FORMAT_U32_BE, + SND_PCM_FORMAT_FLOAT_LE, + SND_PCM_FORMAT_FLOAT_BE, + SND_PCM_FORMAT_FLOAT64_LE, + SND_PCM_FORMAT_FLOAT64_BE, + SND_PCM_FORMAT_IEC958_SUBFRAME_LE, + SND_PCM_FORMAT_IEC958_SUBFRAME_BE, + SND_PCM_FORMAT_MU_LAW, + SND_PCM_FORMAT_A_LAW, + SND_PCM_FORMAT_IMA_ADPCM, + SND_PCM_FORMAT_MPEG, + SND_PCM_FORMAT_GSM, + SND_PCM_FORMAT_SPECIAL, + SND_PCM_FORMAT_S24_3LE, + SND_PCM_FORMAT_S24_3BE, + SND_PCM_FORMAT_U24_3LE, + SND_PCM_FORMAT_U24_3BE, + SND_PCM_FORMAT_S20_3LE, + SND_PCM_FORMAT_S20_3BE, + SND_PCM_FORMAT_U20_3LE, + SND_PCM_FORMAT_U20_3BE, + SND_PCM_FORMAT_S18_3LE, + SND_PCM_FORMAT_S18_3BE, + SND_PCM_FORMAT_U18_3LE, + SND_PCM_FORMAT_U18_3BE, + SND_PCM_FORMAT_G723_24, + SND_PCM_FORMAT_G723_24_1B, + SND_PCM_FORMAT_G723_40, + SND_PCM_FORMAT_G723_40_1B, + SND_PCM_FORMAT_DSD_U8, + //SND_PCM_FORMAT_DSD_U16_LE, + //SND_PCM_FORMAT_DSD_U32_LE, + //SND_PCM_FORMAT_DSD_U16_BE, + //SND_PCM_FORMAT_DSD_U32_BE, + SND_PCM_FORMAT_UNKNOWN + }; + + snd_pcm_format_mask_alloca(&mask); + + snd_pcm_hw_params_get_format_mask(hwParams,mask); + + cwLogInfo("Formats: " ); + + int i; + for(i=0; fmt[i]!=SND_PCM_FORMAT_UNKNOWN; ++i) + if( snd_pcm_format_mask_test(mask, fmt[i] )) + cwLogInfo("%s%s",snd_pcm_format_name(fmt[i]), snd_pcm_format_cpu_endian(fmt[i]) ? " " : " (swap) "); + + + } + + void _cmApDevReport( cmApDevRecd_t* drp ) + { + bool inputFl = true; + snd_pcm_t* pcmH; + int err; + unsigned i; + + //cmApRoot_t* p = drp->rootPtr; + + cwLogInfo("%s %s Device:%s Desc:%s\n", drp->flags & kInFl ? "IN ":"", drp->flags & kOutFl ? "OUT ":"", drp->nameStr, drp->descStr); + + for(i=0; i<2; i++,inputFl=!inputFl) + { + if( ((inputFl==true) && (drp->flags&kInFl)) || (((inputFl==false) && (drp->flags&kOutFl)))) + { + const char* ioLabel = inputFl ? "In " : "Out"; + + // attempt to open the sub-device + if((err = snd_pcm_open(&pcmH,drp->nameStr,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,0)) < 0 ) + _alsaSetupError(err,inputFl,drp,"Attempt to open the PCM handle failed"); + else + { + snd_pcm_hw_params_t* hwParams; + + snd_pcm_hw_params_alloca(&hwParams); + memset(hwParams,0,snd_pcm_hw_params_sizeof()); + + // load the parameter record + if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error obtaining hw param record"); + else + { + unsigned minChCnt=0,maxChCnt=0,minSrate=0,maxSrate=0; + snd_pcm_uframes_t minPeriodFrmCnt=0,maxPeriodFrmCnt=0,minBufFrmCnt=0,maxBufFrmCnt=0; + int dir; + + + // extract the min channel count + if((err = snd_pcm_hw_params_get_channels_min(hwParams, &minChCnt )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting min. channel count."); + + // extract the max channel count + if((err = snd_pcm_hw_params_get_channels_max(hwParams, &maxChCnt )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting max. channel count."); + + // extract the min srate + if((err = snd_pcm_hw_params_get_rate_min(hwParams, &minSrate,&dir )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting min. sample rate."); + + // extract the max srate + if((err = snd_pcm_hw_params_get_rate_max(hwParams, &maxSrate,&dir )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting max. sample rate."); + + // extract the min period + if((err = snd_pcm_hw_params_get_period_size_min(hwParams, &minPeriodFrmCnt,&dir )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting min. period frame count."); + + // extract the max period + if((err = snd_pcm_hw_params_get_period_size_max(hwParams, &maxPeriodFrmCnt,&dir )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting max. period frame count."); + + // extract the min buf + if((err = snd_pcm_hw_params_get_buffer_size_min(hwParams, &minBufFrmCnt )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting min. period frame count."); + + // extract the max buffer + if((err = snd_pcm_hw_params_get_buffer_size_max(hwParams, &maxBufFrmCnt )) < 0 ) + _alsaSetupError(err,inputFl,drp,"Error getting max. period frame count."); + + cwLogInfo("%s chs:%i - %i srate:%i - %i period:%i - %i buf:%i - %i half duplex only:%s joint duplex:%s", + ioLabel,minChCnt,maxChCnt,minSrate,maxSrate,minPeriodFrmCnt,maxPeriodFrmCnt,minBufFrmCnt,maxBufFrmCnt, + (snd_pcm_hw_params_is_half_duplex(hwParams) ? "yes" : "no"), + (snd_pcm_hw_params_is_joint_duplex(hwParams) ? "yes" : "no")); + + _cmApDevReportFormats( hwParams ); + } + + if((err = snd_pcm_close(pcmH)) < 0) + _alsaSetupError(err,inputFl,drp,"Error closing PCM handle"); + } + } + } + } + + + // Called by cmApInitialize() to append a cmApDevRecd to the _cmApRoot.devArray[]. + void _cmApDevAppend( cmApRoot_t* p, cmApDevRecd_t* drp ) + { + if( p->devCnt == p->devAllocCnt ) + { + p->devArray = memResizeZ( p->devArray, p->devAllocCnt + INIT_DEV_CNT ); + p->devAllocCnt += INIT_DEV_CNT; + } + + drp->devIdx = p->devCnt; // set the device index + drp->rootPtr = p; // set the pointer back to the root + +#ifdef IMPULSE_FN + drp->recdN = 44100*2*2; + drp->recdBuf = cmMemAllocZ(int,drp->recdN); + drp->recdIdx = 0; +#endif + + memcpy(p->devArray + p->devCnt, drp, sizeof(cmApDevRecd_t)); + + ++p->devCnt; + } + + rc_t _cmApDevShutdown( cmApRoot_t* p, cmApDevRecd_t* drp, bool inputFl ) + { + int err; + + snd_pcm_t** pcmH = inputFl ? &drp->iPcmH : &drp->oPcmH; + + if( *pcmH != NULL ) + { + if((err = snd_pcm_close(*pcmH)) < 0 ) + { + return _alsaSetupError(err,inputFl,drp,"Error closing device handle."); + } + + *pcmH = NULL; + } + + return kOkRC; + } + + int _cmApDevOpen( snd_pcm_t** pcmHPtr, const char* devNameStr, bool inputFl ) + { + int cnt = 0; + int err; + + do + { + if((err = snd_pcm_open(pcmHPtr,devNameStr,inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,0)) < 0 ) + { + cnt++; + usleep(10000); // sleep for 10 milliseconds + } + + }while(cnt<100 && err == -EBUSY ); + + return err; + } + + void _cmApXrun_recover( snd_pcm_t* pcmH, int err, cmApDevRecd_t* drp, bool inputFl, int line ) + { + char dirCh = inputFl ? 'I' : 'O'; + + inputFl ? drp->iErrCnt++ : drp->oErrCnt++; + + recdRecover(drp,inputFl,err,line); + + // -EPIPE signals and over/underrun (see pcm.c example xrun_recovery()) + switch( err ) + { + case -EPIPE: + { + + int silentFl = 1; + if((err = snd_pcm_recover( pcmH, err, silentFl )) < 0 ) + _alsaSetupError(err,inputFl,drp,"recover failed."); + + if( inputFl ) + { + if((err= snd_pcm_prepare(pcmH)) < 0 ) + _alsaSetupError(err,inputFl,drp,"re-prepare failed."); + else + if((err = snd_pcm_start(pcmH)) < 0 ) + _alsaSetupError(err,inputFl,drp,"restart failed."); + } + else + { + /* + _cmApWriteBuf(pcmH, NULL, drp->oChCnt, drp->oFpC ); + _cmApWriteBuf(pcmH, NULL, drp->oChCnt, drp->oFpC ); + + if((err = snd_pcm_prepare(pcmH))<0) + _alsaSetupError(err,inputFl,drp,"Recovery failed.\n"); + else + if((err = snd_pcm_resume(pcmH))<0) + _alsaSetupError(err,inputFl,drp,"Resume failed.\n"); + */ + } + + + printf("EPIPE %c %i %i %i\n",dirCh,drp->devIdx,drp->oCbCnt,line); + + //if((err = snd_pcm_prepare(pcmH))<0) + // _devSetupError(err,inputFl,*drp,"Recovery failed.\n"); + //else + // if((err = snd_pcm_resume(pcmH))<0) + // _devSetupError(err,inputFl,*drp,"Resume failed.\n"); + break; + } + + case -ESTRPIPE: + { + int silentFl = 1; + if((err = snd_pcm_recover( pcmH, err, silentFl )) < 0 ) + _alsaSetupError(err,inputFl,drp,"recover failed."); + + printf("audio port impl ESTRPIPE:%c\n",dirCh); + break; + } + + case -EBADFD: + { + _alsaSetupError(err,inputFl,drp,"%s failed.",inputFl ? "Read" : "Write" ); + break; + } + + default: + _alsaSetupError(err,inputFl,drp,"Unknown rd/wr error.\n"); + + } // switch + + } + + void _cmApStateRecover( snd_pcm_t* pcmH, cmApDevRecd_t* drp, bool inputFl ) + { + int err = 0; + + switch( snd_pcm_state(pcmH)) + { + case SND_PCM_STATE_XRUN: + err = -EPIPE; + break; + + case SND_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + break; + + case SND_PCM_STATE_OPEN: + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_DRAINING: + case SND_PCM_STATE_PAUSED: + case SND_PCM_STATE_DISCONNECTED: + case SND_PCM_STATE_PRIVATE1: + //case SND_PCM_STATE_LAST: + break; + } + + if( err < 0 ) + _cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ ); + + } + + void _cmApS24_3BE_to_Float( const char* x, device::sample_t* y, unsigned n ) + { + unsigned i; + for(i=0; i> 16); + y[i*3+1] = (char)((s & 0x00ff00) >> 8); + y[i*3+0] = (char)((s & 0x0000ff) >> 0); + } + } + + + // Returns count of frames written on success or < 0 on error; + // set smpPtr to NULL to write a buffer of silence + int _cmApWriteBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, const device::sample_t* sp, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits ) + { + int err = 0; + unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8; + unsigned smpCnt = chCnt * frmCnt; + unsigned byteCnt = bytesPerSmp * smpCnt; + const device::sample_t* ep = sp + smpCnt; + char obuf[ byteCnt ]; + + // if no output was given then fill the device buffer with zeros + if( sp == NULL ) + memset(obuf,0,byteCnt); + else + { + // otherwise convert the floating point samples to integers + switch( bits ) + { + case 8: + { + char* dp = (char*)obuf; + while( sp < ep ) + *dp++ = (char)(*sp++ * 0x7f); + } + break; + + case 16: + { + short* dp = (short*)obuf; + while( sp < ep ) + *dp++ = (short)(*sp++ * 0x7fff); + } + break; + + case 24: + { + // for use w/ MBox + //_cmApS24_3BE_from_Float(sp, obuf, ep-sp ); + + int* dp = (int*)obuf; + while( sp < ep ) + *dp++ = (int)(*sp++ * 0x7fffff); + + } + break; + + case 32: + { + int* dp = (int*)obuf; + + while( sp < ep ) + *dp++ = (int)(*sp++ * 0x7fffffff); + +#ifdef IMPLUSE_FN + int* tmp = (int*)obuf; + unsigned ii = 0; + if( drp->oBufCnt < 3 ) + for(; ii<32; ++ii) + tmp[ii] = 0x7fffffff; +#endif + + } + break; + } + } + + + // send the bytes to the device + err = snd_pcm_writei( pcmH, obuf, frmCnt ); + + ++drp->oBufCnt; + + if( err < 0 ) + { + recdAppErr(drp,false,kWriteErrRId,err); + _alsaSetupError( err, false, drp, "ALSA write error" ); + } + else + if( err > 0 && ((unsigned)err) != frmCnt ) + { + _alsaSetupError( 0, false, drp, "Actual count of bytes written did not match the count provided." ); + } + + + return err; + } + + + // Returns frames read on success or < 0 on error. + // Set smpPtr to NULL to read the incoming buffer and discard it + int _cmApReadBuf( cmApDevRecd_t* drp, snd_pcm_t* pcmH, device::sample_t* smpPtr, unsigned chCnt, unsigned frmCnt, unsigned bits, unsigned sigBits ) + { + int err = 0; + unsigned bytesPerSmp = (bits==24 ? 32 : bits)/8; + unsigned smpCnt = chCnt * frmCnt; + unsigned byteCnt = smpCnt * bytesPerSmp; + + char buf[ byteCnt ]; + + // get the incoming samples into buf[] ... + err = snd_pcm_readi(pcmH,buf,frmCnt); + + // if a read error occurred + if( err < 0 ) + { + recdAppErr(drp,true,kReadErrRId,err); + _alsaSetupError( err, false, drp, "ALSA read error" ); + } + else + if( err > 0 && ((unsigned)err) != frmCnt ) + { + _alsaSetupError( 0, false, drp, "Actual count of bytes read did not match the count requested." ); + } + + // if no buffer was given then there is nothing else to do + if( smpPtr == NULL ) + return err; + + // setup the return buffer + device::sample_t* dp = smpPtr; + device::sample_t* ep = dp + std::min(smpCnt,err*chCnt); + + switch(bits) + { + case 8: + { + char* sp = buf; + while(dp < ep) + *dp++ = ((device::sample_t)*sp++) / 0x7f; + } + break; + + case 16: + { + short* sp = (short*)buf; + while(dp < ep) + *dp++ = ((device::sample_t)*sp++) / 0x7fff; + } + break; + + case 24: + { + // For use with MBox + //_cmApS24_3BE_to_Float(buf, dp, ep-dp ); + int* sp = (int*)buf; + while(dp < ep) + *dp++ = ((device::sample_t)*sp++) / 0x7fffff; + } + break; + + + case 32: + { + int* sp = (int*)buf; + // The delta1010 (ICE1712) uses only the 24 highest bits according to + // + // http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html + // The example: ICE1712 chips support 32-bit sample processing, + // but low byte is ignored (playback) or zero (capture). + // + int mv = sigBits==24 ? 0x7fffff00 : 0x7fffffff; + while(dp < ep) + *dp++ = ((device::sample_t)*sp++) / mv; + +#ifdef IMPULSE_FN + sp = (int*)buf; + int* esp = sp + smpCnt; + for(; sprecdIdx < drp->recdN; ++drp->recdIdx) + drp->recdBuf[drp->recdIdx] = *sp++; +#endif + + } + break; + default: + { cwAssert(0); } + } + + return err; + + } + + void _cmApStaticAsyncHandler( snd_async_handler_t* ahandler ) + { + int err; + snd_pcm_sframes_t avail; + cmApDevRecd_t* drp = (cmApDevRecd_t*)snd_async_handler_get_callback_private(ahandler); + snd_pcm_t* pcmH = snd_async_handler_get_pcm(ahandler); + bool inputFl = snd_pcm_stream(pcmH) == SND_PCM_STREAM_CAPTURE; + device::sample_t* b = inputFl ? drp->iBuf : drp->oBuf; + unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt; + unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC; + device::audioPacket_t pkt; + + inputFl ? drp->iCbCnt++ : drp->oCbCnt++; + + pkt.devIdx = drp->devIdx; + pkt.begChIdx = 0; + pkt.chCnt = chCnt; + pkt.audioFramesCnt = frmCnt; + pkt.bitsPerSample = 32; + pkt.flags = kInterleavedApFl | kFloatApFl; + pkt.audioBytesPtr = b; + pkt.userCbPtr = drp->userCbPtr; + + recdCb(drp,inputFl,0); + + _cmApStateRecover( pcmH, drp, inputFl ); + + while( (avail = snd_pcm_avail_update(pcmH)) >= (snd_pcm_sframes_t)frmCnt ) + { + + // Handle input + if( inputFl ) + { + // read samples from the device + if((err = _cmApReadBuf(drp,pcmH,drp->iBuf,chCnt,frmCnt,drp->iBits,drp->oBits)) > 0 ) + { + pkt.audioFramesCnt = err; + + drp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application + } + } + + // Handle output + else + { + // callback to fill the buffer + drp->cbPtr(NULL,0,&pkt,1); + + // note that the application may return fewer samples than were requested + err = _cmApWriteBuf(drp, pcmH, pkt.audioFramesCnt < frmCnt ? NULL : drp->oBuf,chCnt,frmCnt,drp->oBits,drp->oSigBits); + + } + + // Handle read/write errors + if( err < 0 ) + { + inputFl ? drp->iErrCnt++ : drp->oErrCnt++; + _cmApXrun_recover( pcmH, err, drp, inputFl, __LINE__ ); + } + else + { + // _cmApStateRecover( pcmH, drp, inputFl ); + } + + } // while + + } + + bool _cmApThreadFunc(void* param) + { + cmApRoot_t* p = (cmApRoot_t*)param; + int result; + bool retFl = true; + + switch( result = poll(p->pollfds, p->pollfdsCnt, 250) ) + { + case 0: + // time out + break; + + case -1: + _alsaError(errno,"Poll fail."); + break; + + default: + { + cwAssert( result > 0 ); + + unsigned i; + + // for each i/o stream + for(i=0; ipollfdsCnt; ++i) + { + cmApDevRecd_t* drp = p->pollfdsDesc[i].devPtr; + bool inputFl = p->pollfdsDesc[i].inputFl; + snd_pcm_t* pcmH = inputFl ? drp->iPcmH : drp->oPcmH; + unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt; + unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC; + device::sample_t* b = inputFl ? drp->iBuf : drp->oBuf; + unsigned short revents = 0; + int err; + device::audioPacket_t pkt; + snd_pcm_uframes_t avail_frames; + + inputFl ? drp->iCbCnt++ : drp->oCbCnt++; + + pkt.devIdx = drp->devIdx; + pkt.begChIdx = 0; + pkt.chCnt = chCnt; + pkt.audioFramesCnt = frmCnt; + pkt.bitsPerSample = 32; + pkt.flags = kInterleavedApFl | kFloatApFl; + pkt.audioBytesPtr = b; + pkt.userCbPtr = drp->userCbPtr; + + inputFl ? drp->iCbCnt++ : drp->oCbCnt++; + + // get the timestamp for this buffer + if((err = snd_pcm_htimestamp(pcmH,&avail_frames,&pkt.timeStamp)) != 0 ) + { + _alsaSetupError( err, p->pollfdsDesc[i].inputFl, drp, "Get timestamp error."); + pkt.timeStamp.tv_sec = 0; + pkt.timeStamp.tv_nsec = 0; + } + + // Note that based on experimenting with the timestamp and the current + // clock_gettime(CLOCK_MONOTONIC) time it appears that the time stamp + // marks the end of the current buffer - so in fact the time stamp should + // be backed up by the availble sample count period to get the time of the + // first sample in the buffer + /* + unsigned avail_nano_secs = (unsigned)(avail_frames * (1000000000.0/drp->srate)); + if( pkt.timeStamp.tv_nsec > avail_nano_secs ) + pkt.timeStamp.tv_nsec -= avail_nano_secs; + else + { + pkt.timeStamp.tv_sec -= 1; + pkt.timeStamp.tv_nsec = 1000000000 - avail_nano_secs; + } + */ + + //printf("AUDI: %ld %ld\n",pkt.timeStamp.tv_sec,pkt.timeStamp.tv_nsec); + //cmTimeSpec_t t; + //clock_gettime(CLOCK_PROCESS_CPUTIME_ID,&t); + //printf("AUDI: %ld %ld\n",t.tv_sec,t.tv_nsec); + + + switch( snd_pcm_state(pcmH) ) + { + case SND_PCM_STATE_OPEN: + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_DRAINING: + case SND_PCM_STATE_PAUSED: + case SND_PCM_STATE_DISCONNECTED: + case SND_PCM_STATE_PRIVATE1: + continue; + + case SND_PCM_STATE_RUNNING: + case SND_PCM_STATE_XRUN: + case SND_PCM_STATE_SUSPENDED: + break; + } + + if(( err = snd_pcm_poll_descriptors_revents(pcmH, p->pollfds + i, 1 , &revents)) != 0 ) + { + _alsaSetupError( err, p->pollfdsDesc[i].inputFl, drp, "Return poll events failed."); + retFl = false; + goto errLabel; + } + + if(revents & POLLERR) + { + _alsaSetupError( err, p->pollfdsDesc[i].inputFl, drp, "Poll error."); + _cmApStateRecover( pcmH, drp, inputFl ); + //goto errLabel; + } + + if( inputFl && (revents & POLLIN) ) + { + if((err = _cmApReadBuf(drp,pcmH,drp->iBuf,chCnt,frmCnt,drp->iBits,drp->oBits)) > 0 ) + { + pkt.audioFramesCnt = err; + drp->cbPtr(&pkt,1,NULL,0 ); // send the samples to the application + + } + } + + if( !inputFl && (revents & POLLOUT) ) + { + + /* + unsigned srate = 96; + cmTimeSpec_t t1; + static cmTimeSpec_t t0 = {0,0}; + clock_gettime(CLOCK_MONOTONIC,&t1); + + // time since the time-stamp was generated + unsigned smp = (srate * (t1.tv_nsec - pkt.timeStamp.tv_nsec)) / 1000000; + + // time since the last output buffer was sent + unsigned dsmp = (srate * (t1.tv_nsec - t0.tv_nsec)) / 1000000; + printf("%i %ld %i : %ld %ld -> %ld %ld\n",smp,avail_frames,dsmp,pkt.timeStamp.tv_sec,pkt.timeStamp.tv_nsec,t1.tv_sec,t1.tv_nsec); + t0 = t1; + */ + + // callback to fill the buffer + drp->cbPtr(NULL,0,&pkt,1); + + // note that the application may return fewer samples than were requested + err = _cmApWriteBuf(drp, pcmH, pkt.audioFramesCnt < frmCnt ? NULL : drp->oBuf,chCnt,frmCnt,drp->oBits,drp->oSigBits); + + } + + + } + } + + } + + errLabel: + return retFl; + } + + + rc_t _cmApDevSetup( cmApDevRecd_t *drp, unsigned srate, unsigned framesPerCycle, unsigned periodsPerBuf ) + { + int err; + int dir; + unsigned i; + rc_t rc = kOkRC; + bool inputFl = true; + snd_pcm_uframes_t periodFrameCnt = framesPerCycle; + snd_pcm_uframes_t bufferFrameCnt; + unsigned bits = 0; + int sig_bits = 0; + bool signFl = true; + bool swapFl = false; + cmApRoot_t* p = drp->rootPtr; + + snd_pcm_format_t fmt[] = + { + SND_PCM_FORMAT_S32_LE, + SND_PCM_FORMAT_S32_BE, + SND_PCM_FORMAT_S24_LE, + SND_PCM_FORMAT_S24_BE, + SND_PCM_FORMAT_S24_3LE, + SND_PCM_FORMAT_S24_3BE, + SND_PCM_FORMAT_S16_LE, + SND_PCM_FORMAT_S16_BE, + }; + + + // setup input, then output device + for(i=0; i<2; i++,inputFl=!inputFl) + { + unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt; + snd_pcm_uframes_t actFpC = 0; + + // if this is the in/out pass and the in/out flag is set + if( ((inputFl==true) && (drp->flags & kInFl)) || ((inputFl==false) && (drp->flags & kOutFl)) ) + { + snd_pcm_t* pcmH = NULL; + rc_t rc0; + + if((rc0 = _cmApDevShutdown(p, drp, inputFl )) != kOkRC ) + rc = rc0; + + // attempt to open the sub-device + if((err = snd_pcm_open(&pcmH,drp->nameStr, inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Unable to open the PCM handle"); + else + { + + snd_pcm_hw_params_t* hwParams; + snd_pcm_sw_params_t* swParams; + + // prepare the hwParam recd + snd_pcm_hw_params_alloca(&hwParams); + memset(hwParams,0,snd_pcm_hw_params_sizeof()); + + + // load the hw parameter record + if((err = snd_pcm_hw_params_any(pcmH,hwParams)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error obtaining hw param record"); + else + { + if((err = snd_pcm_hw_params_set_rate_resample(pcmH,hwParams,0)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp,"Unable to disable the ALSA sample rate converter."); + + + if((err = snd_pcm_hw_params_set_channels(pcmH,hwParams,chCnt)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp,"Unable to set channel count to: %i",chCnt); + + + if((err = snd_pcm_hw_params_set_rate(pcmH,hwParams,srate,0)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to set sample rate to: %i",srate); + + + if((err = snd_pcm_hw_params_set_access(pcmH,hwParams,SND_PCM_ACCESS_RW_INTERLEAVED )) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to set access to: RW Interleaved"); + + // select the format width + int j; + int fmtN = sizeof(fmt)/sizeof(fmt[0]); + for(j=0; j= 0 ) + break; + + if( j == fmtN ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to set format to: S16"); + else + { + bits = snd_pcm_format_width(fmt[j]); // bits per sample + signFl = snd_pcm_format_signed(fmt[j]); + swapFl = !snd_pcm_format_cpu_endian(fmt[j]); + } + + sig_bits = snd_pcm_hw_params_get_sbits(hwParams); + + snd_pcm_uframes_t ps_min,ps_max; + if((err = snd_pcm_hw_params_get_period_size_min(hwParams,&ps_min,NULL)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to get the minimum period size."); + + if((err = snd_pcm_hw_params_get_period_size_max(hwParams,&ps_max,NULL)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to get the maximum period size."); + + if( periodFrameCnt < ps_min ) + periodFrameCnt = ps_min; + else + if( periodFrameCnt > ps_max ) + periodFrameCnt = ps_max; + + if((err = snd_pcm_hw_params_set_period_size_near(pcmH,hwParams,&periodFrameCnt,NULL)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to set period to %i.",periodFrameCnt); + + bufferFrameCnt = periodFrameCnt * periodsPerBuf + 1; + + if((err = snd_pcm_hw_params_set_buffer_size_near(pcmH,hwParams,&bufferFrameCnt)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Unable to set buffer to %i.",bufferFrameCnt); + + + // Note: snd_pcm_hw_params() automatically calls snd_pcm_prepare() + if((err = snd_pcm_hw_params(pcmH,hwParams)) < 0 ) + rc = _alsaSetupError(err,inputFl, drp, "Parameter application failed."); + + + //_reportActualParams( hwParams, inputFl, dr, srate, periodFrameCnt, bufferFrameCnt ); + } + + // prepare the sw param recd + snd_pcm_sw_params_alloca(&swParams); + memset(swParams,0,snd_pcm_sw_params_sizeof()); + + // load the sw param recd + if((err = snd_pcm_sw_params_current(pcmH,swParams)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error obtaining sw param record."); + else + { + + if((err = snd_pcm_sw_params_set_start_threshold(pcmH,swParams, inputFl ? 0x7fffffff : periodFrameCnt)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error seting the start threshold."); + + // setting the stop-threshold to twice the buffer frame count is intended to stop spurious + // XRUN states - it will also mean that there will have no direct way of knowing about a + // in/out buffer over/under run. + if((err = snd_pcm_sw_params_set_stop_threshold(pcmH,swParams,bufferFrameCnt*2)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error setting the stop threshold."); + + if((err = snd_pcm_sw_params_set_avail_min(pcmH,swParams,periodFrameCnt)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error setting the avail. min. setting."); + + if((err = snd_pcm_sw_params_set_tstamp_mode(pcmH,swParams,SND_PCM_TSTAMP_MMAP)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error setting the time samp mode."); + + if((err = snd_pcm_sw_params(pcmH,swParams)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error applying sw params."); + } + + // setup the callback + if( p->asyncFl ) + if((err = snd_async_add_pcm_handler(&drp->ahandler,pcmH,_cmApStaticAsyncHandler, drp )) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error assigning callback handler."); + + // get the actual frames per cycle + if((err = snd_pcm_hw_params_get_period_size(hwParams,&actFpC,&dir)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Unable to get the actual period."); + + // store the device handle + if( inputFl ) + { + drp->iBits = bits; + drp->iSigBits = sig_bits; + drp->iSignFl = signFl; + drp->iSwapFl = swapFl; + drp->iPcmH = pcmH; + drp->iBuf = memResizeZ( drp->iBuf, actFpC * drp->iChCnt ); + drp->iFpC = actFpC; + } + else + { + drp->oBits = bits; + drp->oSigBits = sig_bits; + drp->oSignFl = signFl; + drp->oSwapFl = swapFl; + drp->oPcmH = pcmH; + drp->oBuf = memResizeZ( drp->oBuf, actFpC * drp->oChCnt ); + drp->oFpC = actFpC; + } + + if( p->asyncFl == false ) + { + cwAssert( p->pollfdsCnt < p->pollfdsAllocCnt ); + + unsigned incrFdsCnt = 0; + unsigned fdsCnt = 0; + + // locate the pollfd associated with this device/direction + unsigned j; + for(j=0; jpollfdsCnt; j+=p->pollfdsDesc[j].fdsCnt) + if( p->pollfdsDesc[j].devPtr == drp && inputFl == p->pollfdsDesc[j].inputFl ) + break; + + // get the count of descriptors for this device/direction + fdsCnt = snd_pcm_poll_descriptors_count(pcmH); + + // if the device was not found + if( j == p->pollfdsCnt ) + { + j = p->pollfdsCnt; + incrFdsCnt = fdsCnt; + + // if the pollfds[] needs more memroy + if( p->pollfdsCnt + fdsCnt > p->pollfdsAllocCnt ) + { + p->pollfds = memResizeZ( p->pollfds, p->pollfdsCnt + fdsCnt ); + p->pollfdsDesc = memResizeZ( p->pollfdsDesc, p->pollfdsCnt + fdsCnt ); + p->pollfdsAllocCnt += fdsCnt; + } + } + + // get the poll descriptors for this device/dir + if( snd_pcm_poll_descriptors(pcmH,p->pollfds + j,fdsCnt) != 1 ) + rc = _alsaSetupError(0,inputFl,drp,"Poll descriptor assignment failed."); + else + { + // store the desc. record assicoated with the poll descriptor + p->pollfdsDesc[ j ].fdsCnt = fdsCnt; + p->pollfdsDesc[ j ].devPtr = drp; + p->pollfdsDesc[ j ].inputFl = inputFl; + } + + p->pollfdsCnt += incrFdsCnt; + } + printf("%s %s period:%i %i buffer:%i bits:%i sig_bits:%i\n",inputFl?"in ":"out",drp->nameStr,(unsigned)periodFrameCnt,(unsigned)actFpC,(unsigned)bufferFrameCnt,bits,sig_bits); + + } + //_dumpAlsaDevice(pcmH); + + } // end if + + } // end for + + return rc; + } + +#ifdef NOTDEF +#define TRY(e) while(e<0){ printf("LINE:%i ALSA ERROR:%s\n",__LINE__,snd_strerror(e)); exit(EXIT_FAILURE); } + + void open_device( const char* device_name, bool inputFl ) + { + snd_pcm_t *pcm_handle = NULL; + snd_pcm_hw_params_t *hw_params; + + + snd_pcm_uframes_t bs_min,bs_max,ps_min,ps_max; + unsigned rt_min,rt_max,ch_min,ch_max; + const char* ioLabel = inputFl ? "in" : "out"; + + + // Open the device + TRY( snd_pcm_open (&pcm_handle, device_name, inputFl ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, 0)); + + TRY( snd_pcm_hw_params_malloc (&hw_params) ); + TRY( snd_pcm_hw_params_any (pcm_handle, hw_params)); + + TRY( snd_pcm_hw_params_test_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE)); + + // get the sample rate range + TRY(snd_pcm_hw_params_get_rate_min(hw_params,&rt_min,NULL)); + TRY(snd_pcm_hw_params_get_rate_max(hw_params,&rt_max,NULL)); + TRY(snd_pcm_hw_params_get_channels_min(hw_params,&ch_min)); + TRY(snd_pcm_hw_params_get_channels_max(hw_params,&ch_max)); + + // set the basic device format - setting the format may influence the size of the possible + // buffer and period size + //TRY( snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)); + //TRY( snd_pcm_hw_params_set_format (pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE)); + //TRY( snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &srate, NULL)); + //TRY( snd_pcm_hw_params_set_channels(pcm_handle, hw_params, ch_cnt)); + + + // get the range of possible buffer and period sizes + TRY(snd_pcm_hw_params_get_buffer_size_min(hw_params,&bs_min)); + TRY(snd_pcm_hw_params_get_buffer_size_max(hw_params,&bs_max)); + TRY(snd_pcm_hw_params_get_period_size_min(hw_params,&ps_min,NULL)); + TRY(snd_pcm_hw_params_get_period_size_max(hw_params,&ps_max,NULL)); + + //printf("%s %s bs min:%u max:%u ps min:%u max:%u rate min:%u max:%u ch min:%u max:%u\n",device_name,ioLabel,bs_min,bs_max,ps_min,ps_max,rt_min,rt_max,ch_min,ch_max); + printf("%s %s rate min:%u max:%u ch min:%u max:%u\n",device_name,ioLabel,rt_min,rt_max,ch_min,ch_max); + + snd_pcm_hw_params_free(hw_params); + snd_pcm_close(pcm_handle); + } +#endif + + } + } + } +} + +cw::rc_t cw::audio::device::alsa::initialize(unsigned baseApDevIdx) +{ + rc_t rc = kOkRC; + int err; + int cardNum = -1; + + if((rc = finalize()) != kOkRC ) + return rc; + + recdSetup(); + + cmApRoot_t* p = &_cmApRoot; + //memset(p,0,sizeof(cmApRoot_t)); + p->asyncFl = false; + + // for each sound card + while(1) + { + snd_ctl_t* cardH; + char* cardNamePtr = NULL; + char* cardLongNamePtr = NULL; + int devNum = -1; + int devStrN = 31; + char devStr[devStrN+1]; + + + // get the next card handle + if((err = snd_card_next(&cardNum)) < 0 ) + { + return _alsaError(err,"Error getting sound card handle"); + } + + // if no more card's to get + if( cardNum < 0 ) + break; + + // get the card short name + if(((err = snd_card_get_name(cardNum,&cardNamePtr)) < 0) || (cardNamePtr == NULL)) + { + _alsaError(err,"Unable to get card name for card number %i",cardNum); + goto releaseCard; + } + + // get the card long name + if((err = snd_card_get_longname(cardNum,&cardLongNamePtr)) < 0 || cardLongNamePtr == NULL ) + { + _alsaError(err,"Unable to get long card name for card number %i",cardNum); + goto releaseCard; + } + + // form the device name for this card + if(snprintf(devStr,devStrN,"hw:%i",cardNum) > devStrN ) + { + _alsaError(0,"Device name is too long for buffer."); + goto releaseCard; + } + + // open the card device driver + if((err = snd_ctl_open(&cardH, devStr, 0)) < 0 ) + { + _alsaError(err,"Error opening sound card %i.",cardNum); + goto releaseCard; + } + + + // for each device on this card + while(1) + { + snd_pcm_info_t* info; + int subDevCnt = 1; + int i,j; + + // get the next device on this card + if((err = snd_ctl_pcm_next_device(cardH,&devNum)) < 0 ) + { + _alsaError(err,"Error gettign next device on card %i",cardNum); + break; + } + + // if no more devices to get + if( devNum < 0 ) + break; + + // allocate a pcmInfo record + snd_pcm_info_alloca(&info); + memset(info, 0, snd_pcm_info_sizeof()); + + // set the device to query + snd_pcm_info_set_device(info, devNum ); + + for(i=0; iasyncFl==false ) + { + p->pollfdsCnt = 0; + p->pollfdsAllocCnt = 2*p->devCnt; + p->pollfds = memAllocZ( p->pollfdsAllocCnt ); + p->pollfdsDesc = memAllocZ(p->pollfdsAllocCnt ); + + if((rc = thread::create(p->thH,_cmApThreadFunc,p)) != kOkRC ) + { + rc = cwLogError(rc,"Thread create failed."); + } + } + + return rc; + +} + +cw::rc_t cw::audio::device::alsa::finalize() +{ + cmApRoot_t* p = &_cmApRoot; + unsigned i; + rc_t rc = kOkRC; + + if( p->asyncFl==false ) + if((rc = thread::destroy(p->thH)) != kOkRC ) + { + rc = cwLogError(rc,"Thread destroy failed."); + } + + for(i=0; idevCnt; ++i) + { + _cmApDevShutdown(p,p->devArray+i,true); + _cmApDevShutdown(p,p->devArray+i,false); + +#ifdef IMPULSE_FN + if( p->devArray[i].recdIdx > 0 ) + { + const char* fn = "/home/kevin/temp/recd0.txt"; + FILE* fp = fopen(fn,"wt"); + if( fp != NULL ) + { + unsigned j; + for(j=0; jdevArray[i].recdIdx; ++j) + fprintf(fp,"%i\n",p->devArray[i].recdBuf[j]); + fclose(fp); + } + } + memRelease(p->devArray[i].recdBuf); +#endif + + memRelease(p->devArray[i].iBuf); + memRelease(p->devArray[i].oBuf); + + } + + memRelease(p->pollfds); + memRelease(p->pollfdsDesc); + + memRelease(p->devArray); + p->devAllocCnt = 0; + p->devCnt = 0; + + recdFree(); + //write_rec(2); + + + return rc; +} + +unsigned cw::audio::device::alsa::deviceCount() +{ + return _cmApRoot.devCnt; +} + +const char* cw::audio::device::alsa::deviceLabel( unsigned devIdx ) +{ + cwAssert(devIdx < deviceCount()); + return _cmApRoot.devArray[devIdx].descStr; + +} + +unsigned cw::audio::device::alsa::deviceChannelCount( unsigned devIdx, bool inputFl ) +{ + cwAssert(devIdx < deviceCount()); + return inputFl ? _cmApRoot.devArray[devIdx].iChCnt : _cmApRoot.devArray[devIdx].oChCnt; + +} + +double cw::audio::device::alsa::deviceSampleRate( unsigned devIdx ) +{ + cwAssert(devIdx < deviceCount()); + return (double)_cmApRoot.devArray[devIdx].srate; +} + +unsigned cw::audio::device::alsa::deviceFramesPerCycle( unsigned devIdx, bool inputFl ) +{ + cwAssert(devIdx < deviceCount()); + return _cmApRoot.devArray[devIdx].framesPerCycle; +} + +cw::rc_t cw::audio::device::alsa::deviceSetup( unsigned devIdx, double srate, unsigned framesPerCycle, device::cbFunc_t cbFunc, void* cbArg ) +{ + + cwAssert( devIdx < deviceCount()); + rc_t rc = kOkRC; + cmApRoot_t* p = &_cmApRoot; + cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx; + unsigned periodsPerBuf = kDfltPeriodsPerBuf; + + if( p->asyncFl == false ) + if((rc = thread::pause(p->thH,thread::kWaitFl | thread::kPauseFl)) != kOkRC ) + { + return cwLogError(rc,"Audio thread pause failed."); + } + + if((rc = _cmApDevSetup(drp, srate, framesPerCycle, periodsPerBuf )) == kOkRC ) + { + drp->srate = srate; + drp->framesPerCycle = framesPerCycle; + drp->periodsPerBuf = periodsPerBuf; + drp->cbPtr = cbFunc; + drp->userCbPtr = cbArg; + } + + return rc; +} + +cw::rc_t cw::audio::device::alsa::deviceStart( unsigned devIdx ) +{ + cwAssert( devIdx < deviceCount()); + int err; + cmApRoot_t* p = &_cmApRoot; + cmApDevRecd_t* drp = p->devArray + devIdx; + rc_t rc = kOkRC; + bool inputFl = true; + unsigned i; + + for(i=0; i<2; ++i,inputFl=!inputFl) + { + snd_pcm_t* pcmH = inputFl ? drp->iPcmH : drp->oPcmH; + + if( pcmH != NULL ) + { + + snd_pcm_state_t state = snd_pcm_state(pcmH); + + if( state != SND_PCM_STATE_RUNNING ) + { + unsigned chCnt = inputFl ? drp->iChCnt : drp->oChCnt; + unsigned frmCnt = inputFl ? drp->iFpC : drp->oFpC; + const char* ioLabel = inputFl ? "Input" : "Output"; + + //printf("%i %s state:%s %i %i\n",drp->devIdx, ioLabel,_pcmStateToString(snd_pcm_state(pcmH)),chCnt,frmCnt); + + // preparing may not always be necessary because the earlier call to snd_pcm_hw_params() + // may have left the device prepared - the redundant call however doesn't seem to hurt + if((err= snd_pcm_prepare(pcmH)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"Error preparing the %i device.",ioLabel); + else + { + + recdStart(drp,inputFl); + + if( inputFl == false ) + { + int j; + for(j=0; j<1; ++j) + if((err = _cmApWriteBuf( drp, pcmH, NULL, chCnt, frmCnt, drp->oBits, drp->oSigBits )) < 0 ) + { + rc = _alsaSetupError(err,inputFl,drp,"Write before start failed."); + break; + } + } + else + { + + if((err = snd_pcm_start(pcmH)) < 0 ) + rc = _alsaSetupError(err,inputFl,drp,"'%s' start failed.",ioLabel); + } + + // wait 500 microseconds between starting and stopping - this prevents + // input and output and other device callbacks from landing on top of + // each other - when this happens callbacks are dropped. + if( p->asyncFl ) + usleep(500); + + } + //printf("%i %s state:%s %i %i\n",drp->devIdx, ioLabel,_cmApPcmStateToString(snd_pcm_state(pcmH)),chCnt,frmCnt); + + } + + } + } + + if( p->asyncFl == false ) + { + rc_t rc0 = kOkRC; + if((rc0 = thread::unpause(p->thH)) != kOkRC ) + rc = _alsaError(rc0,"Audio thread start failed."); + } + return rc; + +} + +cw::rc_t cw::audio::device::alsa::deviceStop( unsigned devIdx ) +{ + int err; + rc_t rc = kOkRC; + cmApRoot_t* p = &_cmApRoot; + cmApDevRecd_t* drp = p->devArray + devIdx; + + if( drp->iPcmH != NULL ) + if((err = snd_pcm_drop(drp->iPcmH)) < 0 ) + rc = _alsaSetupError(err,true,drp,"Input stop failed."); + + if( drp->oPcmH != NULL ) + if((err = snd_pcm_drop(drp->oPcmH)) < 0 ) + rc = _alsaSetupError(err,false,drp,"Output stop failed."); + + if( p->asyncFl == false ) + { + rc_t rc0; + if((rc0 = thread::pause(p->thH,thread::kPauseFl)) != kOkRC ) + rc =_alsaError(rc0,"Audio thread pause failed."); + } + + return rc; +} + +bool cw::audio::device::alsa::deviceIsStarted( unsigned devIdx ) +{ + cwAssert( devIdx < deviceCount()); + + bool iFl = false; + bool oFl = false; + const cmApDevRecd_t* drp = _cmApRoot.devArray + devIdx; + + if( drp->iPcmH != NULL ) + iFl = snd_pcm_state(drp->iPcmH) == SND_PCM_STATE_RUNNING; + + if( drp->oPcmH != NULL ) + oFl = snd_pcm_state(drp->oPcmH) == SND_PCM_STATE_RUNNING; + + return iFl || oFl; +} + +//{ { label:alsaDevRpt } +//( +// Here's an example of generating a report of available +// ALSA devices. +//) + +//[ +void cw::audio::device::alsa::deviceReport() +{ + unsigned i; + for(i=0; i<_cmApRoot.devCnt; i++) + { + cwLogInfo("%i : ",i); + _cmApDevReport(_cmApRoot.devArray + i ); + } + +} +//] +//} + + + diff --git a/cwAudioPortAlsa.h b/cwAudioPortAlsa.h new file mode 100644 index 0000000..ea74ea0 --- /dev/null +++ b/cwAudioPortAlsa.h @@ -0,0 +1,53 @@ +#ifndef cwAudioPortAlsa_H +#define cwAudioPortAlsa_H + +namespace cw +{ + namespace audio + { + namespace device + { + namespace alsa + { + //{ { label: cmApAlsa kw:[ audio, device, rt ] } + // + //( + // ALSA audio device API + // + // This API is used by the cmAudioPort interface when + // the library is compiled for a Linux platform. + // + //) + + //[ + + rc_t initialize( unsigned baseApDevIdx ); + rc_t finalize(); + + rc_t deviceCount(); + const char* deviceLabel( unsigned devIdx ); + unsigned deviceChannelCount( unsigned devIdx, bool inputFl ); + double deviceSampleRate( unsigned devIdx ); + unsigned deviceFramesPerCycle( unsigned devIdx, bool inputFl ); + + rc_t deviceSetup( + unsigned devIdx, + double srate, + unsigned framesPerCycle, + device::cbFunc_t callbackPtr, + void* userCbPtr ); + + rc_t deviceStart( unsigned devIdx ); + rc_t deviceStop( unsigned devIdx ); + bool deviceIsStarted( unsigned devIdx ); + + void deviceReport( ); + + //] + //} + + } + } + } +} +#endif diff --git a/cwCommon.h b/cwCommon.h index c610668..149d11f 100644 --- a/cwCommon.h +++ b/cwCommon.h @@ -44,10 +44,6 @@ namespace cw typedef unsigned rc_t; - typedef struct handle_str - { - void* p; - } handle_t; template< typename T > struct handle @@ -56,8 +52,8 @@ namespace cw T* p = nullptr; void set(T* ptr) { this->p=ptr; } + void clear() { this->p=nullptr; } bool isValid() const { return this->p != nullptr; } - void release() { memRelease(p); p=nullptr; } }; } diff --git a/cwCommonImpl.h b/cwCommonImpl.h index 1cb6af6..80127c5 100644 --- a/cwCommonImpl.h +++ b/cwCommonImpl.h @@ -10,9 +10,11 @@ #include #include #include +#include #include // std::min,std::max -#include // std::forward -#include // std::numeric_limits< +#include // std::forward +#include // std::numeric_limits< +#include #if defined(cwLINUX) || defined(cwOSX) #define cwPOSIX_FILE_SYS @@ -46,7 +48,8 @@ namespace cw #define cwAssert(cond) while(1){ if(!(cond)){ cwLogFatal(kAssertFailRC,"Assert failed on condition:%s",#cond ); } break; } - template< typename H, typename T > T* handleToPtr( H h ) + template< typename H, typename T > + T* handleToPtr( H h ) { cwAssert( h.p != nullptr ); return h.p; diff --git a/cwLog.cpp b/cwLog.cpp index 6cf0238..e05b049 100644 --- a/cwLog.cpp +++ b/cwLog.cpp @@ -77,7 +77,7 @@ cw::rc_t cw::logMsg( logHandle_t h, unsigned level, const char* function, const if( n != -1 ) { - char msg[n+1]; + char msg[n+1]; // add 1 to allow space for the terminating zero int m = vsnprintf(msg,n+1,fmt,vl1); cwAssert(m==n); diff --git a/cwLog.h b/cwLog.h index 0ded120..7ee20da 100644 --- a/cwLog.h +++ b/cwLog.h @@ -49,8 +49,8 @@ namespace cw #define cwLogVDebugH(h,rc,fmt, vl) cw::logMsg( h, cw::kDebug_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, vl ) #define cwLogDebugH( h,rc,fmt,...) cw::logMsg( h, cw::kDebug_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, ##__VA_ARGS__ ) -#define cwLogVInfoH(h,rc,fmt, vl) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, vl ) -#define cwLogInfoH( h,rc,fmt,...) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, ##__VA_ARGS__ ) +#define cwLogVInfoH(h,fmt, vl) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, cw::kOkRC, fmt, vl ) +#define cwLogInfoH( h,fmt,...) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, cw::kOkRC, fmt, ##__VA_ARGS__ ) #define cwLogVWarningH(h,rc,fmt, vl) cw::logMsg( h, cw::kWarning_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, vl ) #define cwLogWarningH( h,rc,fmt,...) cw::logMsg( h, cw::kWarning_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, ##__VA_ARGS__ ) @@ -82,11 +82,9 @@ namespace cw #endif -#define cwLogVInfoRC(rc,fmt, vl) cwLogVInfoH( cw::logGlobalHandle(), (rc), (fmt), (vl) ) -#define cwLogInfoRC( rc,fmt,...) cwLogInfoH( cw::logGlobalHandle(), (rc), (fmt), ##__VA_ARGS__ ) -#define cwLogVInfo(fmt, vl) cwLogVInfoH( cw::logGlobalHandle(), cw::kOkRC, (fmt), (vl) ) -#define cwLogInfo( fmt,...) cwLogInfoH( cw::logGlobalHandle(), cw::kOkRC, (fmt), ##__VA_ARGS__ ) +#define cwLogVInfo(fmt, vl) cwLogVInfoH( cw::logGlobalHandle(), (fmt), (vl) ) +#define cwLogInfo( fmt,...) cwLogInfoH( cw::logGlobalHandle(), (fmt), ##__VA_ARGS__ ) #define cwLogVWarningRC(rc,fmt, vl) cwLogVWarningH( cw::logGlobalHandle(), (rc), (fmt), (vl) ) #define cwLogWarningRC( rc,fmt,...) cwLogWarningH( cw::logGlobalHandle(), (rc), (fmt), ##__VA_ARGS__ ) diff --git a/cwMem.cpp b/cwMem.cpp index 56857d3..2900b4b 100644 --- a/cwMem.cpp +++ b/cwMem.cpp @@ -9,15 +9,19 @@ namespace cw void* _memAlloc( void* p0, unsigned n, bool zeroFl ) { - void* p = nullptr; // ptr to new block - unsigned p0N = 0; // size of existing block + void* p = nullptr; // ptr to new block + unsigned p0N = 0; // size of existing block + unsigned* p0_1 = nullptr; // pointer to base of existing block + + n += sizeof(unsigned); // add space for the size of the block // if there is no existing block - if( p0 == nullptr ) - n += sizeof(unsigned); // add space for the size of the block - else + if( p0 != nullptr ) { - p0N = ((unsigned*)p0)[-1]; // get size of existing block + // get a pointer to the base of the exsting block + p0_1 = ((unsigned*)p0) - 1; + + p0N = p0_1[0]; // get size of existing block // if the block is shrinking if( p0N >= n ) @@ -29,7 +33,7 @@ namespace cw // if expanding then copy in data from existing block if( p0 != nullptr ) { - memcpy(p,p0,p0N); + memcpy(p,p0_1,p0N); memFree(p0); // free the existing block } @@ -40,7 +44,7 @@ namespace cw // get pointer to base of new block unsigned* p1 = static_cast(p); - p1[0] = p0N + n; // set size of new block + p1[0] = n; // set size of new block // advance past the block size and return return p1+1; diff --git a/cwMidi.cpp b/cwMidi.cpp new file mode 100644 index 0000000..85124f9 --- /dev/null +++ b/cwMidi.cpp @@ -0,0 +1,283 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwMidi.h" + +namespace cw { + namespace midi { + + typedef struct statusDesc_str + { + byte_t status; + byte_t byteCnt; + const char* label; + } statusDesc_t; + + statusDesc_t _statusDescArray[] = + { + // channel messages + { kNoteOffMdId, 2, "nof" }, + { kNoteOnMdId, 2, "non" }, + { kPolyPresMdId,2, "ppr" }, + { kCtlMdId, 2, "ctl" }, + { kPgmMdId, 1, "pgm" }, + { kChPresMdId, 1, "cpr" }, + { kPbendMdId, 2, "pb" }, + + + { kSysExMdId, kInvalidMidiByte,"sex" }, + + // system common + { kSysComMtcMdId, 1, "mtc" }, + { kSysComSppMdId, 2, "spp" }, + { kSysComSelMdId, 1, "sel" }, + { kSysComUndef0MdId, 0, "cu0" }, + { kSysComUndef1MdId, 0, "cu1" }, + { kSysComTuneMdId, 0, "tun" }, + { kSysComEoxMdId, 0, "eox" }, + + // system real-time + { kSysRtClockMdId, 0, "clk" }, + { kSysRtUndef0MdId,0, "ud0" }, + { kSysRtStartMdId, 0, "beg" }, + { kSysRtContMdId, 0, "cnt" }, + { kSysRtStopMdId, 0, "end" }, + { kSysRtUndef1MdId,0, "ud1" }, + { kSysRtSenseMdId, 0, "sns" }, + { kSysRtResetMdId, 0, "rst" }, + + { kInvalidStatusMdId, kInvalidMidiByte, "ERR" } + }; + + statusDesc_t _metaStatusDescArray[] = + { + { kSeqNumbMdId, 2, "seqn" }, + { kTextMdId, 0xff, "text" }, + { kCopyMdId, 0xff, "copy" }, + { kTrkNameMdId, 0xff, "name" }, + { kInstrNameMdId, 0xff, "instr" }, + { kLyricsMdId, 0xff, "lyric" }, + { kMarkerMdId, 0xff, "mark" }, + { kCuePointMdId, 0xff, "cue" }, + { kMidiChMdId, 1, "chan" }, + { kEndOfTrkMdId, 0, "eot" }, + { kTempoMdId, 3, "tempo" }, + { kSmpteMdId, 5, "smpte" }, + { kTimeSigMdId, 4, "tsig" }, + { kKeySigMdId, 2, "ksig" }, + { kSeqSpecMdId, 0xff, "seqs" }, + { kInvalidMetaMdId, kInvalidMidiByte, "ERROR"} + }; + + statusDesc_t _pedalLabel[] = + { + { kSustainCtlMdId, 0, "sustn" }, + { kPortamentoCtlMdId, 0, "porta" }, + { kSostenutoCtlMdId, 0, "sostn" }, + { kSoftPedalCtlMdId, 0, "soft" }, + { kLegatoCtlMdId, 0, "legat" }, + { kInvalidMidiByte, kInvalidMidiByte, "ERROR"} + }; + } +} + +//==================================================================================================== + +const char* cw::midi::statusToLabel( byte_t status ) +{ + unsigned i; + + if( !isStatus(status) ) + return NULL; + + // remove the channel value from ch msg status bytes + if( isChStatus(status) ) + status &= 0xf0; + + for(i=0; _statusDescArray[i].status != kInvalidStatusMdId; ++i) + if( _statusDescArray[i].status == status ) + return _statusDescArray[i].label; + + return _statusDescArray[i].label; +} + +const char* cw::midi::metaStatusToLabel( byte_t metaStatus ) +{ + int i; + for(i=0; _metaStatusDescArray[i].status != kInvalidMetaMdId; ++i) + if( _metaStatusDescArray[i].status == metaStatus ) + break; + + return _metaStatusDescArray[i].label; +} + +const char* cw::midi::pedalLabel( byte_t d0 ) +{ + int i; + for(i=0; _pedalLabel[i].status != kInvalidMidiByte; ++i) + if( _pedalLabel[i].status == d0 ) + break; + + return _pedalLabel[i].label; +} + +cw::midi::byte_t cw::midi::statusToByteCount( byte_t status ) +{ + unsigned i; + + if( !isStatus(status) ) + return kInvalidMidiByte; + + // remove the channel value from ch msg status bytes + if( isChStatus(status) ) + status &= 0xf0; + + for(i=0; _statusDescArray[i].status != kInvalidStatusMdId; ++i) + if( _statusDescArray[i].status == status ) + return _statusDescArray[i].byteCnt; + + assert(0); + + return 0; +} + +unsigned cw::midi::to14Bits( byte_t d0, byte_t d1 ) +{ + unsigned val = d0; + val <<= 7; + val += d1; + return val; +} + +void cw::midi::split14Bits( unsigned v, byte_t& d0Ref, byte_t& d1Ref ) +{ + d0Ref = (v & 0x3f80) >> 7; + d1Ref = v & 0x7f; +} + +int cw::midi::toPbend( byte_t d0, byte_t d1 ) +{ + int v = to14Bits(d0,d1); + return v - 8192; +} + +void cw::midi::splitPbend( int v, byte_t& d0Ref, byte_t& d1Ref ) +{ + unsigned uv = v + 8192; + split14Bits(uv,d0Ref,d1Ref); +} + +//==================================================================================================== +const char* cw::midi::midiToSciPitch( byte_t pitch, char* label, unsigned labelCharCnt ) +{ + static char buf[ kMidiSciPitchCharCnt ]; + + if( label == NULL || labelCharCnt == 0 ) + { + label = buf; + labelCharCnt = kMidiSciPitchCharCnt; + } + + assert( labelCharCnt >= kMidiSciPitchCharCnt ); + + if( /*pitch < 0 ||*/ pitch > 127 ) + { + label[0] = 0; + return label; + } + + assert( labelCharCnt >= 5 && /*pitch >= 0 &&*/ pitch <= 127 ); + + char noteV[] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' }; + char shrpV[] = { ' ', '#', ' ', '#', ' ', ' ', '#', ' ', '#', ' ', '#', ' ' }; + int octave = (pitch / 12)-1; + unsigned noteIdx = pitch % 12; + char noteCh = noteV[ noteIdx ]; + char sharpCh = shrpV[ noteIdx ]; + unsigned idx = 1; + + label[labelCharCnt-1] = 0; + label[0] = noteCh; + + if( sharpCh != ' ' ) + { + label[1] = sharpCh; + idx = 2; + } + + assert( -1 <= octave && octave <= 9); + + snprintf(label+idx,kMidiSciPitchCharCnt-idx-1,"%i",octave); + + return label; +} + + +cw::midi::byte_t cw::midi::sciPitchToMidiPitch( char pitch, int acc, int octave ) +{ + int idx = -1; + + switch(tolower(pitch)) + { + case 'a': idx = 9; break; + case 'b': idx = 11; break; + case 'c': idx = 0; break; + case 'd': idx = 2; break; + case 'e': idx = 4; break; + case 'f': idx = 5; break; + case 'g': idx = 7; break; + default: + return kInvalidMidiPitch; + } + + unsigned rv = (octave*12) + idx + acc + 12; + + if( rv <= 127 ) + return rv; + + return kInvalidMidiPitch; + +} + +cw::midi::byte_t cw::midi::sciPitchToMidi( const char* sciPitchStr ) +{ + const char* cp = sciPitchStr; + bool sharpFl = false; + bool flatFl = false; + int octave; + int acc = 0; + + if( sciPitchStr==NULL || strlen(sciPitchStr) > 5 ) + return kInvalidMidiPitch; + + // skip over leading letter + ++cp; + + if( !(*cp) ) + return kInvalidMidiPitch; + + + if((sharpFl = *cp=='#') == true ) + acc = 1; + else + if((flatFl = *cp=='b') == true ) + acc = -1; + + if( sharpFl || flatFl ) + { + ++cp; + + if( !(*cp) ) + return kInvalidMidiPitch; + } + + if( isdigit(*cp) == false && *cp!='-' ) + return kInvalidMidiPitch; + + octave = atoi(cp); + + + return sciPitchToMidiPitch( *sciPitchStr, acc, octave ); + +} diff --git a/cwMidi.h b/cwMidi.h new file mode 100644 index 0000000..b708654 --- /dev/null +++ b/cwMidi.h @@ -0,0 +1,174 @@ +#ifndef cwMidi_h +#define cwMidi_h + +namespace cw +{ + namespace midi + { + enum + { + kMidiChCnt = 16, + kInvalidMidiByte = 128, + kMidiNoteCnt = kInvalidMidiByte, + kMidiCtlCnt = kInvalidMidiByte, + kMidiPgmCnt = kInvalidMidiByte, + kInvalidMidiPitch = kInvalidMidiByte, + kInvalidMidiVelocity = kInvalidMidiByte, + kInvalidMidiCtl = kInvalidMidiByte, + kInvalidMidiPgm = kInvalidMidiByte, + kMidiSciPitchCharCnt = 5 // A#-1 + }; + + + // MIDI status bytes + enum + { + kInvalidStatusMdId = 0x00, + kNoteOffMdId = 0x80, + kNoteOnMdId = 0x90, + kPolyPresMdId = 0xa0, + kCtlMdId = 0xb0, + kPgmMdId = 0xc0, + kChPresMdId = 0xd0, + kPbendMdId = 0xe0, + kSysExMdId = 0xf0, + + kSysComMtcMdId = 0xf1, + kSysComSppMdId = 0xf2, + kSysComSelMdId = 0xf3, + kSysComUndef0MdId = 0xf4, + kSysComUndef1MdId = 0xf5, + kSysComTuneMdId = 0xf6, + kSysComEoxMdId = 0xf7, + + kSysRtClockMdId = 0xf8, + kSysRtUndef0MdId = 0xf9, + kSysRtStartMdId = 0xfa, + kSysRtContMdId = 0xfb, + kSysRtStopMdId = 0xfc, + kSysRtUndef1MdId = 0xfd, + kSysRtSenseMdId = 0xfe, + kSysRtResetMdId = 0xff, + kMetaStId = 0xff, + + kSeqNumbMdId = 0x00, + kTextMdId = 0x01, + kCopyMdId = 0x02, + kTrkNameMdId = 0x03, + kInstrNameMdId = 0x04, + kLyricsMdId = 0x05, + kMarkerMdId = 0x06, + kCuePointMdId = 0x07, + kMidiChMdId = 0x20, + kEndOfTrkMdId = 0x2f, + kTempoMdId = 0x51, + kSmpteMdId = 0x54, + kTimeSigMdId = 0x58, + kKeySigMdId = 0x59, + kSeqSpecMdId = 0x7f, + kInvalidMetaMdId = 0x80, + + kSustainCtlMdId = 0x40, + kPortamentoCtlMdId = 0x41, + kSostenutoCtlMdId = 0x42, + kSoftPedalCtlMdId = 0x43, + kLegatoCtlMdId = 0x44 + + }; + + + typedef unsigned char byte_t; + typedef struct timespec timestamp_t; + + //=============================================================================================== + // Utility Functions + // + + template< typename T> bool isStatus( T s ) { return (kNoteOffMdId <= (s) /*&& ((unsigned)(s)) <= kSysRtResetMdId*/ ); } + template< typename T> bool isChStatus( T s ) { return (kNoteOffMdId <= (s) && (s) < kSysExMdId); } + + template< typename T> bool isNoteOn( T s ) { return ( kNoteOnMdId <= (s) && (s) <= (kNoteOnMdId + kMidiChCnt) ); } + template< typename T> bool isNoteOff( T s, T d1 ) { return ( (cmMidiIsNoteOn(s) && (d1)==0) || (kNoteOffMdId <= (s) && (s) <= (kNoteOffMdId + kMidiChCnt)) ); } + template< typename T> bool isCtl( T s ) { return ( kCtlMdId <= (s) && (s) <= (kCtlMdId + kMidiChCnt) ); } + + template< typename T> bool isSustainPedal( T s, T d0 ) { return ( kCtlMdId <= (s) && (s) <= (kCtlMdId + kMidiChCnt) && (d0)== kSustainCtlMdId ); } + template< typename T> bool isSustainPedalDown( T s, T d0, T d1) { return ( cmMidiIsSustainPedal(s,d0) && (d1)>=64 ); } + template< typename T> bool isSustainPedalUp( T s, T d0, T d1) { return ( cmMidiIsSustainPedal(s,d0) && (d1)<64 ); } + + template< typename T> bool isSostenutoPedal( T s, T d0 ) { return ( kCtlMdId <= (s) && (s) <= (kCtlMdId + kMidiChCnt) && (d0)== kSostenutoCtlMdId ); } + template< typename T> bool isSostenutoPedalDown( T s, T d0, T d1) { return ( cmMidiIsSostenutoPedal(s,d0) && (d1)>=64 ); } + template< typename T> bool isSostenutoPedalUp( T s, T d0, T d1) { return ( cmMidiIsSostenutoPedal(s,d0) && (d1)<64 ); } + + template< typename T> bool isPedal( T s, T d0 ) { return ( kCtlMdId <= (s) && (s) <= (kCtlMdId + kMidiChCnt) && (d0)>=kSustainCtlMdId && (d0)<=kLegatoCtlMdId ); } + template< typename T> bool isPedalDown( T s, T d0, T d1 ) { return ( cmMidiIsPedal(s,d0) && (d1)>=64 ); } + template< typename T> bool isPedalUp( T s, T d0, T d1 ) { return ( cmMidiIsPedal(s,d0) && (d1)<64 ); } + + + const char* statusToLabel( byte_t status ); + const char* metaStatusToLabel( byte_t metaStatus ); + const char* pedalLabel( byte_t d0 ); + + // Returns kInvalidMidiByte if status is not a valid status byte + byte_t statusToByteCount( byte_t status ); + + unsigned to14Bits( byte_t d0, byte_t d1 ); + void split14Bits( unsigned v, byte_t& d0Ref, byte_t& d1Ref ); + int toPbend( byte_t d0, byte_t d1 ); + void splitPbend( int v, byte_t& d0Ref, byte_t& d1Ref ); + + //=============================================================================================== + // MIDI Communication data types + // + + typedef struct msg_str + { + //unsigned deltaUs; // time since last MIDI msg in microseconds + timestamp_t timeStamp; + byte_t status; // midi status byte + byte_t d0; // midi data byte 0 + byte_t d1; // midi data byte 1 + byte_t pad; + } msg_t; + + typedef struct packet_str + { + void* cbDataPtr; // application supplied reference value from mdParserCreate() + unsigned devIdx; // the device the msg originated from + unsigned portIdx; // the port index on the source device + msg_t* msgArray; // pointer to an array of 'msgCnt' mdMsg records or NULL if sysExMsg is non-NULL + byte_t* sysExMsg; // pointer to a sys-ex msg or NULL if msgArray is non-NULL (see note below) + unsigned msgCnt; // count of mdMsg records or sys-ex bytes + } packet_t; + + // Notes: If the sys-ex message can be contained in a single msg then + // then the first msg byte is kSysExMdId and the last is kSysComEoxMdId. + // If the sys-ex message is broken into multiple pieces then only the + // first will begin with kSysExMdId and the last will end with kSysComEoxMdId. + + + // If label is NULL or labelCharCnt==0 then a pointer to an internal static + // buffer is returned. If label[] is given the it + // should have at least 5 (kMidiPitchCharCnt) char's (including the terminating zero). + // If 'pitch' is outside of the range 0-127 then a blank string is returned. + const char* midiToSciPitch( byte_t pitch, char* label, unsigned labelCharCnt ); + + // Convert a scientific pitch to MIDI pitch. acc == 1 == sharp, acc == -1 == flat. + // The pitch character must be in the range 'A' to 'G'. Upper or lower case is valid. + // Return kInvalidMidiPitch if the arguments are not valid. + byte_t sciPitchToMidiPitch( char pitch, int acc, int octave ); + + + // Scientific pitch string: [A-Ga-g][#b][#] where # may be -1 to 9. + // Return kInvalidMidiPitch if sciPtichStr does not contain a valid + // scientific pitch string. This function will convert C-1 to G9 to + // valid MIDI pitch values 0 to 127. Scientific pitch strings outside + // of this range will be returned as kInvalidMidiPitch. + byte_t sciPitchToMidi( const char* sciPitchStr ); + + } +} + + + + +#endif diff --git a/cwMidiAlsa.cpp b/cwMidiAlsa.cpp new file mode 100644 index 0000000..ac9e24d --- /dev/null +++ b/cwMidiAlsa.cpp @@ -0,0 +1,892 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwMidi.h" +#include "cwTime.h" +#include "cwTextBuf.h" +#include "cwMidiPort.h" +#include "cwThread.h" + +#include + +namespace cw +{ + namespace midi + { + namespace device + { + typedef struct + { + bool inputFl; // true if this an input port + char* nameStr; // string label of this device + unsigned alsa_type; // ALSA type flags from snd_seq_port_info_get_type() + unsigned alsa_cap; // ALSA capability flags from snd_seq_port_info_get_capability() + snd_seq_addr_t alsa_addr; // ALSA client/port address for this port + parser::handle_t parserH; // interface to the client callback function for this port + } port_t; + + // MIDI devices + typedef struct + { + char* nameStr; // string label for this device + unsigned iPortCnt; // input ports on this device + port_t* iPortArray; + unsigned oPortCnt; // output ports on this device + port_t* oPortArray; + unsigned char clientId; // ALSA client id (all ports on this device use use this client id in their address) + + } dev_t; + + typedef struct + { + unsigned devCnt; // MIDI devices attached to this computer + dev_t* devArray; + + cbFunc_t cbFunc; // MIDI input application callback + void* cbDataPtr; + + snd_seq_t* h; // ALSA system sequencer handle + + snd_seq_addr_t alsa_addr; // ALSA client/port address representing the application + int alsa_queue; // ALSA device queue + thread::handle_t thH; // MIDI input listening thread + + int alsa_fdCnt; // MIDI input driver file descriptor array + struct pollfd* alsa_fd; + + dev_t* prvRcvDev; // the last device and port to rcv MIDI + port_t* prvRcvPort; + + unsigned prvTimeMicroSecs; // time of last recognized event in microseconds + unsigned eventCnt; // count of recognized events + + time::spec_t baseTimeStamp; + + } cmMpRoot_t; + + cmMpRoot_t* _cmMpRoot = NULL; + + rc_t _cmMpErrMsgV(rc_t rc, int alsaRc, const char* fmt, va_list vl ) + { + if( alsaRc < 0 ) + cwLogError(kOpFailRC,"ALSA Error:%i %s",alsaRc,snd_strerror(alsaRc)); + + return cwLogVError(rc,fmt,vl); + } + + rc_t _cmMpErrMsg(rc_t rc, int alsaRc, const char* fmt, ... ) + { + va_list vl; + va_start(vl,fmt); + rc = _cmMpErrMsgV(rc,alsaRc,fmt,vl); + va_end(vl); + return rc; + } + + unsigned _cmMpGetPortCnt( snd_seq_t* h, snd_seq_port_info_t* pip, bool inputFl ) + { + unsigned i = 0; + + snd_seq_port_info_set_port(pip,-1); + + while( snd_seq_query_next_port(h,pip) == 0) + if( cwIsFlag(snd_seq_port_info_get_capability(pip),inputFl?SND_SEQ_PORT_CAP_READ:SND_SEQ_PORT_CAP_WRITE) ) + ++i; + + return i; + } + + dev_t* _cmMpClientIdToDev( int clientId ) + { + cmMpRoot_t* p = _cmMpRoot; + unsigned i; + for(i=0; idevCnt; ++i) + if( p->devArray[i].clientId == clientId ) + return p->devArray + i; + + return NULL; + } + + port_t* _cmMpInPortIdToPort( dev_t* dev, int portId ) + { + unsigned i; + + for(i=0; iiPortCnt; ++i) + if( dev->iPortArray[i].alsa_addr.port == portId ) + return dev->iPortArray + i; + + return NULL; + } + + + void _cmMpSplit14Bits( unsigned v, byte_t* d0, byte_t* d1 ) + { + *d0 = (v & 0x3f80) >> 7; + *d1 = v & 0x7f; + } + + rc_t cmMpPoll() + { + rc_t rc = kOkRC; + cmMpRoot_t* p = _cmMpRoot; + int timeOutMs = 50; + + snd_seq_event_t *ev; + + if (poll(p->alsa_fd, p->alsa_fdCnt, timeOutMs) > 0) + { + int rc = 1; + + do + { + rc = snd_seq_event_input(p->h,&ev); + + // if no input + if( rc == -EAGAIN ) + break; + + // if input buffer overrun + if( rc == -ENOSPC ) + break; + + // get the device this event arrived from + if( p->prvRcvDev==NULL || p->prvRcvDev->clientId != ev->source.client ) + p->prvRcvDev = _cmMpClientIdToDev(ev->source.client); + + // get the port this event arrived from + if( p->prvRcvDev != NULL && (p->prvRcvPort==NULL || p->prvRcvPort->alsa_addr.port != ev->source.port) ) + p->prvRcvPort = _cmMpInPortIdToPort(p->prvRcvDev,ev->source.port); + + if( p->prvRcvDev == NULL || p->prvRcvPort == NULL ) + continue; + + //printf("%i %x\n",ev->type,ev->type); + //printf("dev:%i port:%i ch:%i %i\n",ev->source.client,ev->source.port,ev->data.note.channel,ev->data.note.note); + + unsigned microSecs1 = (ev->time.time.tv_sec * 1000000) + (ev->time.time.tv_nsec/1000); + //unsigned deltaMicroSecs = p->prvTimeMicroSecs==0 ? 0 : microSecs1 - p->prvTimeMicroSecs; + byte_t d0 = 0xff; + byte_t d1 = 0xff; + byte_t status = 0; + + switch(ev->type) + { + // + // MIDI Channel Messages + // + + case SND_SEQ_EVENT_NOTEON: + status = kNoteOnMdId; + d0 = ev->data.note.note; + d1 = ev->data.note.velocity; + //printf("%s (%i : %i) (%i)\n", snd_seq_ev_is_abstime(ev)?"abs":"rel",ev->time.time.tv_sec,ev->time.time.tv_nsec, deltaMicroSecs/1000); + break; + + case SND_SEQ_EVENT_NOTEOFF: + status = kNoteOffMdId; + d0 = ev->data.note.note; + d1 = ev->data.note.velocity; + break; + + case SND_SEQ_EVENT_KEYPRESS: + status = kPolyPresMdId; + d0 = ev->data.note.note; + d1 = ev->data.note.velocity; + break; + + case SND_SEQ_EVENT_PGMCHANGE: + status = kPgmMdId; + d0 = ev->data.control.param; + d1 = 0xff; + break; + + case SND_SEQ_EVENT_CHANPRESS: + status = kChPresMdId; + d0 = ev->data.control.param; + d1 = 0xff; + break; + + case SND_SEQ_EVENT_CONTROLLER: + status = kCtlMdId; + d0 = ev->data.control.param; + d1 = ev->data.control.value; + break; + + case SND_SEQ_EVENT_PITCHBEND: + _cmMpSplit14Bits(ev->data.control.value + 8192, &d0, &d1 ); + status = kPbendMdId; + break; + + // + // MIDI System Common Messages + // + case SND_SEQ_EVENT_QFRAME: + status = kSysComMtcMdId; + d0 = ev->data.control.value; + break; + + case SND_SEQ_EVENT_SONGPOS: + _cmMpSplit14Bits(ev->data.control.value, &d0, &d1 ); + status = kSysComSppMdId; + break; + + case SND_SEQ_EVENT_SONGSEL: + status = kSysComSelMdId; + d0 = ev->data.control.value; + break; + + case SND_SEQ_EVENT_TUNE_REQUEST: + status = kSysComTuneMdId; + break; + + // + // MIDI System Real-time Messages + // + case SND_SEQ_EVENT_CLOCK: status = kSysRtClockMdId; break; + case SND_SEQ_EVENT_START: status = kSysRtStartMdId; break; + case SND_SEQ_EVENT_CONTINUE: status = kSysRtContMdId; break; + case SND_SEQ_EVENT_STOP: status = kSysRtStopMdId; break; + case SND_SEQ_EVENT_SENSING: status = kSysRtSenseMdId; break; + case SND_SEQ_EVENT_RESET: status = kSysRtResetMdId; break; + + } + + if( status != 0 ) + { + byte_t ch = ev->data.note.channel; + time::spec_t ts; + ts.tv_sec = p->baseTimeStamp.tv_sec + ev->time.time.tv_sec; + ts.tv_nsec = p->baseTimeStamp.tv_nsec + ev->time.time.tv_nsec; + if( ts.tv_nsec > 1000000000 ) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec += 1; + } + + //printf("MIDI: %ld %ld : 0x%x %i %i\n",ts.tv_sec,ts.tv_nsec,status,d0,d1); + + parser::midiTriple(p->prvRcvPort->parserH, &ts, status | ch, d0, d1 ); + + p->prvTimeMicroSecs = microSecs1; + p->eventCnt += 1; + } + + }while( snd_seq_event_input_pending(p->h,0)); + + parser::transmit(p->prvRcvPort->parserH); + } + + return rc; + } + + + bool _threadCbFunc(void* param) + { + cmMpPoll(); + return true; + } + + rc_t _cmMpAllocStruct( cmMpRoot_t* p, const char* appNameStr, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt ) + { + rc_t rc = kOkRC; + snd_seq_client_info_t* cip = NULL; + snd_seq_port_info_t* pip = NULL; + snd_seq_port_subscribe_t *subs = NULL; + unsigned i,j,k,arc; + + // alloc the subscription recd on the stack + snd_seq_port_subscribe_alloca(&subs); + + // alloc the client recd + if((arc = snd_seq_client_info_malloc(&cip)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA seq client info allocation failed."); + goto errLabel; + } + + // alloc the port recd + if((arc = snd_seq_port_info_malloc(&pip)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA seq port info allocation failed."); + goto errLabel; + } + + if((p->alsa_queue = snd_seq_alloc_queue(p->h)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,p->alsa_queue,"ALSA queue allocation failed."); + goto errLabel; + } + + // Set arbitrary tempo (mm=100) and resolution (240) (FROM RtMidi.cpp) + /* + snd_seq_queue_tempo_t *qtempo; + snd_seq_queue_tempo_alloca(&qtempo); + snd_seq_queue_tempo_set_tempo(qtempo, 600000); + snd_seq_queue_tempo_set_ppq(qtempo, 240); + snd_seq_set_queue_tempo(p->h, p->alsa_queue, qtempo); + snd_seq_drain_output(p->h); + */ + + // setup the client port + snd_seq_set_client_name(p->h,appNameStr); + snd_seq_port_info_set_client(pip, p->alsa_addr.client = snd_seq_client_id(p->h) ); + snd_seq_port_info_set_name(pip,cwStringNullGuard(appNameStr)); + snd_seq_port_info_set_capability(pip,SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_DUPLEX | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE ); + snd_seq_port_info_set_type(pip, SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC ); + + snd_seq_port_info_set_midi_channels(pip, 16); + + // cfg for real-time time stamping + snd_seq_port_info_set_timestamping(pip, 1); + snd_seq_port_info_set_timestamp_real(pip, 1); + snd_seq_port_info_set_timestamp_queue(pip, p->alsa_queue); + + // create the client port + if((p->alsa_addr.port = snd_seq_create_port(p->h,pip)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,p->alsa_addr.port,"ALSA client port creation failed."); + goto errLabel; + } + + + p->devCnt = 0; + + // determine the count of devices + snd_seq_client_info_set_client(cip, -1); + while( snd_seq_query_next_client(p->h,cip) == 0) + p->devCnt += 1; + + // allocate the device array + p->devArray = memAllocZ(p->devCnt); + + // fill in each device record + snd_seq_client_info_set_client(cip, -1); + for(i=0; snd_seq_query_next_client(p->h,cip)==0; ++i) + { + assert(idevCnt); + + int client = snd_seq_client_info_get_client(cip); + const char* name = snd_seq_client_info_get_name(cip); + + // initalize the device record + p->devArray[i].nameStr = memDuplStr(cwStringNullGuard(name)); + p->devArray[i].iPortCnt = 0; + p->devArray[i].oPortCnt = 0; + p->devArray[i].iPortArray = NULL; + p->devArray[i].oPortArray = NULL; + p->devArray[i].clientId = client; + + + snd_seq_port_info_set_client(pip,client); + snd_seq_port_info_set_port(pip,-1); + + // determine the count of in/out ports on this device + while( snd_seq_query_next_port(p->h,pip) == 0 ) + { + unsigned caps = snd_seq_port_info_get_capability(pip); + + if( cwIsFlag(caps,SND_SEQ_PORT_CAP_READ) ) + p->devArray[i].iPortCnt += 1; + + if( cwIsFlag(caps,SND_SEQ_PORT_CAP_WRITE) ) + p->devArray[i].oPortCnt += 1; + + } + + // allocate the device port arrays + if( p->devArray[i].iPortCnt > 0 ) + p->devArray[i].iPortArray = memAllocZ(p->devArray[i].iPortCnt); + + if( p->devArray[i].oPortCnt > 0 ) + p->devArray[i].oPortArray = memAllocZ(p->devArray[i].oPortCnt); + + + snd_seq_port_info_set_client(pip,client); // set the ports client id + snd_seq_port_info_set_port(pip,-1); + + // fill in the port information + for(j=0,k=0; snd_seq_query_next_port(p->h,pip) == 0; ) + { + const char* port = snd_seq_port_info_get_name(pip); + unsigned type = snd_seq_port_info_get_type(pip); + unsigned caps = snd_seq_port_info_get_capability(pip); + snd_seq_addr_t addr = *snd_seq_port_info_get_addr(pip); + + if( cwIsFlag(caps,SND_SEQ_PORT_CAP_READ) ) + { + assert(jdevArray[i].iPortCnt); + p->devArray[i].iPortArray[j].inputFl = true; + p->devArray[i].iPortArray[j].nameStr = memDuplStr(cwStringNullGuard(port)); + p->devArray[i].iPortArray[j].alsa_type = type; + p->devArray[i].iPortArray[j].alsa_cap = caps; + p->devArray[i].iPortArray[j].alsa_addr = addr; + parser::create(p->devArray[i].iPortArray[j].parserH, i, j, cbFunc, cbDataPtr, parserBufByteCnt); + + // port->app + snd_seq_port_subscribe_set_sender(subs, &addr); + snd_seq_port_subscribe_set_dest(subs, &p->alsa_addr); + snd_seq_port_subscribe_set_queue(subs, 1); + snd_seq_port_subscribe_set_time_update(subs, 1); + snd_seq_port_subscribe_set_time_real(subs, 1); + if((arc = snd_seq_subscribe_port(p->h, subs)) < 0) + rc = _cmMpErrMsg(kOpFailRC,arc,"Input port to app. subscription failed on port '%s'.",cwStringNullGuard(port)); + + ++j; + } + + if( cwIsFlag(caps,SND_SEQ_PORT_CAP_WRITE) ) + { + assert(kdevArray[i].oPortCnt); + p->devArray[i].oPortArray[k].inputFl = false; + p->devArray[i].oPortArray[k].nameStr = memDuplStr(cwStringNullGuard(port)); + p->devArray[i].oPortArray[k].alsa_type = type; + p->devArray[i].oPortArray[k].alsa_cap = caps; + p->devArray[i].oPortArray[k].alsa_addr = addr; + + // app->port connection + snd_seq_port_subscribe_set_sender(subs, &p->alsa_addr); + snd_seq_port_subscribe_set_dest( subs, &addr); + if((arc = snd_seq_subscribe_port(p->h, subs)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"App to output port subscription failed on port '%s'.",cwStringNullGuard(port)); + + ++k; + } + } + } + + errLabel: + if( pip != NULL) + snd_seq_port_info_free(pip); + + if( cip != NULL ) + snd_seq_client_info_free(cip); + + return rc; + + } + + void _cmMpReportPort( textBuf::handle_t tbH, const port_t* port ) + { + textBuf::print( tbH," client:%i port:%i %s caps:(",port->alsa_addr.client,port->alsa_addr.port,port->nameStr); + if( port->alsa_cap & SND_SEQ_PORT_CAP_READ ) textBuf::print( tbH,"Read " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_WRITE ) textBuf::print( tbH,"Writ " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SYNC_READ ) textBuf::print( tbH,"Syrd " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SYNC_WRITE ) textBuf::print( tbH,"Sywr " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_DUPLEX ) textBuf::print( tbH,"Dupl " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SUBS_READ ) textBuf::print( tbH,"Subr " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SUBS_WRITE ) textBuf::print( tbH,"Subw " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_NO_EXPORT ) textBuf::print( tbH,"Nexp " ); + + textBuf::print( tbH,") type:("); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SPECIFIC ) textBuf::print( tbH,"Spec "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) textBuf::print( tbH,"Gnrc "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GM ) textBuf::print( tbH,"GM "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GS ) textBuf::print( tbH,"GS "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_XG ) textBuf::print( tbH,"XG "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_MT32 ) textBuf::print( tbH,"MT32 "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GM2 ) textBuf::print( tbH,"GM2 "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SYNTH ) textBuf::print( tbH,"Syn "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_DIRECT_SAMPLE) textBuf::print( tbH,"Dsmp "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SAMPLE ) textBuf::print( tbH,"Samp "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_HARDWARE ) textBuf::print( tbH,"Hwar "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SOFTWARE ) textBuf::print( tbH,"Soft "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SYNTHESIZER ) textBuf::print( tbH,"Sizr "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_PORT ) textBuf::print( tbH,"Port "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_APPLICATION ) textBuf::print( tbH,"Appl "); + + textBuf::print( tbH,")\n"); + + } + + } // device + } // midi +} // cw + +cw::rc_t cw::midi::device::initialize( cbFunc_t cbFunc, void* cbArg, unsigned parserBufByteCnt, const char* appNameStr ) +{ + rc_t rc = kOkRC; + int arc = 0; + cmMpRoot_t* p = NULL; + + if((rc = finalize()) != kOkRC ) + return rc; + + // allocate the global root object + _cmMpRoot = p = memAllocZ(1); + p->h = NULL; + p->alsa_queue = -1; + + + // create the listening thread + if((rc = thread::create( p->thH, _threadCbFunc, NULL)) != kOkRC ) + { + rc = _cmMpErrMsg(rc,0,"Thread initialization failed."); + goto errLabel; + } + + // initialize the ALSA sequencer + if((arc = snd_seq_open(&p->h, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK )) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA Sequencer open failed."); + goto errLabel; + } + + // prevent valgrind from report memory leaks in libasound (https://stackoverflow.com/questions/13478861/alsa-mem-leak) + snd_config_update_free_global(); + + // setup the device and port structures + if((rc = _cmMpAllocStruct(p,appNameStr,cbFunc,cbArg,parserBufByteCnt)) != kOkRC ) + goto errLabel; + + // allocate the file descriptors used for polling + p->alsa_fdCnt = snd_seq_poll_descriptors_count(p->h, POLLIN); + p->alsa_fd = memAllocZ(p->alsa_fdCnt); + snd_seq_poll_descriptors(p->h, p->alsa_fd, p->alsa_fdCnt, POLLIN); + + p->cbFunc = cbFunc; + p->cbDataPtr = cbArg; + + // start the sequencer queue + if((arc = snd_seq_start_queue(p->h, p->alsa_queue, NULL)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA queue start failed."); + goto errLabel; + } + + // send any pending commands to the driver + snd_seq_drain_output(p->h); + + // all time stamps will be an offset from this time stamp + clock_gettime(CLOCK_MONOTONIC,&p->baseTimeStamp); + + if((rc = thread::unpause(p->thH)) != kOkRC ) + rc = _cmMpErrMsg(rc,0,"Thread start failed."); + + errLabel: + + if( rc != kOkRC ) + finalize(); + + return rc; + +} + +cw::rc_t cw::midi::device::finalize() +{ + rc_t rc = kOkRC; + cmMpRoot_t* p = _cmMpRoot; + + if( _cmMpRoot != NULL ) + { + int arc; + + // stop the thread first + if((rc = thread::destroy(p->thH)) != kOkRC ) + { + rc = _cmMpErrMsg(rc,0,"Thread destroy failed."); + goto errLabel; + } + + // stop the queue + if( p->h != NULL ) + if((arc = snd_seq_stop_queue(p->h,p->alsa_queue, NULL)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA queue stop failed."); + goto errLabel; + } + + // release the alsa queue + if( p->alsa_queue != -1 ) + { + if((arc = snd_seq_free_queue(p->h,p->alsa_queue)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA queue release failed."); + else + p->alsa_queue = -1; + } + + // release the alsa system handle + if( p->h != NULL ) + { + if( (arc = snd_seq_close(p->h)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA sequencer close failed."); + else + p->h = NULL; + } + + unsigned i,j; + for(i=0; idevCnt; ++i) + { + for(j=0; jdevArray[i].iPortCnt; ++j) + { + parser::destroy(p->devArray[i].iPortArray[j].parserH); + memRelease( p->devArray[i].iPortArray[j].nameStr ); + } + + for(j=0; jdevArray[i].oPortCnt; ++j) + { + memRelease( p->devArray[i].oPortArray[j].nameStr ); + } + + memRelease(p->devArray[i].iPortArray); + memRelease(p->devArray[i].oPortArray); + memRelease(p->devArray[i].nameStr); + + } + + memRelease(p->devArray); + + memFree(p->alsa_fd); + + memRelease(_cmMpRoot); + + } + + errLabel: + return rc; +} + +bool cw::midi::device::isInitialized() +{ return _cmMpRoot!=NULL; } + +unsigned cw::midi::device::count() +{ return _cmMpRoot==NULL ? 0 : _cmMpRoot->devCnt; } + +const char* cw::midi::device::name( unsigned devIdx ) +{ + if( _cmMpRoot==NULL || devIdx>=_cmMpRoot->devCnt) + return NULL; + + return _cmMpRoot->devArray[devIdx].nameStr; +} + +unsigned cw::midi::device::portCount( unsigned devIdx, unsigned flags ) +{ + if( _cmMpRoot==NULL || devIdx>=_cmMpRoot->devCnt) + return 0; + + if( cwIsFlag(flags,kInMpFl) ) + return _cmMpRoot->devArray[devIdx].iPortCnt; + + return _cmMpRoot->devArray[devIdx].oPortCnt; +} + +const char* cw::midi::device::portName( unsigned devIdx, unsigned flags, unsigned portIdx ) +{ + if( _cmMpRoot==NULL || devIdx>=_cmMpRoot->devCnt) + return 0; + + if( cwIsFlag(flags,kInMpFl) ) + { + if( portIdx >= _cmMpRoot->devArray[devIdx].iPortCnt ) + return 0; + + return _cmMpRoot->devArray[devIdx].iPortArray[portIdx].nameStr; + } + + if( portIdx >= _cmMpRoot->devArray[devIdx].oPortCnt ) + return 0; + + return _cmMpRoot->devArray[devIdx].oPortArray[portIdx].nameStr; +} + + +cw::rc_t cw::midi::device::send( unsigned devIdx, unsigned portIdx, byte_t status, byte_t d0, byte_t d1 ) +{ + rc_t rc = kOkRC; + snd_seq_event_t ev; + int arc; + cmMpRoot_t* p = _cmMpRoot; + + assert( p!=NULL && devIdx < p->devCnt && portIdx < p->devArray[devIdx].oPortCnt ); + + port_t* port = p->devArray[devIdx].oPortArray + portIdx; + + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, p->alsa_addr.port); + //snd_seq_ev_set_subs(&ev); + + snd_seq_ev_set_dest(&ev, port->alsa_addr.client, port->alsa_addr.port); + snd_seq_ev_set_direct(&ev); + snd_seq_ev_set_fixed(&ev); + + + switch( status & 0xf0 ) + { + case kNoteOffMdId: + ev.type = SND_SEQ_EVENT_NOTEOFF; + ev.data.note.note = d0; + ev.data.note.velocity = d1; + break; + + case kNoteOnMdId: + ev.type = SND_SEQ_EVENT_NOTEON; + ev.data.note.note = d0; + ev.data.note.velocity = d1; + break; + + case kPolyPresMdId: + ev.type = SND_SEQ_EVENT_KEYPRESS ; + ev.data.note.note = d0; + ev.data.note.velocity = d1; + break; + + case kCtlMdId: + ev.type = SND_SEQ_EVENT_CONTROLLER; + ev.data.control.param = d0; + ev.data.control.value = d1; + break; + + case kPgmMdId: + ev.type = SND_SEQ_EVENT_PGMCHANGE; + ev.data.control.param = d0; + ev.data.control.value = d1; + break; + + case kChPresMdId: + ev.type = SND_SEQ_EVENT_CHANPRESS; + ev.data.control.param = d0; + ev.data.control.value = d1; + break; + + case kPbendMdId: + { + int val = d0; + val <<= 7; + val += d1; + val -= 8192; + + ev.type = SND_SEQ_EVENT_PITCHBEND; + ev.data.control.param = 0; + ev.data.control.value = val; + } + break; + + default: + rc = _cmMpErrMsg(kInvalidArgRC,0,"Cannot send an invalid MIDI status byte:0x%x.",status & 0xf0); + goto errLabel; + } + + ev.data.note.channel = status & 0x0f; + + if((arc = snd_seq_event_output(p->h, &ev)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"MIDI event output failed."); + + if((arc = snd_seq_drain_output(p->h)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"MIDI event output drain failed."); + + errLabel: + return rc; +} + +cw::rc_t cw::midi::device::sendData( unsigned devIdx, unsigned portIdx, const byte_t* dataPtr, unsigned byteCnt ) +{ + return cwLogError(kInvalidOpRC,"cmMpDeviceSendData() has not yet been implemented for ALSA."); +} + +cw::rc_t cw::midi::device::installCallback( unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) +{ + rc_t rc = kOkRC; + unsigned di; + unsigned dn = count(); + cmMpRoot_t* p = _cmMpRoot; + + for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkRC ) + goto errLabel; + } + + errLabel: + return rc; +} + +cw::rc_t cw::midi::device::removeCallback( unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) +{ + rc_t rc = kOkRC; + unsigned di; + unsigned dn = count(); + unsigned remCnt = 0; + cmMpRoot_t* p = _cmMpRoot; + + for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) ) + { + if( parser::removeCallback( p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkRC ) + goto errLabel; + else + ++remCnt; + } + } + + if( remCnt == 0 && dn > 0 ) + rc = _cmMpErrMsg(kInvalidArgRC,0,"The callback was not found on any of the specified devices or ports."); + + errLabel: + return rc; +} + +bool cw::midi::device::usesCallback( unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) +{ + unsigned di; + unsigned dn = count(); + cmMpRoot_t* p = _cmMpRoot; + + for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) ) + return true; + } + + return false; +} + + + +void cw::midi::device::report( textBuf::handle_t tbH ) +{ + cmMpRoot_t* p = _cmMpRoot; + unsigned i,j; + + textBuf::print( tbH,"Buffer size bytes in:%i out:%i\n",snd_seq_get_input_buffer_size(p->h),snd_seq_get_output_buffer_size(p->h)); + + for(i=0; idevCnt; ++i) + { + const dev_t* d = p->devArray + i; + + textBuf::print( tbH,"%i : Device: %s \n",i,cwStringNullGuard(d->nameStr)); + + if(d->iPortCnt > 0 ) + textBuf::print( tbH," Input:\n"); + + for(j=0; jiPortCnt; ++j) + _cmMpReportPort(tbH,d->iPortArray+j); + + if(d->oPortCnt > 0 ) + textBuf::print( tbH," Output:\n"); + + for(j=0; joPortCnt; ++j) + _cmMpReportPort(tbH,d->oPortArray+j); + } +} + + diff --git a/cwMidiPort.cpp b/cwMidiPort.cpp new file mode 100644 index 0000000..0e60cb1 --- /dev/null +++ b/cwMidiPort.cpp @@ -0,0 +1,563 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwMidi.h" +#include "cwTime.h" +#include "cwTextBuf.h" + +#include "cwMidiPort.h" + +//=================================================================================================== +// +// + +namespace cw +{ + namespace midi + { + namespace parser + { + enum + { + kBufByteCnt = 1024, + + kExpectStatusStId=0, // 0 + kExpectDataStId, // 1 + kExpectStatusOrDataStId, // 2 + kExpectEOXStId // 3 + }; + + typedef struct cmMpParserCb_str + { + cbFunc_t cbFunc; + void* cbDataPtr; + struct cmMpParserCb_str* linkPtr; + } cbRecd_t; + + typedef struct parser_str + { + cbRecd_t* cbChain; + + packet_t pkt; + + unsigned state; // parser state id + unsigned errCnt; // accumlated error count + byte_t status; // running status + byte_t data0; // data byte 0 + unsigned dataCnt; // data byte cnt for current status + unsigned dataIdx; // index (0 or 1) of next data byte + byte_t* buf; // output buffer + unsigned bufByteCnt; // output buffer byte cnt + unsigned bufIdx; // next output buffer index + unsigned msgCnt; // count of channel messages in the buffer + } parser_t; + + parser_t* _handleToPtr( handle_t h ) + { + return handleToPtr(h); + } + + void _cmMpParserCb( parser_t* p, packet_t* pkt, unsigned pktCnt ) + { + cbRecd_t* c = p->cbChain; + for(; c!=NULL; c=c->linkPtr) + { + pkt->cbDataPtr = c->cbDataPtr; + c->cbFunc( pkt, pktCnt ); + } + } + + void _cmMpTransmitChMsgs( parser_t* p ) + { + if( p->msgCnt > 0 ) + { + p->pkt.msgArray = (msg_t*)p->buf; + p->pkt.msgCnt = p->msgCnt; + p->pkt.sysExMsg = NULL; + + //p->cbFunc( &p->pkt, 1 ); + _cmMpParserCb(p,&p->pkt,1); + + p->bufIdx = 0; + p->msgCnt = 0; + } + } + + void _cmMpTransmitSysEx( parser_t* p ) + { + p->pkt.msgArray = NULL; + p->pkt.sysExMsg = p->buf; + p->pkt.msgCnt = p->bufIdx; + //p->cbFunc( &p->pkt, 1 ); + _cmMpParserCb(p,&p->pkt,1); + p->bufIdx = 0; + + } + + void _cmMpParserStoreChMsg( parser_t* p, const time::spec_t* timeStamp, byte_t d ) + { + // if there is not enough room left in the buffer then transmit + // the current messages + if( p->bufByteCnt - p->bufIdx < sizeof(msg_t) ) + _cmMpTransmitChMsgs(p); + + + assert( p->bufByteCnt - p->bufIdx >= sizeof(msg_t) ); + + // get a pointer to the next msg in the buffer + msg_t* msgPtr = (msg_t*)(p->buf + p->bufIdx); + + // fill the buffer msg + msgPtr->timeStamp = *timeStamp; + msgPtr->status = p->status; + + switch( p->dataCnt ) + { + case 0: + break; + case 1: + msgPtr->d0 = d; + msgPtr->d1 = 0; + break; + + case 2: + msgPtr->d0 = p->data0; + msgPtr->d1 = d; + break; + + default: + assert(0); + } + + // update the msg count and next buffer + ++p->msgCnt; + + p->bufIdx += sizeof(msg_t); + + } + + void _report( parser_t* p ) + { + cwLogInfo("state:%i st:0x%x d0:%i dcnt:%i didx:%i buf[%i]->%i msg:%i err:%i\n",p->state,p->status,p->data0,p->dataCnt,p->dataIdx,p->bufByteCnt,p->bufIdx,p->msgCnt,p->errCnt); + } + + void _destroy( parser_t* p ) + { + memRelease(p->buf); + + cbRecd_t* c = p->cbChain; + while(c != NULL) + { + cbRecd_t* nc = c->linkPtr; + memRelease(c); + c = nc; + } + + memRelease(p); + + } + + } // parser + } // midi +} // cw + +cw::rc_t cw::midi::parser::create( handle_t& hRef, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr, unsigned bufByteCnt ) +{ + rc_t rc = kOkRC; + parser_t* p = memAllocZ( 1 ); + + + p->pkt.devIdx = devIdx; + p->pkt.portIdx = portIdx; + + //p->cbChain = cmMemAllocZ( cbRecd_t, 1 ); + //p->cbChain->cbFunc = cbFunc; + //p->cbChain->cbDataPtr = cbDataPtr; + //p->cbChain->linkPtr = NULL; + p->cbChain = NULL; + p->buf = memAllocZ( bufByteCnt ); + p->bufByteCnt = bufByteCnt; + p->bufIdx = 0; + p->msgCnt = 0; + p->state = kExpectStatusStId; + p->dataIdx = kInvalidIdx; + p->dataCnt = kInvalidCnt; + p->status = kInvalidStatusMdId; + + hRef.set(p); + + if( cbFunc != NULL ) + rc = installCallback(hRef, cbFunc, cbDataPtr ); + + + if( rc != kOkRC ) + { + _destroy(p); + hRef.clear(); + } + + + return rc; +} + + +cw::rc_t cw::midi::parser::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + if( !hRef.isValid() ) + return rc; + + parser_t* p = _handleToPtr(hRef); + + _destroy(p); + + hRef.clear(); + + return rc; +} + +unsigned cw::midi::parser::errorCount( handle_t h ) +{ + parser_t* p = _handleToPtr(h); + if( p == NULL ) + return 0; + + return p->errCnt; +} + + +void cw::midi::parser::parseMidiData( handle_t h, const time::spec_t* timeStamp, const byte_t* iBuf, unsigned iByteCnt ) +{ + + parser_t* p = _handleToPtr(h); + + if( p == NULL ) + return; + + const byte_t* ip = iBuf; + const byte_t* ep = iBuf + iByteCnt; + + for(; ip < ep; ++ip ) + { + // if this byte is a status byte + if( isStatus(*ip) ) + { + if( p->state != kExpectStatusStId && p->state != kExpectStatusOrDataStId ) + ++p->errCnt; + + p->status = *ip; + p->dataCnt = statusToByteCount(*ip); + + switch( p->status ) + { + case kSysExMdId: // if this is the start of a sys-ex msg ... + // ... clear the buffer to prepare from sys-ex data + _cmMpTransmitChMsgs(p); + + p->state = kExpectEOXStId; + p->dataCnt = kInvalidCnt; + p->dataIdx = kInvalidIdx; + p->buf[ p->bufIdx++ ] = kSysExMdId; + break; + + case kSysComEoxMdId: // if this is the end of a sys-ex msg + assert( p->bufIdx < p->bufByteCnt ); + p->buf[p->bufIdx++] = *ip; + _cmMpTransmitSysEx(p); + p->state = kExpectStatusStId; + break; + + default: // ... otherwise it is a 1,2, or 3 byte msg status + if( p->dataCnt > 0 ) + { + p->state = kExpectDataStId; + p->dataIdx = 0; + } + else + { + // this is a status only msg - store it + _cmMpParserStoreChMsg(p,timeStamp,*ip); + + p->state = kExpectStatusStId; + p->dataIdx = kInvalidIdx; + p->dataCnt = kInvalidCnt; + } + + } + + continue; + } + + // at this point the current byte (*ip) is a data byte + + switch(p->state) + { + + case kExpectStatusOrDataStId: + assert( p->dataIdx == 0 ); + + case kExpectDataStId: + switch( p->dataIdx ) + { + case 0: // expecting data byte 0 ... + + switch( p->dataCnt ) + { + case 1: // ... of a 1 byte msg - the msg is complete + _cmMpParserStoreChMsg(p,timeStamp,*ip); + p->state = kExpectStatusOrDataStId; + break; + + case 2: // ... of a 2 byte msg - prepare to recv the second data byte + p->state = kExpectDataStId; + p->dataIdx = 1; + p->data0 = *ip; + break; + + default: + assert(0); + } + break; + + case 1: // expecting data byte 1 of a two byte msg + assert( p->dataCnt == 2 ); + assert( p->state == kExpectDataStId ); + + _cmMpParserStoreChMsg(p,timeStamp,*ip); + p->state = kExpectStatusOrDataStId; + p->dataIdx = 0; + break; + + default: + assert(0); + + } + break; + + case kExpectEOXStId: + assert( p->bufIdx < p->bufByteCnt ); + + p->buf[p->bufIdx++] = *ip; + + // if the buffer is full - then transmit it + if( p->bufIdx == p->bufByteCnt ) + _cmMpTransmitSysEx(p); + + break; + + } + + } // ip loop + + _cmMpTransmitChMsgs(p); + +} + +cw::rc_t cw::midi::parser::midiTriple( handle_t h, const time::spec_t* timeStamp, byte_t status, byte_t d0, byte_t d1 ) +{ + rc_t rc = kOkRC; + parser_t* p = _handleToPtr(h); + byte_t mb = 0xff; // a midi triple may never have a status of 0xff + + if( d0 == 0xff ) + p->dataCnt = 0; + else + if( d1 == 0xff ) + p->dataCnt = 1; + else + p->dataCnt = 2; + + p->status = status; + switch( p->dataCnt ) + { + case 0: + mb = status; + break; + + case 1: + mb = d0; + break; + + case 2: + p->data0 = d0; + mb = d1; + break; + + default: + rc = cwLogError(kInvalidArgRC,"An invalid MIDI status byte (0x%x) was encountered by the MIDI data parser."); + goto errLabel; + break; + } + + if( mb != 0xff ) + _cmMpParserStoreChMsg(p,timeStamp,mb); + + p->dataCnt = kInvalidCnt; + + errLabel: + return rc; +} + +cw::rc_t cw::midi::parser::transmit( handle_t h ) +{ + parser_t* p = _handleToPtr(h); + _cmMpTransmitChMsgs(p); + return kOkRC; +} + +cw::rc_t cw::midi::parser::installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr ) +{ + parser_t* p = _handleToPtr(h); + cbRecd_t* newCbPtr = memAllocZ( 1 ); + cbRecd_t* c = p->cbChain; + + newCbPtr->cbFunc = cbFunc; + newCbPtr->cbDataPtr = cbDataPtr; + newCbPtr->linkPtr = NULL; + + if( p->cbChain == NULL ) + p->cbChain = newCbPtr; + else + { + while( c->linkPtr != NULL ) + c = c->linkPtr; + + c->linkPtr = newCbPtr; + } + + return kOkRC; +} + +cw::rc_t cw::midi::parser::removeCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr ) +{ + parser_t* p = _handleToPtr(h); + cbRecd_t* c1 = p->cbChain; // target link + cbRecd_t* c0 = NULL; // link pointing to target + + // search for the cbFunc to remove + for(; c1!=NULL; c1=c1->linkPtr) + { + if( c1->cbFunc == cbFunc && c1->cbDataPtr == cbDataPtr) + break; + + c0 = c1; + } + + // if the cbFunc was not found + if( c1 == NULL ) + return cwLogError(kInvalidArgRC,"Unable to locate the callback function %p for removal.",cbFunc); + + // the cbFunc to remove was found + + // if it was the first cb in the chain + if( c0 == NULL ) + p->cbChain = c1->linkPtr; + else + c0->linkPtr = c1->linkPtr; + + memRelease(c1); + + return kOkRC; +} + +bool cw::midi::parser::hasCallback( handle_t h, cbFunc_t cbFunc, void* cbArg ) +{ + parser_t* p = _handleToPtr(h); + cbRecd_t* c = p->cbChain; // target link + + // search for the cbFunc to remove + for(; c!=NULL; c=c->linkPtr) + if( c->cbFunc == cbFunc && c->cbDataPtr == cbArg ) + return true; + + return false; +} + +//==================================================================================================== +// +// + +unsigned cw::midi::device::nameToIndex(const char* deviceName) +{ + assert(deviceName!=NULL); + unsigned i; + unsigned n = count(); + for(i=0; imsgCnt; ++j) + if( p->msgArray != NULL ) + printf("%ld %ld 0x%x %i %i\n", p->msgArray[j].timeStamp.tv_sec, p->msgArray[j].timeStamp.tv_nsec, p->msgArray[j].status,p->msgArray[j].d0, p->msgArray[j].d1); + else + printf("0x%x ",p->sysExMsg[j]); + + } + } + } // device + } // midi +} // cw + +cw::rc_t cw::midi::device::test() +{ + rc_t rc = kOkRC; + char ch; + unsigned parserBufByteCnt = 1024; + textBuf::handle_t tbH; + + // initialie the MIDI system + if((rc = initialize(testCallback,NULL,parserBufByteCnt,"app")) != kOkRC ) + return rc; + + // create a text buffer to hold the MIDI system report text + if((rc = textBuf::create(tbH)) != kOkRC ) + goto errLabel; + + // generate and print the MIDI system report + report(tbH); + cwLogInfo("%s",textBuf::text(tbH)); + + cwLogInfo("any key to send note-on (=quit)\n"); + + while((ch = getchar()) != 'q') + { + send(2,0,0x90,60,60); + } + + errLabel: + textBuf::destroy(tbH); + finalize(); + return rc; +} diff --git a/cwMidiPort.h b/cwMidiPort.h new file mode 100644 index 0000000..83b6a09 --- /dev/null +++ b/cwMidiPort.h @@ -0,0 +1,97 @@ +#ifndef cwMidiPort_H +#define cwMidiPort_H + + +namespace cw +{ + namespace midi + { + + //( { file_desc:"Device independent MIDI port related code." kw:[midi]} + + // Flags used to identify input and output ports on MIDI devices + enum + { + kInMpFl = 0x01, + kOutMpFl = 0x02 + }; + + typedef void (*cbFunc_t)( const packet_t* pktArray, unsigned pktCnt ); + + //) + //( { label:parser file_desc:"MIDI event parser converts raw MIDI events into packet_t messages." kw:[midi]} + + //=============================================================================================== + // MIDI Parser + // + + namespace parser + { + typedef handle handle_t; + + // 'cbFunc' and 'cbDataPtr' are optional. If 'cbFunc' is not supplied in the call to + // create() it may be supplied later by installCallback(). + // 'bufByteCnt' defines is the largest complete system-exclusive message the parser will + // by able to transmit. System-exclusive messages larger than this will be broken into + // multiple sequential callbacks. + rc_t create( handle_t& hRef, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbArg, unsigned bufByteCnt ); + rc_t destroy( handle_t& hRef ); + unsigned errorCount( handle_t h ); + void parseMidiData( handle_t h, const time::spec_t* timestamp, const byte_t* buf, unsigned bufByteCnt ); + + // The following two functions are intended to be used togetther. + // Use midiTriple() to insert pre-parsed msg's to the output buffer, + // and then use transmit() to send the buffer via the parsers callback function. + // Set the data bytes to 0xff if they are not used by the message. + rc_t midiTriple( handle_t h, const time::spec_t* timestamp, byte_t status, byte_t d0, byte_t d1 ); + rc_t transmit( handle_t h ); + + // Install/Remove additional callbacks. + rc_t installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr ); + rc_t removeCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr ); + + // Returns true if the parser uses the given callback. + bool hasCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr ); + + } + + //) + //( { label:cmMidiPort file_desc:"Device independent MIDI port." kw:[midi]} + + //=============================================================================================== + // MIDI Device Interface + // + + + namespace device + { + // 'cbFunc' and 'cbDataPtr' are optional (they may be set to NULL). In this case + // 'cbFunc' and 'cbDataPtr' may be set in a later call to cmMpInstallCallback(). + rc_t initialize( cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr ); + rc_t finalize(); + bool isInitialized(); + + unsigned count(); + const char* name( unsigned devIdx ); + unsigned nameToIndex(const char* deviceName); + unsigned portCount( unsigned devIdx, unsigned flags ); + const char* portName( unsigned devIdx, unsigned flags, unsigned portIdx ); + unsigned portNameToIndex( unsigned devIdx, unsigned flags, const char* portName ); + rc_t send( unsigned devIdx, unsigned portIdx, byte_t st, byte_t d0, byte_t d1 ); + rc_t sendData( unsigned devIdx, unsigned portIdx, const byte_t* dataPtr, unsigned byteCnt ); + + // Set devIdx to -1 to assign the callback to all devices. + // Set portIdx to -1 to assign the callback to all ports on the specified devices. + // + rc_t installCallback( unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ); + rc_t removeCallback( unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ); + bool usesCallback( unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ); + void report( textBuf::handle_t h); + + rc_t test(); + } + } +} + + +#endif diff --git a/cwMpScNbQueue.h b/cwMpScNbQueue.h new file mode 100644 index 0000000..01f9109 --- /dev/null +++ b/cwMpScNbQueue.h @@ -0,0 +1,81 @@ +#ifndef cwMpScNbQueue_h +#define cwMpScNbQueue_h + +namespace cw +{ + + template + class MpScNbQueue + { + public: + + + typedef struct node_str + { + std::atomic next = nullptr; + T* payload = nullptr; + } node_t; + + MpScNbQueue() + { + node_t* stub = memAllocZ(); + _head = stub; + _tail = stub; + } + + virtual ~MpScNbQueue() + { memFree(_tail); } + + MpScNbQueue( const MpScNbQueue& ) = delete; + MpScNbQueue( const MpScNbQueue&& ) = delete; + MpScNbQueue& operator=(const MpScNbQueue& ) = delete; + MpScNbQueue& operator=(const MpScNbQueue&& ) = delete; + + + void push( T* payload ) + { + node_t* new_node = memAllocZ(1); + new_node->payload = payload; + new_node->next.store(nullptr); + node_t* prev = _head.exchange(new_node,std::memory_order_acq_rel); // append the new node to the list (aquire-release) + prev->next.store(new_node,std::memory_order_release); // make the new node accessible by the consumer (release to consumer) + + } + + T* pop() + { + T* payload = nullptr; + node_t* t = _tail; + node_t* next = t->next.load(std::memory_order_acquire); // acquire from producer + if( next != nullptr ) + { + _tail = next; + payload = next->payload; + memFree(t); + } + + return payload; + + } + + bool isempty() const + { + return _tail->next.load(std::memory_order_acquire) == nullptr; // acquire from producer + } + + private: + + void _push( node_t* new_node ) + { + } + + node_t* _stub; + node_t* _tail; + std::atomic _head; + }; + + void mpScNbQueueTest(); +} + + +#endif diff --git a/cwObject.h b/cwObject.h index 88943a4..471b120 100644 --- a/cwObject.h +++ b/cwObject.h @@ -137,12 +137,12 @@ namespace cw rc_t getv() const { return kOkRC; } template< typename T0, typename T1, typename... ARGS > - rc_t getv( T0 label, T1* valRef, ARGS ...args ) const + rc_t getv( T0 label, T1& valRef, ARGS&&... args ) const { rc_t rc; - if((rc = get(label,*valRef)) == kOkRC ) - rc = getv(args...); + if((rc = get(label,valRef)) == kOkRC ) + rc = getv(std::forward(args)...); return rc; } @@ -153,8 +153,6 @@ namespace cw - unsigned object_child_count( const object_t* o ); - rc_t objectFromString( const char* s, object_t*& objRef ); rc_t objectFromFile( const char* fn, object_t*& objRef ); diff --git a/cwSerialPort.cpp b/cwSerialPort.cpp new file mode 100644 index 0000000..7e84242 --- /dev/null +++ b/cwSerialPort.cpp @@ -0,0 +1,462 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwSerialPort.h" + +#include +#include +#include // ::close() +#include // O_RDWR +#include // TIOCEXCL + + +namespace cw +{ + namespace serialPort + { + typedef struct port_str + { + const char* _deviceStr; + int _deviceH; + unsigned _baudRate; + unsigned _cfgFlags; + callbackFunc_t _cbFunc; + void* _cbArg; + struct termios _ttyAttrs; + struct pollfd _pollfd; + } port_t; + + inline port_t* _portHandleToPtr(handle_t h){ return handleToPtr(h); } + + void _setClosedState( port_t* p ) + { + if( p->_deviceStr != nullptr ) + cw::memFree(const_cast(p->_deviceStr)); + + p->_deviceH = -1; + p->_deviceStr = nullptr; + p->_baudRate = 0; + p->_cfgFlags = 0; + p->_cbFunc = nullptr; + p->_cbArg = nullptr; + + } + + rc_t _getAttributes( port_t* p, struct termios& attr ) + { + if( tcgetattr(p->_deviceH, &attr) == -1 ) + return cwLogSysError(kGetAttrFailRC,errno,"Error getting tty attributes from %s.",p->_deviceStr); + + return kOkRC; + } + + rc_t _poll( port_t* p, unsigned timeOutMs ) + { + rc_t rc = kOkRC; + int sysRC; + + if((sysRC = ::poll(&p->_pollfd,1,timeOutMs)) == 0) + rc = kTimeOutRC; + else + { + if( sysRC < 0 ) + rc = cwLogSysError(kReadFailRC,errno,"Poll failed on serial port."); + } + + return rc; + + } + + rc_t _destroy( port_t* p ) + { + rc_t rc = kOkRC; + + // Block until all written output has been sent from the device. + // Note that this call is simply passed on to the serial device driver. + // See tcsendbreak(3) ("man 3 tcsendbreak") for details. + if (tcdrain(p->_deviceH) == -1) + { + rc = cwLogSysError(kFlushFailRC,errno,"Error waiting for serial device '%s' to drain.", p->_deviceStr ); + goto errLabel; + } + + // It is good practice to reset a serial port back to the state in + // which you found it. This is why we saved the original termios struct + // The constant TCSANOW (defined in termios.h) indicates that + // the change should take effect immediately. + + if (tcsetattr(p->_deviceH, TCSANOW, &p->_ttyAttrs) == -1) + { + rc = cwLogSysError(kSetAttrFailRC,errno,"Error resetting tty attributes on serial device '%s'.",p->_deviceStr); + goto errLabel; + } + + if( p->_deviceH != -1 ) + { + if( ::close(p->_deviceH ) != 0 ) + { + rc = cwLogSysError(kCloseFailRC,errno,"Port close failed on serial dvice '%s'.", p->_deviceStr); + goto errLabel; + } + + _setClosedState(p); + } + + memRelease(p); + + errLabel: + return rc; + } + + + } +} + + +cw::rc_t cw::serialPort::create( handle_t& h, const char* deviceStr, unsigned baudRate, unsigned cfgFlags, callbackFunc_t cbFunc, void* cbArg ) +{ + rc_t rc = kOkRC; + struct termios options; + + // if the port is already open then close it + if((rc = destroy(h)) != kOkRC ) + return rc; + + port_t* p = memAllocZ(); + + p->_deviceH = -1; + + // open the port + if( (p->_deviceH = ::open(deviceStr, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1 ) + { + rc = cwLogSysError(kOpenFailRC,errno,"Error opening serial '%s'",cwStringNullGuard(deviceStr)); + goto errLabel;; + } + + // Note that open() follows POSIX semantics: multiple open() calls to + // the same file will succeed unless the TIOCEXCL ioctl is issued. + // This will prevent additional opens except by root-owned processes. + // See tty(4) ("man 4 tty") and ioctl(2) ("man 2 ioctl") for details. + + if( ioctl(p->_deviceH, TIOCEXCL) == -1 ) + { + rc = cwLogSysError(kResourceNotAvailableRC,errno,"The serial device '%s' is already in use.", cwStringNullGuard(deviceStr)); + goto errLabel; + } + + + // Now that the device is open, clear the O_NONBLOCK flag so + // subsequent I/O will block. + // See fcntl(2) ("man 2 fcntl") for details. + /* + if (fcntl(_deviceH, F_SETFL, 0) == -1) + + { + _error("Error clearing O_NONBLOCK %s - %s(%d).", pr.devFilePath.c_str(), strerror(errno), errno); + goto errLabel; + } + */ + + // Get the current options and save them so we can restore the + // default settings later. + if (tcgetattr(p->_deviceH, &p->_ttyAttrs) == -1) + { + rc = cwLogSysError(kGetAttrFailRC,errno,"Error getting tty attributes from the device '%s'.",deviceStr); + goto errLabel; + } + + + // The serial port attributes such as timeouts and baud rate are set by + // modifying the termios structure and then calling tcsetattr to + // cause the changes to take effect. Note that the + // changes will not take effect without the tcsetattr() call. + // See tcsetattr(4) ("man 4 tcsetattr") for details. + options = p->_ttyAttrs; + + + // Set raw input (non-canonical) mode, with reads blocking until either + // a single character has been received or a 100ms timeout expires. + // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") + // for details. + cfmakeraw(&options); + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 1; + + + // The baud rate, word length, and handshake options can be set as follows: + + + // set baud rate + cfsetspeed(&options, baudRate); + + options.c_cflag |= CREAD | CLOCAL; // ignore modem controls + + // set data word size + cwClrBits(options.c_cflag, CSIZE); // clear the word size bits + cwEnaBits(options.c_cflag, CS5, cwIsFlag(cfgFlags, kDataBits5Fl)); + cwEnaBits(options.c_cflag, CS6, cwIsFlag(cfgFlags, kDataBits6Fl)); + cwEnaBits(options.c_cflag, CS7, cwIsFlag(cfgFlags, kDataBits7Fl)); + cwEnaBits(options.c_cflag, CS8, cwIsFlag(cfgFlags, kDataBits8Fl)); + + cwClrBits(options.c_cflag, PARENB); // assume no-parity + + // if the odd or even parity flag is set + if( cwIsFlag( cfgFlags, kEvenParityFl) || cwIsFlag( cfgFlags, kOddParityFl ) ) + { + cwSetBits(options.c_cflag, PARENB); + + if( cwIsFlag(cfgFlags, kOddParityFl ) ) + cwSetBits( options.c_cflag, PARODD); + } + + // set two stop bits + cwEnaBits( options.c_cflag, CSTOPB, cwIsFlag(cfgFlags, k2StopBitFl)); + + + // set hardware flow control + //cwEnaBits(options.c_cflag, CCTS_OFLOW, cwIsFlag(cfgFlags, kCTS_OutFlowCtlFl)); + //cwEnaBits(options.c_cflag, CRTS_IFLOW, cwIsFlag(cfgFlags, kRTS_InFlowCtlFl)); + //cwEnaBits(options.c_cflag, CDTR_IFLOW, cwIsFlag(cfgFlags, kDTR_InFlowCtlFl)); + //cwEnaBits(options.c_cflag, CDSR_OFLOW, cwIsFlag(cfgFlags, kDSR_OutFlowCtlFl)); + //cwEnaBits(options.c_cflag, CCAR_OFLOW, cwIsFlag(cfgFlags, kDCD_OutFlowCtlFl)); + + cwClrBits(options.c_cflag,CRTSCTS); // turn-off hardware flow control + + // 7 bit words, enable even parity, CTS out ctl flow, RTS in ctl flow + // note: set PARODD and PARENB to enable odd parity) + //options.c_cflag |= (CS7 | PARENB | CCTS_OFLOW | CRTS_IFLOW ); + + // Cause the new options to take effect immediately. + if (tcsetattr(p->_deviceH, TCSANOW, &options) == -1) + { + + rc = cwLogSysError(kSetAttrFailRC,errno,"Error setting tty attributes on serial device %.", deviceStr); + goto errLabel; + } + + memset(&p->_pollfd,0,sizeof(p->_pollfd)); + p->_pollfd.fd = p->_deviceH; + p->_pollfd.events = POLLIN; + + p->_deviceStr = cw::memAllocStr( deviceStr ); + p->_baudRate = baudRate; + p->_cfgFlags = cfgFlags; + p->_cbFunc = cbFunc; + p->_cbArg = cbArg; + + h.set(p); + + errLabel: + if( rc != kOkRC ) + _destroy(p); + + return rc; + +} + +cw::rc_t cw::serialPort::destroy(handle_t& h ) +{ + rc_t rc = kOkRC; + + if( !isopen(h) ) + return rc; + + port_t* p = _portHandleToPtr(h); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + h.clear(); + + return rc; +} + +bool cw::serialPort::isopen( handle_t h) +{ + if( !h.isValid() ) + return false; + + port_t* p = _portHandleToPtr(h); + return p->_deviceH != -1; +} + +cw::rc_t cw::serialPort::send( handle_t h, const void* byteA, unsigned byteN ) +{ + rc_t rc = kOkRC; + + port_t* p = _portHandleToPtr(h); + + if( !isopen(h) ) + return cwLogWarningRC( kResourceNotAvailableRC, "An attempt was made to transmit from a closed serial port."); + + if( byteN == 0 ) + return rc; + + // implement a non blocking write - if less than all the bytes were written then iterate + unsigned i = 0; + do + { + int n = 0; + if((n = write( p->_deviceH, ((char*)byteA)+i, byteN-i )) == -1 ) + { + rc = cwLogSysError(kWriteFailRC,errno,"Write failed on serial port '%s'.", p->_deviceStr ); + break; + } + + i += n; + + + }while( i 0 && p->_cbFunc != nullptr ) + p->_cbFunc( p->_cbArg, buf, readN_Ref ); + + return rc; + +} + +cw::rc_t cw::serialPort::receive( handle_t h, unsigned timeOutMs, unsigned& readN_Ref) +{ + rc_t rc; + port_t* p = _portHandleToPtr(h); + + if((rc = _poll(p,timeOutMs)) == kOkRC ) + rc = receive(h,readN_Ref); + return rc; + +} + +cw::rc_t cw::serialPort::receive( handle_t h, void* buf, unsigned bufN, unsigned& readN_Ref) +{ + rc_t rc = kOkRC; + port_t* p = _portHandleToPtr(h); + + readN_Ref = 0; + + if( !isopen(h) ) + return cwLogWarningRC( kResourceNotAvailableRC, "An attempt was made to read from a closed serial port."); + + int n = 0; + + // if attempt to read the port succeeded ... + if((n =read( p->_deviceH, buf, bufN )) != -1 ) + readN_Ref = n; + else + { + // ... or failed and it wasn't because the port was empty + if( errno != EAGAIN) + rc = cwLogSysError(kReadFailRC,errno,"An attempt to read the serial port '%s' failed.", p->_deviceStr ); + } + + return rc; +} + +cw::rc_t cw::serialPort::receive( handle_t h, void* buf, unsigned bufByteN, unsigned timeOutMs, unsigned& readN_Ref ) +{ + rc_t rc = kOkRC; + port_t* p = _portHandleToPtr(h); + if((rc = _poll(p,timeOutMs)) == kOkRC ) + rc = receive(h,buf,bufByteN,readN_Ref); + + return rc; +} + +const char* cw::serialPort::device( handle_t h) +{ + port_t* p = _portHandleToPtr(h); + return p->_deviceStr; +} + +unsigned cw::serialPort::baudRate( handle_t h) +{ + port_t* p = _portHandleToPtr(h); + return p->_baudRate; +} + +unsigned cw::serialPort::cfgFlags( handle_t h) +{ + port_t* p = _portHandleToPtr(h); + return p->_cfgFlags; +} + +unsigned cw::serialPort::readInBaudRate( handle_t h ) +{ + struct termios attr; + port_t* p = _portHandleToPtr(h); + + if((_getAttributes(p,attr)) != kOkRC ) + return 0; + + return cfgetispeed(&attr); + +} + +unsigned cw::serialPort::readOutBaudRate( handle_t h) +{ + struct termios attr; + port_t* p = _portHandleToPtr(h); + + if((_getAttributes(p,attr)) != kOkRC ) + return 0; + + return cfgetospeed(&attr); + +} + +unsigned cw::serialPort::readCfgFlags( handle_t h) +{ + struct termios attr; + unsigned result = 0; + port_t* p = _portHandleToPtr(h); + + if((_getAttributes(p,attr)) == false ) + return 0; + + switch( attr.c_cflag & CSIZE ) + { + case CS5: + cwSetBits( result, kDataBits5Fl); + break; + + case CS6: + cwSetBits( result, kDataBits6Fl ); + break; + + case CS7: + cwSetBits( result, kDataBits7Fl); + break; + + case CS8: + cwSetBits( result, kDataBits8Fl); + break; + } + + cwEnaBits( result, k2StopBitFl, cwIsFlag( attr.c_cflag, CSTOPB )); + cwEnaBits( result, k1StopBitFl, !cwIsFlag( attr.c_cflag, CSTOPB )); + + if( cwIsFlag( attr.c_cflag, PARENB ) ) + { + cwEnaBits( result, kOddParityFl, cwIsFlag( attr.c_cflag, PARODD )); + cwEnaBits( result, kEvenParityFl, !cwIsFlag( attr.c_cflag, PARODD )); + } + + return result; + +} + diff --git a/cwSerialPort.h b/cwSerialPort.h new file mode 100644 index 0000000..13a98e8 --- /dev/null +++ b/cwSerialPort.h @@ -0,0 +1,73 @@ +#ifndef cwSerialPort_H +#define cwSerialPort_H + +namespace cw +{ + namespace serialPort + { + enum + { + kDataBits5Fl = 0x0001, + kDataBits6Fl = 0x0002, + kDataBits7Fl = 0x0004, + kDataBits8Fl = 0x0008, + kDataBitsMask = 0x000f, + + k1StopBitFl = 0x0010, + k2StopBitFl = 0x0020, + + kEvenParityFl = 0x0040, + kOddParityFl = 0x0080, + kNoParityFl = 0x0000, + /* + kCTS_OutFlowCtlFl = 0x0100, + kRTS_InFlowCtlFl = 0x0200, + kDTR_InFlowCtlFl = 0x0400, + kDSR_OutFlowCtlFl = 0x0800, + kDCD_OutFlowCtlFl = 0x1000 + */ + + kDefaultCfgFlags = kDataBits8Fl | k1StopBitFl | kNoParityFl + }; + + + typedef handle handle_t; + typedef void (*callbackFunc_t)( void* cbArg, const void* byteA, unsigned byteN ); + + + rc_t create( handle_t& h, const char* device, unsigned baudRate, unsigned cfgFlags, callbackFunc_t cbFunc, void* cbArg ); + rc_t destroy(handle_t& h ); + + bool isopen( handle_t h); + + rc_t send( handle_t h, const void* byteA, unsigned byteN ); + + + // Make callback to listener with result of read - Non-blocking + rc_t receive( handle_t h, unsigned& readN_Ref); + + // Make callback to listener with result of read - Block for up to timeOutMs. + rc_t receive( handle_t h, unsigned timeOutMs, unsigned& readN_Ref); + + // Return result of read in buf[bufByteN] - Non-blocking. + rc_t receive( handle_t h, void* buf, unsigned bufByteN, unsigned& readN_Ref); + + // Return result of read in buf[bufByteN] - Block for up to timeOutMs. + rc_t receive( handle_t h, void* buf, unsigned bufByteN, unsigned timeOutMs, unsigned& readN_Ref ); + + const char* device( handle_t h); + + // Get the baud rate and cfgFlags used to initialize the port + unsigned baudRate( handle_t h); + unsigned cfgFlags( handle_t h); + + // Get the baud rate and cfg flags by reading the device. + // Note the the returned buad rate is a system id rather than the actual baud rate, + // however the cfgFlags are converted to the same kXXXFl defined in this class. + unsigned readInBaudRate( handle_t h ); + unsigned readOutBaudRate( handle_t h); + unsigned readCfgFlags( handle_t h); + } +} + +#endif diff --git a/cwSerialPortSrv.cpp b/cwSerialPortSrv.cpp new file mode 100644 index 0000000..c09b6eb --- /dev/null +++ b/cwSerialPortSrv.cpp @@ -0,0 +1,175 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwThread.h" +#include "cwSerialPort.h" +#include "cwSerialPortSrv.h" + + +namespace cw +{ + namespace serialPortSrv + { + typedef struct this_str + { + serialPort::handle_t portH; + thread::handle_t threadH; + unsigned _pollPeriodMs; + } this_t; + + inline this_t* _handleToPtr( handle_t h ) { return handleToPtr(h); } + + rc_t _destroy( this_t* p ) + { + rc_t rc = kOkRC; + + if((rc = serialPort::destroy(p->portH)) != kOkRC ) + return rc; + + if((rc = thread::destroy(p->threadH)) != kOkRC ) + return rc; + + memRelease(p); + + return rc; + } + + bool threadCallback( void* arg ) + { + this_t* p = static_cast(arg); + + unsigned readN; + if( serialPort::isopen(p->portH) ) + serialPort::receive(p->portH,p->_pollPeriodMs,readN); + + + return true; + } + } +} + + + +cw::rc_t cw::serialPortSrv::create( handle_t& h, const char* deviceStr, unsigned baudRate, unsigned cfgFlags, serialPort::callbackFunc_t cbFunc, void* cbArg, unsigned pollPeriodMs ) +{ + rc_t rc = kOkRC; + + this_t* p = memAllocZ(); + + if((rc = serialPort::create( p->portH, deviceStr, baudRate, cfgFlags, cbFunc, cbArg )) != kOkRC ) + goto errLabel; + + if((rc = thread::create( p->threadH, threadCallback, p)) != kOkRC ) + goto errLabel; + + p->_pollPeriodMs = pollPeriodMs; + + errLabel: + if( rc != kOkRC ) + _destroy(p); + else + h.set(p); + + return rc; +} + +cw::rc_t cw::serialPortSrv::destroy(handle_t& h ) +{ + rc_t rc = kOkRC; + + if( !h.isValid() ) + return rc; + + this_t* p = _handleToPtr(h); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + h.clear(); + return rc; +} + + +cw::serialPort::handle_t cw::serialPortSrv::portHandle( handle_t h ) +{ + this_t* p = _handleToPtr(h); + return p->portH; +} + +cw::thread::handle_t cw::serialPortSrv::threadHandle( handle_t h ) +{ + this_t* p = _handleToPtr(h); + return p->threadH; +} + +cw::rc_t cw::serialPortSrv::start( handle_t h ) +{ + this_t* p = _handleToPtr(h); + return cw::thread::pause(p->threadH, thread::kWaitFl ); +} + +cw::rc_t cw::serialPortSrv::pause( handle_t h ) +{ + this_t* p = _handleToPtr(h); + return cw::thread::pause(p->threadH, thread::kPauseFl | thread::kWaitFl ); +} + + +cw::rc_t cw::serialPortSrv::send( handle_t h, const void* byteA, unsigned byteN ) +{ + this_t* p = _handleToPtr(h); + return cw::serialPort::send(p->portH,byteA,byteN); +} + + +namespace cw +{ + + void serialPortSrvTestCb( void* arg, const void* byteA, unsigned byteN ) + { + const char* text = static_cast(byteA); + + for(unsigned i=0; i handle_t; + + rc_t create( handle_t& h, const char* device, unsigned baudRate, unsigned cfgFlags, serialPort::callbackFunc_t cbFunc, void* cbArg, unsigned pollPeriodMs ); + rc_t destroy(handle_t& h ); + + serialPort::handle_t portHandle( handle_t h ); + thread::handle_t threadHandle( handle_t h ); + + rc_t start( handle_t h ); + rc_t pause( handle_t h ); + + rc_t send( handle_t h, const void* byteA, unsigned byteN ); + } + + + rc_t serialPortSrvTest(); + + +} + +#endif diff --git a/cwText.cpp b/cwText.cpp index 9a06d24..68890eb 100644 --- a/cwText.cpp +++ b/cwText.cpp @@ -15,3 +15,11 @@ int cw::textCompare( const char* s0, const char* s1 ) return strcmp(s0,s1); } + +int cw::textCompare( const char* s0, const char* s1, unsigned n) +{ + if( s0 == nullptr || s1 == nullptr ) + return s0==s1 ? 0 : 1; // if both pointers are nullptr then trigger a match + + return strncmp(s0,s1,n); +} diff --git a/cwText.h b/cwText.h index aeae373..731dbef 100644 --- a/cwText.h +++ b/cwText.h @@ -8,6 +8,7 @@ namespace cw // if both s0 and s1 are nullptr then a match is indicated int textCompare( const char* s0, const char* s1 ); + int textCompare( const char* s0, const char* s1, unsigned n); } #endif diff --git a/cwTextBuf.cpp b/cwTextBuf.cpp index f55bfa0..22e8585 100644 --- a/cwTextBuf.cpp +++ b/cwTextBuf.cpp @@ -6,114 +6,140 @@ namespace cw { - typedef struct textBuf_str + namespace textBuf { - char* buf; - unsigned expandCharN; // count of characters to expand buf - unsigned allocCharN; // current allocated size of buf - unsigned endN; // current count of character in buf - char* boolTrueText; - char* boolFalseText; - int intWidth; - unsigned intFlags; - int floatWidth; - int floatDecPlN; - } textBuf_t; - + typedef struct textBuf_str + { + char* buf; + unsigned expandCharN; // count of characters to expand buf + unsigned allocCharN; // current allocated size of buf + unsigned endN; // current count of character in buf + char* boolTrueText; + char* boolFalseText; + int intWidth; + unsigned intFlags; + int floatWidth; + int floatDecPlN; + } this_t; + } } -#define _textBufHandleToPtr(h) handleToPtr(h) +#define _handleToPtr(h) handleToPtr(h) -cw::rc_t cw::textBufCreate( textBufH_t& hRef, unsigned initCharN, unsigned expandCharN ) +cw::rc_t cw::textBuf::create( handle_t& hRef, unsigned initCharN, unsigned expandCharN ) { rc_t rc; - if((rc = textBufDestroy(hRef)) != kOkRC ) + if((rc = destroy(hRef)) != kOkRC ) return rc; - textBuf_t* p = memAllocZ(); - p->buf = memAllocZ(initCharN); - p->expandCharN = expandCharN; - p->boolTrueText = memDuplStr("true"); + this_t* p = memAllocZ(); + p->buf = memAllocZ(initCharN); + p->expandCharN = expandCharN; + p->allocCharN = initCharN; + p->boolTrueText = memDuplStr("true"); p->boolFalseText = memDuplStr("false"); hRef.set(p); return rc; } -cw::rc_t cw::textBufDestroy(textBufH_t& hRef ) +cw::textBuf::handle_t cw::textBuf::create( unsigned initCharN, unsigned expandCharN ) +{ + handle_t h; + rc_t rc; + if((rc = create(h,initCharN,expandCharN)) != kOkRC ) + { + cwLogError(rc,"Log create ailed."); + h.clear(); + } + return h; +} + +cw::rc_t cw::textBuf::destroy(handle_t& hRef ) { rc_t rc = kOkRC; if( !hRef.isValid() ) return rc; - textBuf_t* p = _textBufHandleToPtr(hRef); + this_t* p = _handleToPtr(hRef); memRelease(p->buf); memRelease(p->boolTrueText); memRelease(p->boolFalseText); - hRef.release(); + memRelease(p); + hRef.clear(); return rc; } -const char* cw::textBufText( textBufH_t h ) +void cw::textBuf::clear( handle_t h ) { - textBuf_t* p = _textBufHandleToPtr(h); + this_t* p = _handleToPtr(h); + p->endN = 0; +} + +const char* cw::textBuf::text( handle_t h ) +{ + this_t* p = _handleToPtr(h); return p->buf; } -cw::rc_t cw::textBufPrintf( textBufH_t h, const char* fmt, va_list vl ) +cw::rc_t cw::textBuf::print( handle_t h, const char* fmt, va_list vl ) { va_list vl1; va_copy(vl1,vl); - textBuf_t* p = _textBufHandleToPtr(h); + this_t* p = _handleToPtr(h); - int n = snprintf(nullptr,0,fmt,vl); + int n = vsnprintf(nullptr,0,fmt,vl1); - - if( p->endN + n > p->allocCharN ) + if( n > 0 ) { - unsigned minExpandCharN = (p->endN + n) - p->allocCharN; - unsigned expandCharN = std::max(minExpandCharN,p->expandCharN); - p->allocCharN =+ expandCharN; - p->buf = memResizeZ( p->buf, p->allocCharN ); - } + n += 1; // add one to make space for the terminating zero + + if( p->endN + n > p->allocCharN ) + { + unsigned minExpandCharN = (p->endN + n) - p->allocCharN; + unsigned expandCharN = std::max(minExpandCharN,p->expandCharN); + p->allocCharN += expandCharN; + p->buf = memResizeZ( p->buf, p->allocCharN ); + } - int m = snprintf(p->buf + p->endN, n, fmt, vl ); + int m = vsnprintf(p->buf + p->endN, n, fmt, vl ); - cwAssert(m=n); - p->endN += n; - + cwAssert(m==(n-1)); + p->endN += n-1; // subtract 1 to no count the terminating zero in the character count + } + va_end(vl1); return kOkRC; } -cw::rc_t cw::textBufPrintf( textBufH_t h, const char* fmt, ... ) +cw::rc_t cw::textBuf::print( handle_t h, const char* fmt, ... ) { va_list vl; va_start(vl,fmt); - rc_t rc = textBufPrintf(h,fmt,vl); + rc_t rc = print(h,fmt,vl); va_end(vl); return rc; } -cw::rc_t cw::textBufPrintBool( textBufH_t h, bool v ) +cw::rc_t cw::textBuf::printBool( handle_t h, bool v ) { - textBuf_t* p = _textBufHandleToPtr(h); - return textBufPrintf(h,"%s", v ? p->boolTrueText : p->boolFalseText); + this_t* p = _handleToPtr(h); + return print(h,"%s", v ? p->boolTrueText : p->boolFalseText); } -cw::rc_t cw::textBufPrintInt( textBufH_t h, int v ) -{ return textBufPrintf(h,"%i",v); } +cw::rc_t cw::textBuf::printInt( handle_t h, int v ) +{ return print(h,"%i",v); } -cw::rc_t cw::textBufPrintUInt( textBufH_t h, unsigned v ) -{ return textBufPrintf(h,"%i",v); } +cw::rc_t cw::textBuf::printUInt( handle_t h, unsigned v ) +{ return print(h,"%i",v); } -cw::rc_t cw::textBufPrintFloat( textBufH_t h, double v ) -{ return textBufPrintf(h,"%f",v); } +cw::rc_t cw::textBuf::printFloat( handle_t h, double v ) +{ return print(h,"%f",v); } -cw::rc_t cw::textBufSetBoolFormat( textBufH_t h, bool v, const char* s) +cw::rc_t cw::textBuf::setBoolFormat( handle_t h, bool v, const char* s) { - textBuf_t* p = _textBufHandleToPtr(h); + this_t* p = _handleToPtr(h); if( v ) p->boolTrueText = memReallocStr(p->boolTrueText,s); @@ -122,19 +148,34 @@ cw::rc_t cw::textBufSetBoolFormat( textBufH_t h, bool v, const char* s) return kOkRC; } -cw::rc_t cw::textBufSetIntFormat( textBufH_t h, unsigned width, unsigned flags ) +cw::rc_t cw::textBuf::setIntFormat( handle_t h, unsigned width, unsigned flags ) { - textBuf_t* p = _textBufHandleToPtr(h); + this_t* p = _handleToPtr(h); p->intWidth = width; p->intFlags = flags; return kOkRC; } -cw::rc_t cw::textBufSetFloatFormat( textBufH_t h, unsigned width, unsigned decPlN ) +cw::rc_t cw::textBuf::setFloatFormat( handle_t h, unsigned width, unsigned decPlN ) { - textBuf_t* p = _textBufHandleToPtr(h); + this_t* p = _handleToPtr(h); p->floatWidth = width; p->floatDecPlN = decPlN; return kOkRC; } +cw::rc_t cw::textBuf::test() +{ + handle_t h; + rc_t rc; + + if((rc = create(h,8,8)) != kOkRC ) + return rc; + + print(h,"Hello\n"); + print(h,"foo\n"); + + printf("%s", text(h) ); + + return destroy(h); +} diff --git a/cwTextBuf.h b/cwTextBuf.h index 43a4e30..936aaf4 100644 --- a/cwTextBuf.h +++ b/cwTextBuf.h @@ -4,27 +4,32 @@ namespace cw { - typedef handle textBufH_t; + namespace textBuf + { + typedef handle handle_t; - rc_t textBufCreate( textBufH_t& hRef, unsigned initCharN=1024, unsigned expandCharN=1024 ); - rc_t textBufDestroy(textBufH_t& hRef ); + rc_t create( handle_t& hRef, unsigned initCharN=1024, unsigned expandCharN=1024 ); + handle_t create(unsigned initCharN=1024, unsigned expandCharN=1024 ); + + rc_t destroy(handle_t& hRef ); - const char* textBufText( textBufH_t h); + void clear( handle_t h ); + const char* text( handle_t h); - rc_t textBufPrintf( textBufH_t h, const char* fmt, va_list vl ); - rc_t textBufPrintf( textBufH_t h, const char* fmt, ... ); + rc_t print( handle_t h, const char* fmt, va_list vl ); + rc_t print( handle_t h, const char* fmt, ... ); - rc_t textBufPrintBool( textBufH_t h, bool v ); - rc_t textBufPrintInt( textBufH_t h, int v ); - rc_t textBufPrintUInt( textBufH_t h, unsigned v ); - rc_t textBufPrintFloat( textBufH_t h, double v ); + rc_t printBool( handle_t h, bool v ); + rc_t printInt( handle_t h, int v ); + rc_t printUInt( handle_t h, unsigned v ); + rc_t printFloat( handle_t h, double v ); - rc_t textBufSetBoolFormat( textBufH_t h, bool v, const char* s); - rc_t textBufSetIntFormat( textBufH_t h, unsigned width, unsigned flags ); - rc_t textBufSetFloatFormat( textBufH_t h, unsigned width, unsigned decPlN ); + rc_t setBoolFormat( handle_t h, bool v, const char* s); + rc_t setIntFormat( handle_t h, unsigned width, unsigned flags ); + rc_t setFloatFormat( handle_t h, unsigned width, unsigned decPlN ); - - + rc_t test(); + } } #endif diff --git a/cwThread.cpp b/cwThread.cpp index 727260f..034f72f 100644 --- a/cwThread.cpp +++ b/cwThread.cpp @@ -8,102 +8,105 @@ namespace cw { - - enum - { - kDoExitThFl = 0x01, - kDoPauseThFl = 0x02, - kDoRunThFl = 0x04 - }; - typedef struct thread_str + namespace thread { - pthread_t pThreadH; - kThreadStateId_t stateId; - threadFunc_t func; - void* funcArg; - unsigned doFlags; - unsigned stateMicros; - unsigned pauseMicros; - unsigned sleepMicros = 15000; - } thread_t; - -#define _threadHandleToPtr(h) handleToPtr(h) - - rc_t _waitForState( thread_t* p, unsigned stateId ) - { - unsigned waitTimeMicroSecs = 0; - - while( p->stateId != stateId && waitTimeMicroSecs < p->stateMicros ) + enum { - sleepUs( p->sleepMicros ); - waitTimeMicroSecs += p->sleepMicros; + kDoExitThFl = 0x01, + kDoPauseThFl = 0x02, + kDoRunThFl = 0x04 + }; + + typedef struct thread_str + { + pthread_t pThreadH; + stateId_t stateId; + cbFunc_t func; + void* funcArg; + unsigned doFlags; + unsigned stateMicros; + unsigned pauseMicros; + unsigned sleepMicros = 15000; + } thread_t; + + inline thread_t* _handleToPtr(handle_t h) { return handleToPtr(h); } + + rc_t _waitForState( thread_t* p, unsigned stateId ) + { + unsigned waitTimeMicroSecs = 0; + + while( p->stateId != stateId && waitTimeMicroSecs < p->stateMicros ) + { + sleepUs( p->sleepMicros ); + waitTimeMicroSecs += p->sleepMicros; + } + + return p->stateId==stateId ? kOkRC : kTimeOutRC; } - return p->stateId==stateId ? kOkRC : kTimeOutRC; - } - - void _threadCleanUpCallback(void* p) - { - ((thread_t*)p)->stateId = kExitedThId; - } + void _threadCleanUpCallback(void* p) + { + ((thread_t*)p)->stateId = kExitedThId; + } - void* _threadCallback(void* param) - { - thread_t* p = (thread_t*)param; - - // set a clean up handler - this will be called when the - // thread terminates unexpectedly or pthread_cleanup_pop() is called. - pthread_cleanup_push(_threadCleanUpCallback,p); - - while( cwIsFlag(p->doFlags,kDoExitThFl) == false ) + void* _threadCallback(void* param) { + thread_t* p = (thread_t*)param; - // if we are in the pause state - if( p->stateId == kPausedThId ) + // set a clean up handler - this will be called when the + // thread terminates unexpectedly or pthread_cleanup_pop() is called. + pthread_cleanup_push(_threadCleanUpCallback,p); + + while( cwIsFlag(p->doFlags,kDoExitThFl) == false ) { + + // if we are in the pause state + if( p->stateId == kPausedThId ) + { - sleepUs( p->pauseMicros ); + sleepUs( p->pauseMicros ); - // check if we have been requested to leave the pause state - if( cwIsFlag(p->doFlags,kDoRunThFl) ) + // check if we have been requested to leave the pause state + if( cwIsFlag(p->doFlags,kDoRunThFl) ) + { + p->doFlags = cwClrFlag(p->doFlags,kDoRunThFl); + p->stateId = kRunningThId; + } + } + else { - p->doFlags = cwClrFlag(p->doFlags,kDoRunThFl); - p->stateId = kRunningThId; + // call the user-defined function + if( p->func(p->funcArg)==false ) + break; + + // check if we have been requested to enter the pause state + if( cwIsFlag(p->doFlags,kDoPauseThFl) ) + { + p->doFlags = cwClrFlag(p->doFlags,kDoPauseThFl); + p->stateId = kPausedThId; + } } } - else - { - // call the user-defined function - if( p->func(p->funcArg)==false ) - break; - // check if we have been requested to enter the pause state - if( cwIsFlag(p->doFlags,kDoPauseThFl) ) - { - p->doFlags = cwClrFlag(p->doFlags,kDoPauseThFl); - p->stateId = kPausedThId; - } - } - } - - pthread_cleanup_pop(1); + pthread_cleanup_pop(1); - pthread_exit(NULL); + pthread_exit(NULL); - return p; - } - + return p; + } + } } -cw::rc_t cw::threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, int stateMicros, int pauseMicros ) +cw::rc_t cw::thread::create( handle_t& hRef, cbFunc_t func, void* funcArg, int stateMicros, int pauseMicros ) { rc_t rc; int sysRC; - - if((rc = threadDestroy(hRef)) != kOkRC ) + pthread_attr_t attr; + + if((rc = destroy(hRef)) != kOkRC ) return rc; thread_t* p = memAllocZ(); @@ -113,25 +116,37 @@ cw::rc_t cw::threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, in p->stateMicros = stateMicros; p->pauseMicros = pauseMicros; p->stateId = kPausedThId; - - if((sysRC = pthread_create(&p->pThreadH,NULL, _threadCallback, (void*)p )) != 0 ) + + if((sysRC = pthread_attr_init(&attr)) != 0) { p->stateId = kNotInitThId; - rc = cwLogSysError(kOpFailRC,sysRC,"Thread create failed."); + rc = cwLogSysError(kOpFailRC,sysRC,"Thread attribute init failed."); } + else + if ((sysRC = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) != 0) + { + p->stateId = kNotInitThId; + rc = cwLogSysError(kOpFailRC,sysRC,"Thread set detach attribute failed."); + } + else + if((sysRC = pthread_create(&p->pThreadH, &attr, _threadCallback, (void*)p )) != 0 ) + { + p->stateId = kNotInitThId; + rc = cwLogSysError(kOpFailRC,sysRC,"Thread create failed."); + } hRef.set(p); return rc; } -cw::rc_t cw::threadDestroy( threadH_t& hRef ) +cw::rc_t cw::thread::destroy( handle_t& hRef ) { rc_t rc = kOkRC; if( !hRef.isValid() ) return rc; - thread_t* p = _threadHandleToPtr(hRef); + thread_t* p = _handleToPtr(hRef); // tell the thread to exit p->doFlags = cwSetFlag(p->doFlags,kDoExitThFl); @@ -140,18 +155,19 @@ cw::rc_t cw::threadDestroy( threadH_t& hRef ) if((rc = _waitForState(p,kExitedThId)) != kOkRC ) return cwLogError(rc,"Thread timed out waiting for destroy."); - hRef.release(); + memRelease(p); + hRef.clear(); return rc; } -cw::rc_t cw::threadPause( threadH_t& h, unsigned cmdFlags ) +cw::rc_t cw::thread::pause( handle_t h, unsigned cmdFlags ) { rc_t rc = kOkRC; - bool pauseFl = cwIsFlag(cmdFlags,kThreadPauseFl); - bool waitFl = cwIsFlag(cmdFlags,kThreadWaitFl); - thread_t* p = _threadHandleToPtr(h); + bool pauseFl = cwIsFlag(cmdFlags,kPauseFl); + bool waitFl = cwIsFlag(cmdFlags,kWaitFl); + thread_t* p = _handleToPtr(h); bool isPausedFl = p->stateId == kPausedThId; unsigned waitId; @@ -179,12 +195,17 @@ cw::rc_t cw::threadPause( threadH_t& h, unsigned cmdFlags ) } -cw::kThreadStateId_t cw::threadState( threadH_t h ) +cw::rc_t cw::thread::unpause( handle_t h ) +{ return pause( h, kWaitFl); } + +cw::thread::stateId_t cw::thread::state( handle_t h ) { - thread_t* p = _threadHandleToPtr(h); + thread_t* p = _handleToPtr(h); return p->stateId; } +unsigned cw::thread::id() +{ return static_cast(pthread_self()); } namespace cw { @@ -198,15 +219,15 @@ namespace cw cw::rc_t cw::threadTest() { - threadH_t h; - unsigned val = 0; - rc_t rc; - char c = 0; + thread::handle_t h; + unsigned val = 0; + rc_t rc; + char c = 0; - if((rc = threadCreate(h,_threadTestCb,&val)) != kOkRC ) + if((rc = thread::create(h,_threadTestCb,&val)) != kOkRC ) return rc; - if((rc = threadPause(h,0)) != kOkRC ) + if((rc = thread::pause(h,0)) != kOkRC ) goto errLabel; @@ -225,18 +246,18 @@ cw::rc_t cw::threadTest() break; case 's': - cwLogInfo("state=%i\n",threadState(h)); + cwLogInfo("state=%i\n",thread::state(h)); break; case 'p': { - if( threadState(h) == kPausedThId ) - rc = threadPause(h,kThreadWaitFl); + if( thread::state(h) == thread::kPausedThId ) + rc = thread::pause(h,thread::kWaitFl); else - rc = threadPause(h,kThreadPauseFl|kThreadWaitFl); + rc = thread::pause(h,thread::kPauseFl|thread::kWaitFl); if( rc == kOkRC ) - cwLogInfo("new state:%i\n", threadState(h)); + cwLogInfo("new state:%i\n", thread::state(h)); else { cwLogError(rc,"threadPause() test failed."); @@ -256,7 +277,7 @@ cw::rc_t cw::threadTest() } errLabel: - rc_t rc0 = rc = threadDestroy(h); + rc_t rc0 = rc = thread::destroy(h); return rc == kOkRC ? rc0 : rc; } diff --git a/cwThread.h b/cwThread.h index 5ed1e94..0b119f0 100644 --- a/cwThread.h +++ b/cwThread.h @@ -3,27 +3,35 @@ namespace cw { - typedef enum + namespace thread { - kNotInitThId, - kPausedThId, - kRunningThId, - kExitedThId - } kThreadStateId_t; + typedef enum + { + kNotInitThId, + kPausedThId, + kRunningThId, + kExitedThId + } stateId_t; - typedef handle threadH_t; + typedef handle handle_t; - typedef bool (*threadFunc_t)( void* arg ); + typedef bool (*cbFunc_t)( void* arg ); - // stateMicros = time out duration for switching in/out of pause or in to exit - // pauseMicros = duration of thread sleep interval when in paused state. - rc_t threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, int stateTimeOutMicros=100000, int pauseMicros=10000 ); - rc_t threadDestroy( threadH_t& hRef ); + // stateMicros = total time out duration for switching to the exit state or for switching in/out of pause state. + // pauseMicros = duration of thread sleep interval when in paused state. + rc_t create( handle_t& hRef, cbFunc_t func, void* funcArg, int stateTimeOutMicros=100000, int pauseMicros=10000 ); + rc_t destroy( handle_t& hRef ); - enum { kThreadPauseFl=0x01, kThreadWaitFl=0x02 }; - rc_t threadPause( threadH_t& h, unsigned cmdFlags ); - kThreadStateId_t threadState( threadH_t h ); + + enum { kPauseFl=0x01, kWaitFl=0x02 }; + rc_t pause( handle_t h, unsigned cmdFlags = kWaitFl ); + rc_t unpause( handle_t h ); // same as threadPause(h,kWaitFl) + + stateId_t state( handle_t h ); + // Return the thread id of the calling context. + unsigned id(); + } rc_t threadTest(); } diff --git a/cwTime.cpp b/cwTime.cpp new file mode 100644 index 0000000..3c0d13d --- /dev/null +++ b/cwTime.cpp @@ -0,0 +1,114 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwTime.h" + +#ifdef cwOSX + +#include +#include +#include + +void cw::time::get( spec_t& t ) +{ + static uint64_t t0 = 0; + static mach_timebase_info_data_t tbi; + static struct timespec ts; + + if( t0 == 0 ) + { + mach_timebase_info(&tbi); + t0 = mach_absolute_time(); + ts.tv_sec = time(NULL); + ts.tv_nsec = 0; // accept 1/2 second error vs. wall-time. + } + + // get the current time + uint64_t t1 = mach_absolute_time(); + + // calc the elapsed time since the last call in nanosecs + uint64_t dt = (t1-t0) * tbi.numer / tbi.denom; + + // calc the elapsed time since the first call in secs + uint32_t s = (uint32_t)(dt / 2^9); + + // calc the current time in secs, and nanosecs + t.tv_sec = ts.tv_sec + s; + t.tv_nsec = dt - (s * 2^9); + +} + +#endif + +#ifdef cwLINUX +void cw::time::get( spec_t& t ) +{ clock_gettime(CLOCK_REALTIME,&t); } +#endif + +// this assumes that the seconds have been normalized to a recent start time +// so as to avoid overflow +unsigned cw::time::elapsedMicros( const spec_t* t0, const spec_t* t1 ) +{ + // convert seconds to usecs + long u0 = t0->tv_sec * 1000000; + long u1 = t1->tv_sec * 1000000; + + // convert nanoseconds to usec + u0 += t0->tv_nsec / 1000; + u1 += t1->tv_nsec / 1000; + + // take diff between t1 and t0 + return u1 - u0; +} + +unsigned cw::time::absElapsedMicros( const spec_t* t0, const spec_t* t1 ) +{ + if( isLTE(t0,t1) ) + return elapsedMicros(t0,t1); + + return elapsedMicros(t1,t0); +} + +int cw::time::diffMicros( const spec_t* t0, const spec_t* t1 ) +{ + if( isLTE(t0,t1) ) + return elapsedMicros(t0,t1); + + return -((int)elapsedMicros(t1,t0)); +} + +bool cw::time::isLTE( const spec_t* t0, const spec_t* t1 ) +{ + if( t0->tv_sec < t1->tv_sec ) + return true; + + if( t0->tv_sec == t1->tv_sec ) + return t0->tv_nsec <= t1->tv_nsec; + + return false; +} + +bool cw::time::isGTE( const spec_t* t0, const spec_t* t1 ) +{ + if( t0->tv_sec > t1->tv_sec ) + return true; + + if( t0->tv_sec == t1->tv_sec ) + return t0->tv_nsec >= t1->tv_nsec; + + return false; +} + +bool cw::time::isEqual( const spec_t* t0, const spec_t* t1 ) +{ return t0->tv_sec==t1->tv_sec && t0->tv_nsec==t1->tv_nsec; } + +bool cw::time::isZero( const spec_t* t0 ) +{ return t0->tv_sec==0 && t0->tv_nsec==0; } + +void cw::time::setZero( spec_t* t0 ) +{ + t0->tv_sec = 0; + t0->tv_nsec = 0; +} + + diff --git a/cwTime.h b/cwTime.h new file mode 100644 index 0000000..5c95a46 --- /dev/null +++ b/cwTime.h @@ -0,0 +1,56 @@ + +//( { file_desc:"Time cand clock related functions." kw: [ time system ] } +// +// +// This interface is used to read the systems high resolution timer and +// calculate elapsed time. +//) + + +#ifndef cwTime_H +#define cwTime_H + +namespace cw +{ + namespace time + { + + //( + typedef struct timespec spec_t; + + // Get the time + void get( spec_t& tRef ); + + // Return the elapsed time (t1 - t0) in microseconds + // t1 is assumed to be at a later time than t0. + unsigned elapsedMicros( const spec_t* t0, const spec_t* t1 ); + + + // Same as elapsedMicros() but the times are not assumed to be ordered. + // The function therefore begins by swapping t1 and t0 if t0 is after t1. + unsigned absElapsedMicros( const spec_t* t0, const spec_t* t1 ); + + + // Same as elapsedMicros() but returns a negative value if t0 is after t1. + int diffMicros( const spec_t* t0, const spec_t* t1 ); + + + // Returns true if t0 <= t1. + bool isLTE( const spec_t* t0, const spec_t* t1 ); + + // Return true if t0 >= t1. + bool isGTE( const spec_t* t0, const spec_t* t1 ); + + bool isEqual( const spec_t* t0, const spec_t* t1 ); + + bool isZero( const spec_t* t0 ); + + void setZero( spec_t* t0 ); + + //) + + } +} + +#endif + diff --git a/cwWebSock.cpp b/cwWebSock.cpp new file mode 100644 index 0000000..2214f0c --- /dev/null +++ b/cwWebSock.cpp @@ -0,0 +1,463 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwWebSock.h" +#include "cwMpScNbQueue.h" + +#include + +namespace cw +{ + namespace websock + { + + // Internal outgoing msg structure. + typedef struct msg_str + { + unsigned protocolId; // Protocol associated with this msg. + unsigned char* msg; // Msg data array. + unsigned msgByteN; // Count of bytes in msg[]. + unsigned msgId; // The msgId assigned when this msg is addded to the protocol state msg queue. + unsigned sessionN; // Count of sessions to which this msg has been sent. + struct msg_str* link; // Pointer to next message or nullptr if this is the last msg in the queue. + } msg_t; + + typedef struct websock_str + { + cbFunc_t _cbFunc; + void* _cbArg; + struct lws_context* _ctx = nullptr; // + struct lws_protocols* _protocolA = nullptr; // Websocket internal protocol state array + unsigned _protocolN = 0; // Count of protocol records in _protocolA[]. + unsigned _nextSessionId = 0; // Next session id. + unsigned _connSessionN = 0; // Count of connected sessions. + struct lws_http_mount* _mount = nullptr; // + MpScNbQueue* _q; // Thread safe, non-blocking, protocol independent msg queue. + + } websock_t; + + inline websock_t* _handleToPtr(handle_t h){ return handleToPtr(h); } + + + // Internal session record. + typedef struct session_str + { + unsigned id; // This sessions id. + unsigned protocolId; // This sessions protocol. + unsigned nextMsgId; // Id of the next msg this session will receieve. + } session_t; + + // Application protocol state record - each lws_protocols record in _protocolA[] points to one of these records. + typedef struct protocolState_str + { + websock_t* thisPtr; // Pointer to this websocket. + unsigned nextNewMsgId; // Id of the next message to add to this outgoing msg queue. + msg_t* endMsg; // End of the protocol outgoing msg queue: next message to be written to the remote endpoint. + msg_t* begMsg; // Begin of the protocol outgoing msg queue: last msg added to the outgoing queue by the application. + unsigned sessionN; // Count of sessions using this protocol. + } protocolState_t; + + + int _internalCallback(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t len) + { + + const struct lws_protocols* p = lws_get_protocol(wsi); + + if( p == nullptr || p->user == nullptr ) + return 0; // TODO: issue a warning + + protocolState_t* ps = static_cast(p->user); + + if( ps == nullptr || ps->thisPtr == nullptr ) + return 0; // TODO: issue a warning + + session_t* sess = static_cast(user); + const struct lws_protocols* proto = lws_get_protocol(wsi); + protocolState_t* protoState = static_cast(proto->user); + websock_t* thisPtr = ps->thisPtr; + + //char buf[32]; + + switch( reason ) + { + case LWS_CALLBACK_PROTOCOL_INIT: + cwLogInfo("Websocket init"); + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + cwLogInfo("Websocket destroy"); + break; + + case LWS_CALLBACK_ESTABLISHED: + cwLogInfo("Websocket connection opened\n"); + + sess->id = thisPtr->_nextSessionId++; + sess->protocolId = proto->id; + protoState->sessionN += 1; + thisPtr->_connSessionN += 1; + + if( thisPtr->_cbFunc != nullptr) + thisPtr->_cbFunc(thisPtr->_cbArg, proto->id, sess->id, kConnectTId, nullptr, 0); + + //if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI) > 0) + // printf("conn:%p %s\n",user,buf); + break; + + case LWS_CALLBACK_CLOSED: + cwLogInfo("Websocket connection closed.\n"); + + thisPtr->_connSessionN -= 1; + protoState->sessionN -= 1; + cwAssert( protoState->sessionN >= 0 ); + + if( thisPtr->_cbFunc != nullptr) + thisPtr->_cbFunc(thisPtr->_cbArg,proto->id,sess->id,kDisconnectTId,nullptr,0); + + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + { + //printf("writable: sess:%i proto:%s\n",sess->id,proto->name); + + msg_t* m1 = protoState->endMsg; + cwAssert(m1 != nullptr); + + // for each possible msg + while( m1->link != nullptr ) + { + msg_t* m = m1->link; + + //printf("writing: %i %i : %i %i\n",m->msgId,sess->nextMsgId,m->sessionN,protoState->sessionN); + + // if this msg has not already been sent to this session + if( m->msgId >= sess->nextMsgId ) + { + + // Send the msg to this session + // Note: msgByteN should not include LWS_PRE + int lws_result = lws_write(wsi, m->msg + LWS_PRE , m->msgByteN, LWS_WRITE_TEXT); + + // if the write failed + if(lws_result < (int)m->msgByteN) + { + cwLogError(kWriteFailRC,"Websocket error: %d on write", lws_result); + return -1; + } + else // otherwise the write succeeded + { + sess->nextMsgId = m->msgId + 1; + m->sessionN += 1; + } + + break; + } + + + m1 = m1->link; + } + + } + break; + + case LWS_CALLBACK_RECEIVE: + //printf("recv: sess:%i proto:%s : %p : len:%li\n",sess->id,proto->name,thisPtr->_cbFunc,len); + + if( thisPtr->_cbFunc != nullptr && len>0) + thisPtr->_cbFunc(thisPtr->_cbArg,proto->id,sess->id,kMessageTId,in,len); + + break; + + default: + break; + + } + + return 0; + } + + struct lws_protocols* _idToProtocol( websock_t* p, unsigned protocolId ) + { + for(unsigned i=0; i_protocolN; ++i) + if( p->_protocolA[i].id == protocolId ) + return p->_protocolA + i; + + cwAssert(0); + return nullptr; + } + + + void _cleanProtocolStateList( protocolState_t* ps ) + { + msg_t* m0 = nullptr; + msg_t* m1 = ps->endMsg; + + while( m1->link != nullptr ) + { + if( m1->link->sessionN >= ps->sessionN ) + { + if( m0 == nullptr ) + ps->endMsg = m1->link; + else + m0->link = m1->link; + + msg_t* t = m1->link; + + memFree(m1->msg); + memFree(m1); + + m1 = t; + + continue; + } + + m0 = m1; + m1 = m1->link; + } + } + + + rc_t _destroy( websock_t* p ) + { + msg_t* m; + + if( p->_ctx != nullptr ) + { + lws_context_destroy(p->_ctx); + p->_ctx = nullptr; + } + + if( p->_q != nullptr ) + { + + while((m = p->_q->pop()) != nullptr) + { + memFree(m->msg); + memFree(m); + } + + delete p->_q; + } + + for(int i=0; p->_protocolA!=nullptr and p->_protocolA[i].callback != nullptr; ++i) + { + memFree(const_cast(p->_protocolA[i].name)); + + // TODO: delete any msgs in the protocol state here + auto ps = static_cast(p->_protocolA[i].user); + + m = ps->endMsg; + while( m != nullptr ) + { + msg_t* tmp = m->link; + + memFree(m->msg); + memFree(m); + m = tmp; + } + + memFree(ps); + } + + memRelease(p->_protocolA); + p->_protocolN = 0; + + if( p->_mount != nullptr ) + { + memFree(const_cast(p->_mount->origin)); + memFree(const_cast(p->_mount->def)); + memRelease(p->_mount); + } + + p->_nextSessionId = 0; + p->_connSessionN = 0; + + memRelease(p); + + return kOkRC; + + } + } +} + +cw::rc_t cw::websock::create( + handle_t& h, + cbFunc_t cbFunc, + void* cbArg, + const char* physRootDir, + const char* dfltHtmlPageFn, + int port, + const protocol_t* protocolArgA, + unsigned protocolN ) +{ + rc_t rc; + struct lws_context_creation_info info; + + if((rc = destroy(h)) != kOkRC ) + return rc; + + websock_t* p = memAllocZ(); + + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + lws_set_log_level(logs, NULL); + + // Allocate one extra record to act as the end-of-list sentinel. + p->_protocolN = protocolN + 1; + p->_protocolA = memAllocZ(p->_protocolN); + + // Setup the websocket internal protocol state array + for(unsigned i=0; i(1); + auto dummy = memAllocZ(1); + + protocolState->thisPtr = p; + protocolState->begMsg = dummy; + protocolState->endMsg = dummy; + + // Setup the interal lws_protocols record + struct lws_protocols* pr = p->_protocolA + i; + pr->name = memAllocStr(protocolArgA[i].label); + pr->id = protocolArgA[i].id; + pr->rx_buffer_size = protocolArgA[i].rcvBufByteN; + pr->tx_packet_size = 0; //protocolArgA[i].xmtBufByteN; + pr->per_session_data_size = sizeof(session_t); + pr->callback = strcmp(pr->name,"http")==0 ? lws_callback_http_dummy : _internalCallback; + pr->user = protocolState; // maintain a ptr to the application protocol state + } + + static const char* slash = {"/"}; + p->_mount = memAllocZ(1); + p->_mount->mountpoint = slash; + p->_mount->mountpoint_len = strlen(slash); + p->_mount->origin = memAllocStr(physRootDir); // physical directory assoc'd with http "/" + p->_mount->def = memAllocStr(dfltHtmlPageFn); + p->_mount->origin_protocol= LWSMPRO_FILE; + + memset(&info,0,sizeof(info)); + info.port = port; + info.mounts = p->_mount; + info.protocols = p->_protocolA; + + p->_q = new MpScNbQueue(); + p->_cbFunc = cbFunc; + p->_cbArg = cbArg; + + if((p->_ctx = lws_create_context(&info)) == 0) + { + rc = cwLogError(kObjAllocFailRC,"Unable to create the websocket context."); + goto errLabel; + } + + errLabel: + if( rc != kOkRC ) + _destroy(p); + else + h.set(p); + + return rc; +} + + +cw::rc_t cw::websock::destroy( handle_t& h ) +{ + rc_t rc = kOkRC; + if(!h.isValid()) + return rc; + + websock_t* p = _handleToPtr(h); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + h.clear(); + + return rc; +} + + +cw::rc_t cw::websock::send(handle_t h, unsigned protocolId, const void* msg, unsigned byteN ) +{ + rc_t rc = kOkRC; + + msg_t* m = memAllocZ(1); + m->msg = memAllocZ(byteN); + memcpy(m->msg,msg,byteN); + m->msgByteN = byteN; + m->protocolId = protocolId; + + websock_t* p = _handleToPtr(h); + p->_q->push(m); + + return rc; +} + +cw::rc_t cw::websock::sendV( handle_t h, unsigned protocolId, const char* fmt, va_list vl0 ) +{ + rc_t rc = kOkRC; + va_list vl1; + va_copy(vl1,vl0); + + unsigned bufN = vsnprintf(NULL,0,fmt,vl0); + char buf[bufN+1]; + + unsigned n = vsnprintf(buf,bufN+1,fmt,vl1); + + rc = send(h,protocolId,buf,n); + + va_end(vl1); + return rc; +} + +cw::rc_t cw::websock::sendF( handle_t h, unsigned protocolId, const char* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc_t rc = sendV(h,protocolId,fmt,vl); + va_end(vl); + return rc; +} + +cw::rc_t cw::websock::exec( handle_t h, unsigned timeOutMs ) +{ + rc_t rc = kOkRC; + websock_t* p = _handleToPtr(h); + + // TODO: The return value of lws_service() is undocumented - look at the source code. + lws_service(p->_ctx, timeOutMs ); + + msg_t* m; + + // Get the next pending message. + if((m = p->_q->pop()) != nullptr ) + { + auto protocol = _idToProtocol(p,m->protocolId); + + // Get the application protcol record for this message + protocolState_t* ps = static_cast(protocol->user); + + // remove messages from the protocol message queue which have already been sent + _cleanProtocolStateList( ps ); + + + // add the pre-padding bytes to the msg + unsigned char* msg = memAllocZ(LWS_PRE + m->msgByteN); + memcpy( msg+LWS_PRE, m->msg, m->msgByteN ); + + memFree(m->msg); // free the original msg buffer + + m->msg = msg; + m->msgId = ps->nextNewMsgId; // set the msg id + ps->begMsg->link = m; // put the msg on the front of the outgoing queue + ps->begMsg = m; // + ps->nextNewMsgId += 1; + + + lws_callback_on_writable_all_protocol(p->_ctx,protocol); + + } + + + return rc; +} diff --git a/cwWebSock.h b/cwWebSock.h new file mode 100644 index 0000000..d664033 --- /dev/null +++ b/cwWebSock.h @@ -0,0 +1,79 @@ +#ifndef cwWebSock_H +#define cwWebSock_H + +/* + +Each Websocket represents multiple datastreams referred to as 'protocols'. +Each protocol may be connected to by multiple remote endpoints referred to as 'sessions'. + +When a session connects/disconnects to/from a protocol datastream the Websocket listener is called with +a kConnected + +Use the Websocket.send() function to send a message to all sessions connected to a given protocol. + +Websocket.send() places messages into the thread-safe,non-blocking WebSocket._q. +Messages are then transferred to a protocolState_t queue inside the exec() function +based on their protocolId. + +These messages are then sent to the remote endpoint on the next LWS_CALLBACK_SERVER_WRITEABLE +message to the internal websockets callback function. + +Note that messages are not removed from the protocol message queue immediately after they are +written. Instead the protocol state 'nextMsgId' is advanced to indicate the next message to +write. Sent messages are removed from the protocol state queue inside exec() - the same +place they are added. Since exec() is only called from a single thread this eliminates the +need to make the protocol state queue thread-safe. + + */ + + +namespace cw +{ + + namespace websock + { + typedef handle handle_t; + + typedef struct protocol_str + { + const char* label; // unique label identifying this protocol + unsigned id; // unique id identifying this protocol + unsigned rcvBufByteN; // larger rcv'd packages will be broken into multiple parts + unsigned xmtBufByteN; // larger xmt'd packages are broken into multiple parts + } protocol_t; + + typedef enum + { + kConnectTId, + kDisconnectTId, + kMessageTId + } msgTypeId_t; + + + typedef void (*cbFunc_t)( void* cbArg, unsigned protocolId, unsigned connectionId, msgTypeId_t msg_type, const void* msg, unsigned byteN ); + + rc_t create( + handle_t& h, + cbFunc_t cbFunc, + void* cbArg, + const char* physRootDir, + const char* dfltHtmlPageFn, + int port, + const protocol_t* protocolA, + unsigned protocolN ); + + rc_t destroy( handle_t& h ); + + // + rc_t send( handle_t h, unsigned protocolId, const void* msg, unsigned byteN ); + rc_t sendV( handle_t h, unsigned protocolId, const char* fmt, va_list vl ); + rc_t sendF( handle_t h, unsigned protocolId, const char* fmt, ... ); + + // Call periodically from the same thread to send/recv messages. + rc_t exec( handle_t h, unsigned timeOutMs ); + + } + +} + +#endif diff --git a/cwWebSockSvr.cpp b/cwWebSockSvr.cpp new file mode 100644 index 0000000..0f09631 --- /dev/null +++ b/cwWebSockSvr.cpp @@ -0,0 +1,205 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwWebSock.h" +#include "cwThread.h" +#include "cwWebSockSvr.h" +#include "cwText.h" + +namespace cw +{ + namespace websockSrv + { + typedef struct websockSrv_str + { + websock::handle_t _websockH; + thread::handle_t _thread; + unsigned _timeOutMs; + } websockSrv_t; + + websockSrv_t* _handleToPtr(handle_t h) { return handleToPtr(h); } + + rc_t _destroy( websockSrv_t* p ) + { + rc_t rc; + + if((rc = thread::destroy(p->_thread)) != kOkRC ) + return rc; + + if((rc = websock::destroy(p->_websockH)) != kOkRC ) + return rc; + + memRelease(p); + + return rc; + } + + bool _websockSrvThreadCb( void* arg ) + { + websockSrv_t* p = static_cast(arg); + websock::exec( p->_websockH, p->_timeOutMs ); + return true; + } + } + +} + +cw::rc_t cw::websockSrv::create( + handle_t& h, + websock::cbFunc_t cbFunc, + void* cbArg, + const char* physRootDir, + const char* dfltHtmlPageFn, + int port, + const websock::protocol_t* protocolA, + unsigned protocolN, + unsigned timeOutMs ) +{ + rc_t rc; + if((rc = destroy(h)) != kOkRC ) + return rc; + + websockSrv_t* p = memAllocZ(); + + if((rc = websock::create( p->_websockH, cbFunc, cbArg, physRootDir, dfltHtmlPageFn, port, protocolA, protocolN )) != kOkRC ) + goto errLabel; + + + if((rc = thread::create(p->_thread,_websockSrvThreadCb,p)) != kOkRC ) + goto errLabel; + + p->_timeOutMs = timeOutMs; + + h.set(p); + + errLabel: + if( rc != kOkRC ) + _destroy(p); + + return rc; +} + +cw::rc_t cw::websockSrv::destroy( handle_t& h ) +{ + rc_t rc = kOkRC; + + if( !h.isValid() ) + return rc; + + websockSrv_t* p = _handleToPtr(h); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + h.clear(); // the instance was released in _websockSrvDestroy() + + return rc; +} + +cw::thread::handle_t cw::websockSrv::threadHandle( handle_t h ) +{ + websockSrv_t* p = _handleToPtr(h); + return p->_thread; +} + +cw::websock::handle_t cw::websockSrv::websockHandle( handle_t h ) +{ + websockSrv_t* p = _handleToPtr(h); + return p->_websockH; + +} + + +cw::rc_t cw::websockSrv::start( handle_t h ) +{ + websockSrv_t* p = _handleToPtr(h); + return thread::pause( p->_thread, thread::kWaitFl); +} + +cw::rc_t cw::websockSrv::pause( handle_t h ) +{ + websockSrv_t* p = _handleToPtr(h); + return thread::pause( p->_thread, thread::kPauseFl | thread::kWaitFl); +} + +namespace cw +{ + typedef struct appCtx_str + { + bool quitFl = false; + websock::handle_t wsH; + unsigned protocolId; + } appCtx_t; + + // Note that this function is called from context of the websockSrv internal thread + // and from within the websockExec() call. + void websockCb( void* cbArg, unsigned protocolId, unsigned connectionId, websock::msgTypeId_t msg_type, const void* vmsg, unsigned byteN ) + { + appCtx_t* app = static_cast(cbArg); + const char* msg = static_cast(vmsg); + + printf("protcol:%i connection:%i type:%i bytes:%i %.*s\n",protocolId,connectionId, msg_type, byteN, byteN, msg); + + + if( msg_type == websock::kMessageTId ) + { + if( textCompare(msg,"quit",4) == 0) + app->quitFl = true; + + websock::send(app->wsH, app->protocolId, vmsg, byteN ); + } + + + } + +} + +cw::rc_t cw::websockSrvTest() +{ + rc_t rc; + websockSrv::handle_t h; + const char* physRootDir = "/home/kevin/src/cw_rt/html/websockSrvTest"; + const char* dfltHtmlPageFn = "test_websocket.html"; + unsigned timeOutMs = 50; + int port = 7681; + unsigned rcvBufByteN = 128; + unsigned xmtBufByteN = 128; + appCtx_t appCtx; + + enum + { + kHttpProtocolId = 1, + kWebsockSrvProtocolId = 2 + }; + + websock::protocol_t protocolA[] = + { + { "http", kHttpProtocolId, 0, 0}, + { "websocksrv_test_protocol",kWebsockSrvProtocolId,rcvBufByteN,xmtBufByteN} + }; + + unsigned protocolN = sizeof(protocolA)/sizeof(protocolA[0]); + + if((rc = websockSrv::create( h, websockCb, &appCtx, physRootDir, dfltHtmlPageFn, port, protocolA, protocolN, timeOutMs )) != kOkRC ) + return rc; + + appCtx.wsH = websockSrv::websockHandle(h); + appCtx.protocolId = kWebsockSrvProtocolId; + + if((rc = websockSrv::start(h)) != kOkRC ) + goto errLabel; + else + { + while( !appCtx.quitFl ) + { + sleepMs(500); + } + } + + errLabel: + + websockSrv::destroy(h); + + return rc; +} diff --git a/cwWebSockSvr.h b/cwWebSockSvr.h new file mode 100644 index 0000000..9515bcc --- /dev/null +++ b/cwWebSockSvr.h @@ -0,0 +1,38 @@ +#ifndef cwWebsockSvr_H +#define cwWebsockSvr_H + +namespace cw { + + namespace websockSrv + { + typedef handle handle_t; + + rc_t create( + handle_t& h, + websock::cbFunc_t cbFunc, // This callback is made from the thread identified by websockSrv::threadHandle(). + void* cbArg, + const char* physRootDir, + const char* dfltHtmlPageFn, + int port, + const websock::protocol_t* protocolA, + unsigned protocolN, + unsigned websockTimeOutMs ); + + rc_t destroy( handle_t& h ); + + thread::handle_t threadHandle( handle_t h ); + + websock::handle_t websockHandle( handle_t h ); + + // Start or unpause the server. + rc_t start( handle_t h ); + + // Put the server into a pause state. + rc_t pause( handle_t h ); + + } + + rc_t websockSrvTest(); +} + +#endif diff --git a/html/websockSrvTest/test_websocket.html b/html/websockSrvTest/test_websocket.html new file mode 100644 index 0000000..87467fb --- /dev/null +++ b/html/websockSrvTest/test_websocket.html @@ -0,0 +1,96 @@ + + + + +
Websock Test App

+ Incoming text messages from the server. +
+
+
+
+ + Outgoing text message to the server. + + + +
+ + + + + + + + diff --git a/main.cpp b/main.cpp index 72b163d..4a03ede 100644 --- a/main.cpp +++ b/main.cpp @@ -3,11 +3,21 @@ #include "cwCommonImpl.h" #include "cwMem.h" #include "cwFileSys.h" +#include "cwTextBuf.h" #include "cwLex.h" #include "cwNumericConvert.h" #include "cwObject.h" #include "cwThread.h" #include "cwText.h" +#include "cwWebSock.h" +#include "cwWebSockSvr.h" +#include "cwSerialPort.h" +#include "cwSerialPortSrv.h" +#include "cwMidi.h" +#include "cwTime.h" +#include "cwMidiPort.h" +#include "cwAudioPort.h" +#include "cwAudioBuf.h" #include @@ -17,12 +27,12 @@ void print() printf("\n"); } -template - void print(T0 t0, T1 t1, ARGS ...args) +template + void print(T0 t0, T1 t1, ARGS&&... args) { static const unsigned short int size = sizeof...(ARGS); std::cout << t0 << ":" << t1 << " (" << size << "), "; - print(args...); + print(std::forward(args)...); } void get(int) @@ -31,18 +41,31 @@ void get(int) } template - void get(int n, T0 t0, T1* t1, ARGS ...args) + void get(int n, T0 t0, T1& t1, ARGS&&... args) { std::cout << t0 << ":" " (" << n << "), "; - *t1 = n; - get(n+1,args...); + t1 = n; + get(n+1,std::forward(args)...); } using namespace std; -void fileSysTest( cw::object_t* cfg, int argc, char* argv[] ) +void variadicTplTest( cw::object_t* cfg, int argc, const char* argv[] ) +{ + print("a", 1, "b", 3.14, "c",5L); + + int v0=0,v1=0,v2=0; + get(0, "a", v0, "b", v1, "c", v2); + printf("get: %i %i %i",v0,v1,v2); + + printf("\n"); +} + + + +void fileSysTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::fileSysPathPart_t* pp = cw::fileSysPathParts(__FILE__); @@ -59,7 +82,7 @@ void fileSysTest( cw::object_t* cfg, int argc, char* argv[] ) } -void numbCvtTest( cw::object_t* cfg, int argc, char* argv[] ) +void numbCvtTest( cw::object_t* cfg, int argc, const char* argv[] ) { int8_t x0 = 3; int x1 = 127; @@ -76,7 +99,7 @@ void numbCvtTest( cw::object_t* cfg, int argc, char* argv[] ) } -void objectTest( cw::object_t* cfg, int argc, char* argv[] ) +void objectTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::object_t* o; const char s [] = "{ a:1, b:2, c:[ 1.23, 4.56 ] }"; @@ -91,48 +114,62 @@ void objectTest( cw::object_t* cfg, int argc, char* argv[] ) int a = 0; int b = 0; - o->getv("a",&a,"b",&b); + o->getv("a",a,"b",b); printf("G: %i %i\n",a,b); o->free(); - } -void threadTest( cw::object_t* cfg, int argc, char* argv[] ) +void threadTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::threadTest(); } +void websockSrvTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::websockSrvTest(); } +void serialPortSrvTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::serialPortSrvTest(); } +void midiDeviceTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::midi::device::test();} +void textBufTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::textBuf::test(); } +void audioBufTest( cw::object_t* cfg, int argc, const char* argv[] ) { cw::audio::buf::test(); } +void audioPortTest( cw::object_t* cfg, int argc, const char* argv[] ) { - cw::threadTest(); + cw::audio::device::test( false, argc, argv ); } -void variadicTplTest( cw::object_t* cfg, int argc, char* argv[] ) +void stubTest( cw::object_t* cfg, int argc, const char* argv[] ) { - print("a", 1, "b", 3.14, "c",5L); - - int v0=0,v1=0,v2=0; - get(0, "a", &v0, "b", &v1, "c", &v2); - printf("%i %i %i",v0,v1,v2); - - printf("\n"); + typedef struct v_str + { + int x = 1; + int y = 2; + void* z = nullptr; + } v_t; + + + v_t v; + printf("%i %i %p\n",v.x,v.y,v.z); } - -int main( int argc, char* argv[] ) +int main( int argc, const char* argv[] ) { typedef struct func_str { const char* label; - void (*func)(cw::object_t* cfg, int argc, char* argv[] ); + void (*func)(cw::object_t* cfg, int argc, const char* argv[] ); } func_t; // function dispatch list func_t modeArray[] = { + { "variadicTpl", variadicTplTest }, { "fileSys", fileSysTest }, { "numbCvt", numbCvtTest }, { "object", objectTest }, { "thread", threadTest }, - { "variadicTpl", variadicTplTest }, + { "websockSrv", websockSrvTest }, + { "serialSrv", serialPortSrvTest }, + { "midiDevice", midiDeviceTest }, + { "textBuf", textBufTest }, + { "audioBuf", audioBufTest }, + { "audioPort",audioPortTest }, + { "stub", stubTest }, { nullptr, nullptr } }; diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..0a55d2f --- /dev/null +++ b/setup.sh @@ -0,0 +1,2 @@ +export LD_LIBRARY_PATH=~/sdk/libwebsockets/build/out/lib + diff --git a/study/serial/Makefile b/study/serial/Makefile new file mode 100644 index 0000000..94aeec7 --- /dev/null +++ b/study/serial/Makefile @@ -0,0 +1,4 @@ + + +serial : serial.c + gcc -g -Wall -o $@ serial.c diff --git a/study/serial/arduino_xmt_rcv/Makefile b/study/serial/arduino_xmt_rcv/Makefile new file mode 100644 index 0000000..ced7b94 --- /dev/null +++ b/study/serial/arduino_xmt_rcv/Makefile @@ -0,0 +1,37 @@ + + +# compiler flags: +# -Os : optimize size +# -DF_CPU=16000000UL : define F_CPU as 16Mghz +# -mmcu=atmega328p : target MCU + + +#MMCU=atmega328 +MMCU=atmega2560 + +ifeq ($(MMCU),atmega328) +PROG_MMCU=atmega328p +PROG_DEV=arduino +AVRD_CONF= +endif + +ifeq ($(MMCU),atmega2560) +PROG_MMCU=$(MMCU) +PROG_DEV=wiring +AVRD_CONF="-C/etc/avrdude/avrdude.conf" +endif + +main.hex : main.c + # compile to object file (optimize for size) + avr-gcc -Os -DF_CPU=16000000UL -mmcu=$(MMCU) -o main.elf main.c + # convert ELF format to an IHEX format as used by avrdude + avr-objcopy -O ihex -R .eeprom main.elf main.hex + + +burn: + avrdude $(AVR_CONF) -v -p$(PROG_MMCU) -c$(PROG_DEV) -P/dev/ttyACM0 -b115200 -D -Uflash:w:main.hex:i + +clean : + rm -f main.o main.elf main.hex + + diff --git a/study/serial/arduino_xmt_rcv/main.c b/study/serial/arduino_xmt_rcv/main.c new file mode 100644 index 0000000..4c8a054 --- /dev/null +++ b/study/serial/arduino_xmt_rcv/main.c @@ -0,0 +1,123 @@ +#define BAUD 38400 + + +#include +#include +#include + +#if defined(__AVR_ATmega2560__) +#define SERIAL_RX_ISR USART0_RX_vect +#elif defined(__AVR_ATmega328__) +#define SERIAL_RX_ISR USART_RX_vect +#else +#error Unknown target processor +#endif + +void uart_putchar(char c); + +void on_error( const char* msg, uint8_t code ) +{ + uart_putchar('0' + code ); + uart_putchar(':'); + for(; *msg; ++msg ) + uart_putchar(*msg); + +} + +// Basic UART setup code from here: +// https://appelsiini.net/2011/simple-usart-with-avr-libc/ + + +void uart_init(void) +{ + UBRR0H = UBRRH_VALUE; + UBRR0L = UBRRL_VALUE; + +#if USE_2X + UCSR0A |= _BV(U2X0); +#else + UCSR0A &= ~(_BV(U2X0)); +#endif + + UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); // 8-bit data + UCSR0B = _BV(RXEN0) | _BV(TXEN0) | _BV(RXCIE0); // Enable RX and TX and RX intertupt enable +} + +void uart_putchar(char c) +{ + loop_until_bit_is_set(UCSR0A, UDRE0); // Wait until data register empty. + UDR0 = c; +} + +void uart_putchar_alt(char c) +{ + UDR0 = c; + loop_until_bit_is_set(UCSR0A, TXC0); // Wait until transmission ready. +} + +char uart_getchar(void) +{ + loop_until_bit_is_set(UCSR0A, RXC0); // Wait until data exists. + return UDR0; +} + + +#define BUF_N (64) // size of receive buffer + +// Note that 'buf_i_idx' must be declared volatile or the +// the compare in the main loop will not work. +volatile int buf_i_idx = 0; // receive buffer input index +int buf_o_idx = 0; // receive buffer output index + +// Receive buffer +char buf[ BUF_N ]; + + +ISR(SERIAL_RX_ISR) +{ + // receive the incoming byte + buf[ buf_i_idx ] = uart_getchar(); + + // advance the buffer input index + buf_i_idx = (buf_i_idx + 1) % BUF_N; +} + + +int main (void) +{ + char c; + + cli(); // mask all interupts + uart_init(); // setup UART data format and baud rate + sei(); // re-enable interrupts + + // set pin 5 of PORTB for output + DDRB |= _BV(DDB5); + + + uart_putchar('a'); + + for(;;) + { + // if there are bytes waiting in the receive buffer + if( buf_o_idx != buf_i_idx ) + { + // get the waiting byte + c = buf[buf_o_idx]; + + // advance the buffer output index + buf_o_idx = (buf_o_idx+1) % BUF_N; + + // transmit the buffer input index as an asii char. + c += 1; + + PORTB ^= _BV(PORTB5);// toggle LED + + // transmit + uart_putchar(c); + + } + + } + +} diff --git a/study/serial/arduino_xmt_rcv/main.elf b/study/serial/arduino_xmt_rcv/main.elf new file mode 100755 index 0000000..2f7a96b Binary files /dev/null and b/study/serial/arduino_xmt_rcv/main.elf differ diff --git a/study/serial/arduino_xmt_rcv/main.hex b/study/serial/arduino_xmt_rcv/main.hex new file mode 100644 index 0000000..b4ec808 --- /dev/null +++ b/study/serial/arduino_xmt_rcv/main.hex @@ -0,0 +1,45 @@ +:100000000C9472000C9486000C9486000C9486006C +:100010000C9486000C9486000C9486000C94860048 +:100020000C9486000C9486000C9486000C94860038 +:100030000C9486000C9486000C9486000C94860028 +:100040000C9486000C9486000C9486000C94860018 +:100050000C9486000C9486000C9486000C94860008 +:100060000C9486000C94C6000C9486000C948600B8 +:100070000C9486000C9486000C9486000C948600E8 +:100080000C9486000C9486000C9486000C948600D8 +:100090000C9486000C9486000C9486000C948600C8 +:1000A0000C9486000C9486000C9486000C948600B8 +:1000B0000C9486000C9486000C9486000C948600A8 +:1000C0000C9486000C9486000C9486000C94860098 +:1000D0000C9486000C9486000C9486000C94860088 +:1000E0000C94860011241FBECFEFD1E2DEBFCDBF3E +:1000F00000E00CBF22E0A0E0B2E001C01D92A434F9 +:10010000B207E1F70E9406010C9458010C9400001C +:100110001092C50089E18093C400E0ECF0E080819A +:100120008D7F808386E08093C20088E98093C10040 +:1001300008959091C00095FFFCCF8093C60008956C +:100140000F931F93CF93DF938C0180E3860F0E9460 +:1001500099008AE30E949900E8018991811105C004 +:10016000DF91CF911F910F9108950E949900F5CFD3 +:100170008093C6008091C00086FFFCCF08958091D7 +:10018000C00087FFFCCF8091C60008951F920F9298 +:100190000FB60F9211240BB60F922F933F934F93EC +:1001A0005F936F937F938F939F93AF93BF93CF93FF +:1001B000DF93EF93FF93C0910202D09103020E945C +:1001C000BF00CC5FDD4F88838091020290910302D3 +:1001D000019660E470E00E943001909303028093E6 +:1001E0000202FF91EF91DF91CF91BF91AF919F916B +:1001F0008F917F916F915F914F913F912F910F90D0 +:100200000BBE0F900FBE0F901F901895F8940E9490 +:1002100088007894259A81E60E94990000E410E015 +:10022000C0E280910002909101022091020230917F +:10023000030282179307C9F3FC01EC5FFD4F208195 +:100240000196B8010E943001909301028093000250 +:1002500085B18C2785B981E0820F0E949900E1CF9A +:1002600097FB072E16F4009407D077FD09D00E9463 +:10027000440107FC05D03EF4909581959F4F089569 +:10028000709561957F4F0895AA1BBB1B51E107C074 +:10029000AA1FBB1FA617B70710F0A61BB70B881F16 +:1002A000991F5A95A9F780959095BC01CD010895A5 +:0402B000F894FFCFF0 +:00000001FF diff --git a/study/serial/serial.c b/study/serial/serial.c new file mode 100644 index 0000000..20fcdb7 --- /dev/null +++ b/study/serial/serial.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include + +int set_interface_attribs(int fd, int speed) +{ + struct termios tty; + + if (tcgetattr(fd, &tty) < 0) + { + printf("Error from tcgetattr: %s\n", strerror(errno)); + return -1; + } + + cfsetospeed(&tty, (speed_t)speed); + cfsetispeed(&tty, (speed_t)speed); + + tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */ + tty.c_cflag &= ~CSIZE; + tty.c_cflag |= CS8; /* 8-bit characters */ + tty.c_cflag &= ~PARENB; /* no parity bit */ + tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */ + tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */ + + /* setup for non-canonical mode */ + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tty.c_oflag &= ~OPOST; + + /* fetch bytes as they become available */ + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 0; + + if (tcsetattr(fd, TCSANOW, &tty) != 0) + { + printf("Error from tcsetattr: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +void set_mincount(int fd, int mcount) +{ + struct termios tty; + + if (tcgetattr(fd, &tty) < 0) + { + printf("Error tcgetattr: %s\n", strerror(errno)); + return; + } + + tty.c_cc[VMIN] = mcount ? 1 : 0; + tty.c_cc[VTIME] = 5; /* half second timer */ + + if (tcsetattr(fd, TCSANOW, &tty) < 0) + printf("Error tcsetattr: %s\n", strerror(errno)); +} + +void print_attributes(int fd ) +{ + struct termios attr; + if(tcgetattr(fd,&attr) == -1) + printf("tcgetattr failed."); + + printf("ibaud:%i",cfgetispeed(&attr)); + printf("obaud:%i",cfgetospeed(&attr)); + +} + +int main() +{ + char *portname = "/dev/ttyACM0"; + int fd; + int wlen; + + fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC); + if (fd < 0) + { + printf("Error opening %s: %s\n", portname, strerror(errno)); + return -1; + } + + /*baudrate 115200, 8 bits, no parity, 1 stop bit */ + set_interface_attribs(fd, B38400); + //set_mincount(fd, 0); /* set to pure timed read */ + + print_attributes(fd); + + unsigned char buf[80]; + + buf[0] = '0'; + + printf("Reading\n"); + + /* simple noncanonical input */ + do + { + int rdlen; + + int displayStringFl = 0; + + + /* simple output */ + wlen = write(fd, buf, 1); + if (wlen != 1) + { + printf("Error from write: %d, %d\n", wlen, errno); + } + + tcdrain(fd); /* delay for output */ + + + + //rdlen = read(fd, buf, sizeof(buf) - 1); + rdlen = read(fd, buf, 1); + + printf("."); + + if (rdlen > 0) + { + printf("rdlen:%i\n",rdlen); + + if(displayStringFl) + { + buf[rdlen] = 0; + printf("Read %d: \"%s\"\n", rdlen, buf); + } + else + { + unsigned char *p; + printf("Read %d:", rdlen); + for (p = buf; rdlen-- > 0; p++) + printf(" 0x%x", *p); + printf("\n"); + } + + } + else + if (rdlen < 0) + { + printf("Error from read: %d: %s\n", rdlen, strerror(errno)); + } + + sleep(1); + /* repeat read to get full message */ + } while (1); +} diff --git a/valgrind_test.sh b/valgrind_test.sh index 7754f78..6f2f5b8 100755 --- a/valgrind_test.sh +++ b/valgrind_test.sh @@ -1,3 +1,4 @@ -valgrind --track-origins=yes --leak-check=yes --log-file=vg0.txt ./cw_rt $1 $2 +valgrind --track-origins=yes --leak-check=yes --log-file=vg0.txt ./cw_rt $1 $2 $3 +