From d6e0f5e67534e94a7c0559677862569f79db9b16 Mon Sep 17 00:00:00 2001 From: kpl Date: Tue, 24 Dec 2019 10:05:24 -0500 Subject: [PATCH] Many changes and additions. Added serial port, websocket, midi and initial audio functionality. --- Makefile | 25 +- README.md | 12 +- cwAudioBuf.cpp | 991 +++++++++++++ cwAudioBuf.h | 241 ++++ cwAudioPort.cpp | 848 +++++++++++ cwAudioPort.h | 119 ++ cwAudioPortAlsa.cpp | 1715 +++++++++++++++++++++++ cwAudioPortAlsa.h | 53 + cwCommon.h | 6 +- cwCommonImpl.h | 9 +- cwLog.cpp | 2 +- cwLog.h | 10 +- cwMem.cpp | 20 +- cwMidi.cpp | 283 ++++ cwMidi.h | 174 +++ cwMidiAlsa.cpp | 892 ++++++++++++ cwMidiPort.cpp | 563 ++++++++ cwMidiPort.h | 97 ++ cwMpScNbQueue.h | 81 ++ cwObject.h | 8 +- cwSerialPort.cpp | 462 ++++++ cwSerialPort.h | 73 + cwSerialPortSrv.cpp | 175 +++ cwSerialPortSrv.h | 28 + cwText.cpp | 8 + cwText.h | 1 + cwTextBuf.cpp | 153 +- cwTextBuf.h | 35 +- cwThread.cpp | 213 +-- cwThread.h | 38 +- cwTime.cpp | 114 ++ cwTime.h | 56 + cwWebSock.cpp | 463 ++++++ cwWebSock.h | 79 ++ cwWebSockSvr.cpp | 205 +++ cwWebSockSvr.h | 38 + html/websockSrvTest/test_websocket.html | 96 ++ main.cpp | 87 +- setup.sh | 2 + study/serial/Makefile | 4 + study/serial/arduino_xmt_rcv/Makefile | 37 + study/serial/arduino_xmt_rcv/main.c | 123 ++ study/serial/arduino_xmt_rcv/main.elf | Bin 0 -> 13244 bytes study/serial/arduino_xmt_rcv/main.hex | 45 + study/serial/serial.c | 152 ++ valgrind_test.sh | 3 +- 46 files changed, 8597 insertions(+), 242 deletions(-) create mode 100644 cwAudioBuf.cpp create mode 100644 cwAudioBuf.h create mode 100644 cwAudioPort.cpp create mode 100644 cwAudioPort.h create mode 100644 cwAudioPortAlsa.cpp create mode 100644 cwAudioPortAlsa.h create mode 100644 cwMidi.cpp create mode 100644 cwMidi.h create mode 100644 cwMidiAlsa.cpp create mode 100644 cwMidiPort.cpp create mode 100644 cwMidiPort.h create mode 100644 cwMpScNbQueue.h create mode 100644 cwSerialPort.cpp create mode 100644 cwSerialPort.h create mode 100644 cwSerialPortSrv.cpp create mode 100644 cwSerialPortSrv.h create mode 100644 cwTime.cpp create mode 100644 cwTime.h create mode 100644 cwWebSock.cpp create mode 100644 cwWebSock.h create mode 100644 cwWebSockSvr.cpp create mode 100644 cwWebSockSvr.h create mode 100644 html/websockSrvTest/test_websocket.html create mode 100644 setup.sh create mode 100644 study/serial/Makefile create mode 100644 study/serial/arduino_xmt_rcv/Makefile create mode 100644 study/serial/arduino_xmt_rcv/main.c create mode 100755 study/serial/arduino_xmt_rcv/main.elf create mode 100644 study/serial/arduino_xmt_rcv/main.hex create mode 100644 study/serial/serial.c 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 0000000000000000000000000000000000000000..2f7a96bf7da6481e06e82139d76f66f544487327 GIT binary patch literal 13244 zcmcJWdvp|amdAh9Nt49HB!~kdY61oUl}@_TK_2qxbaz4^A)%89KATR`2~m=cc_88= z9i9O}(sY92sGuUg=B$r#6h>EY*74{%v)~-pJ+luD4#(AV+;upkXPnWQ?Dy8A`{uUe z*?;DDs9)Xh=l-6zepRXJD*FA^6`H0gE7?@NvXmRJ#i`T;?A@wRrK#B}Pvz?VVXFf6 zFWZdG4jZQKU!F!Hs{3H+iQ1vIX|w!PhLR7P<EKK3f?&N z<@=v}bmG@1)M(C$8KZ|sUmDd;Ub_E?`=yLCf6N`8pydp&#imsL{$HwHXQJ`9)aVz# zAB_&|?T>EVF&d5kLJfW%jlZcfM}}f2)yUX)r=sz;)mkT$4}@zeHkB%pN!dTr(WEN>|Ljl7{eYz0FXjeIkl zJ3Od14bOaN;8Pl{I-5Uw@{?#xp=Hfhh#pGN!9nEaI*&NzL?U$Rc;4*Rr>@9d|Sd^I#OFmk9ibEIn| zG}4d#v(sLiw)#xQ$!7~*D>ypk)r|b#AD#AU_F%!mf*VGj`D1isXykS6*IFjVICjc7 zXKK}PtYflUu;t)nn27y0Z0qR6K=P?a>}304M+qR+Vk=UQ>YTJs9i~%BPd@dS-K+}! zxII0=pRm*ULdmC&*fR|i_EGzJ3HvE~PQrfLo|~|rv0sp|pS53@u%EL}PT2oozbIip zZ@)NUzhJ*4VZUg|3z4VB>LnArZHa zGRfv~>qOZi^|8)jV(w?GFn}kMPkqYDe3tnV+PMR6+1FQQ6`vZ+BAfTM6S_$4(m56L z!KOVWpW2;e4iol$S?LCby(cSqjPB2}2Io^lS;=D*%Ss-j{aMLl6wk^x+o8FTjn$tr z=D|dK`t4-%F+YRyMJlRum>5r#t*ZgHuC}mswUw=_?QC7`VC!lpTUUc@UG1`4V@6Ea z8oQ6JvAt}K-Otw8KDNe&*cywmHMZYgNE1QwsbRLpM%WrV!0ZRudV7%B2UFIbD(>y* zDsFDB>}+<=>vHrchod<0udTUx?gEF)QBu62y$PbYInvqM-c}si*iFRQ+Sl3KNtnSmCr9_pY8_)>VVXgW*l>z0BdS?e3XhPmW+qB-p$m z9O@1>_4Ke3fz7q`eYff*`o=X~>GduwoLv!aiFAhw^+C>cI$E1cOXe5~v7+@j7aLud zJl6$~Qv#*6Q%E)v4jipd{W=!v?FhGpTxIi0bl0(LqbTa=Zi2QvDx8@%h3t%Ux`f!L zAsfq66AH0cY6dpVj@y6%l`%&rR0!lQ6;+u=%OUN36fLE z$(tbOuj1qvAi0e?q0yoDw?Oi4kuI(RCtGYc@#1^PJ2ADSVleD?p~xU%EuCt3YO);N(h>%irQ;4oK0voH#&cp5kOK z$Q2)Oay7`T-*Vyvnf);*B_MOoa#9R(<>#EZL9Y6elTwhYzv9FNGWSoM*tDo}jB%oY z6dUJ(8D|AjlBJ&+Cb5G!^EgQdab3ts8c69>PBKB<1)OAnlofF@0c74BPO?F+Ddyxn zkoj&-vOpFr;AA;wx^O8cl^~1!oK%2ZyOI+>$l?GeK9D8XbJ7X2bR8#UAj?`g@q&0Z zaMA@*-owc?Al^GUc>%;1<>W;W|4vR`0;$-`$;%*>aZY{;viu+?uYgoN$_b5d#Su={ zfvkLvlYarJeu0yBKx$s))k8&~q((@!ITR?h$q7!q> z+d%s0ZaHz>2SGL-<779;9dB^52V~P*oa_L(^Ie^oV~&7qKF!HIkh?zQWHrd$|G`Nk z$USE`X#lzRzd30K>Hi;2ZUu>If8b~_(17+Qj;;mTqWzVl4xp|2ElgrfZvfh+<Lz~LcdZ3+J0Y^cgL9IwfX25R-+NI6nrD)E(wYeP8r0>&8IqC)4 zqb=a54`{Epl%rOl`?U&=LO}bpYL4oGhP2fj-2@cVuH)!Np#9oS9IXV3Yt0<302(?Ex{mpFPK=rQd$N527jTsx^FbMzkoJ)ynL zOU=Vo^oaHzMkl=C&iX@mR z!4(qBl3=z3b0oM@f~zFBT7tO}I3y^RphNk;FZ89fnS0O2`VL6EFkl z`Xz{Jk_@DfEh%Jc3fY!Iwx^ICDP(5~8B8I&QpoNUa$gGBlS1~Uko!}}z7#T)LSiXo ze+r4Gkl_?El0puokOxx8gDK=-3VA4n{5XX?oI)N+A&;hzLn-8N3VAGrJf1?HNFhg3 z$df7LXbO2Mg{Y}`gtIB!{3@IFZSMIBZ^7v|4{Z2{U-swXm;L5K{OIo}QGM;5z4L>; z`rbJYMlcu-cXdZPl*i|-50onpa^WDXj4$Eo8+iAqxX~f&9{C-5$??){Ce(DV^ml++DQ3p_7y`M7b0UpZr-oUG18{?y0 z1qoDe8`a%Lb+=L7O%>sxT(1s}>TaVt?J7v1dZ|&p)Tmx+R4=89@KCN-2S@c%qdM&> zNT52Vsqc*Hm?nkUNfqItT(1s}>Q19N?J6K@y@*1Mw-I4Lj@bE8#-r!u$Z=$lS76b3 zVY-Z+)7X_@XSh&B)d&2QSfAtCT3bVHQH}xBd%d1&<*i;>hr&Q;7mG3Asj0g`Rn}G2 z23*F@Y3$H?0OLbef-|b7aV?!!#<_(8bD*;PsxL)Wf;nyFx6D87Adcr0nP2q`}r8NBu zmn2A28A$;vO@9NIL}@B3DRjT}*4V&lDtp`r)77~#dWW|pQS00oeSff+X?l;pl}yxD4ZLsNIEgwb7S?;o6NA#^|S>#$~3(X+rjh>QU88u;}=G9*wiD! z|19)h6U?UGfc+iQ?z}UiIp%~4;XhyKsY1^bx*rm4U8p_Bcc8>9CHyuUAm{zm9@bj-&;8JgFhA@p3K7eMp=>DPDO-Wq71d4A<% zej!m`|4lvJir2ZZZNg8-dJo1NMUe|I--m_&X=tlII{wcK|7+0qqCS0xLG|Ae{ttv6 z75WRIzY#hOmyBg-pWc5{`}9+CKJ*MzPlg^BPw21U%cl+N$0_`aq3IPnrF@(LD}?_# z-LJd#^|ns?p@uv0nq40kq^#4Lz-vrX} z|1kf6O2T7}tN>oaY&MfJ7K>|Cq%(-vMY@d! z$9LnVT72Op8+6m4jHlx96Llv?Kp(hS2bPVcCEOb7+n^sS>r6=&Wb-bimg=i2S9uy5 z1O6X~8?{Y~II*g%Udz}*E~Qd*Sd7C(Cw{V5w(Rg0ATjDPn&kR$sB8QbSuCl8#(IC? z2ga!%sIET!Km|kkyseY$U>w>hseW;Ty_;c7cI9>{<>CKOSQ=150J zxD!{>&PZ?A(bn1Lz)wMrmhi^*=5TvwD_wTOO?_=R`;vC3si`}>(elvM%(AJ;|9{fo z+oHJs_Y8ky_5UNmsvCc0m6{Gl_5{;6jJ=dQUhdDADRn7TFq7rm3T@fs zno^+Dr+P4rf|if=R?KzJ<+uaE7|(}56+M1*M?z2Yth&m-N~yQ3Aj4<1N6+lM+y@9G KJtteqD))b}KHEkB literal 0 HcmV?d00001 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 +