Many changes and additions.

Added serial port, websocket, midi and initial audio functionality.
This commit is contained in:
kpl 2019-12-24 10:05:24 -05:00
parent 3954d794f1
commit d6e0f5e675
46 changed files with 8597 additions and 242 deletions

View File

@ -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)

View File

@ -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.
- thread needs setters and getters for internal variables
- change cwMpScNbQueue so that it does not require 'new'.

991
cwAudioBuf.cpp Normal file
View File

@ -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<unsigned> 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<unsigned> 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; i<cp->mn; ++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; i<n0; ++i,++cp->phs)
b0[i*stride] = (float)(cp->gain * sin( 2.0 * M_PI * cp->hz * cp->phs / srate ));
for(i=0; i<n1; ++i,++cp->phs)
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(; b<ep; b+=stride)
sum += *b * *b;
return sum / bn;
}
void _cmApChFinalize( cmApCh* chPtr )
{
memRelease( chPtr->b );
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<sample_t>( 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<sample_t>(mn);
chPtr->mi = 0;
}
void _cmApIoFinalize( cmApIO* ioPtr )
{
unsigned i;
for(i=0; i<ioPtr->chCnt; ++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<cmApCh>(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; i<chCnt; ++i )
_cmApChInitialize( ioPtr->chArray + i, n, meterBufN );
}
void _cmApDevFinalize( cmApDev* dp )
{
unsigned i;
for(i=0; i<kIoApCnt; ++i)
_cmApIoFinalize( dp->ioArray+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; i<kIoApCnt; ++i)
{
unsigned chCnt = i==kInApIdx ? iChCnt : oChCnt;
unsigned bufN = i==kInApIdx ? iBufN : oBufN;
unsigned fpc = i==kInApIdx ? iFpC : oFpC;
_cmApIoInitialize( dp->ioArray+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<cmApDev>(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<sample_t>(_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; i<iop->chCnt; ++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; i<inPktCnt; ++i)
{
device::audioPacket_t* pp = inPktArray + i;
cmApIO* ip = _theBuf.devArray[pp->devIdx].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; j<pp->chCnt; ++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(; dp<ep; sp += ssn )
*dp++ = cp->gain * *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<unsigned>(&cp->fn,pp->audioFramesCnt);
}
}
}
// copy samples from the buffer to the packet
if( outPktArray != NULL )
{
for(i=0; i<outPktCnt; ++i)
{
device::audioPacket_t* pp = outPktArray + i;
cmApIO* op = _theBuf.devArray[pp->devIdx].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; j<pp->chCnt; ++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(; sp<ep; dp += pp->chCnt )
*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<unsigned>(&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(; i<n; ++i)
{
cmApCh* cp = _theBuf.devArray[devIdx].ioArray[idx].chArray + i;
cp->fl = 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(; i<n; ++i)
_theBuf.devArray[devIdx].ioArray[idx].chArray[i].gain = gain;
}
double cw::audio::buf::gain( unsigned devIdx, unsigned chIdx, unsigned flags )
{
if( devIdx == kInvalidIdx )
return 0;
unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx;
return _theBuf.devArray[devIdx].ioArray[idx].chArray[chIdx].gain;
}
unsigned cw::audio::buf::getStatus( unsigned devIdx, unsigned flags, double* meterArray, unsigned meterCnt, unsigned* faultCntPtr )
{
if( devIdx == kInvalidIdx )
return 0;
unsigned ioIdx = cwIsFlag(flags,kInFl) ? kInApIdx : kOutApIdx;
cmApIO* iop = _theBuf.devArray[devIdx].ioArray + ioIdx;
unsigned chCnt = std::min(iop->chCnt, meterCnt );
unsigned i;
if( faultCntPtr != NULL )
*faultCntPtr = iop->faultCnt;
for(i=0; i<chCnt; ++i)
meterArray[i] = _cmApMeterValue(iop->chArray + 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; i<ioPtr->chCnt; ++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; i<ioPtr->chCnt; ++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; i<bufChCnt; ++i)
bufArray[i] = NULL;
return;
}
unsigned idx = flags & kInFl ? kInApIdx : kOutApIdx;
const cmApIO* ioPtr = _theBuf.devArray[devIdx].ioArray + idx;
unsigned n = bufChCnt < ioPtr->chCnt ? bufChCnt : ioPtr->chCnt;
//unsigned offs = flags & kInFl ? ioPtr->oi : ioPtr->ii;
cmApCh* cp = ioPtr->chArray;
for(i=0; i<n; ++i,++cp)
{
unsigned offs = flags & kInFl ? cp->oi : 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; i<minChCnt; ++i)
{
cmApCh* ocp = op->chArray + 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(; i<oBufChCnt; ++i)
if( oBufArray[i] != NULL )
memset( oBufArray[i], 0, byteCnt );
}
}
void cw::audio::buf::advance( unsigned devIdx, unsigned flags )
{
unsigned i;
if( devIdx == kInvalidIdx )
return;
if( flags & kInFl )
{
cmApIO* ioPtr = _theBuf.devArray[devIdx].ioArray + kInApIdx;
for(i=0; i<ioPtr->chCnt; ++i)
{
cmApCh* cp = ioPtr->chArray + i;
cp->oi = (cp->oi + ioPtr->dspFrameCnt) % ioPtr->n;
//cmThUIntDecr(&cp->fn,ioPtr->dspFrameCnt);
std::atomic_fetch_sub<unsigned>(&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<unsigned>(&ioPtr->ioFrameCnt, ioPtr->dspFrameCnt);
}
}
if( flags & kOutFl )
{
cmApIO* ioPtr = _theBuf.devArray[devIdx].ioArray + kOutApIdx;
for(i=0; i<ioPtr->chCnt; ++i)
{
cmApCh* cp = ioPtr->chArray + i;
cp->ii = (cp->ii + ioPtr->dspFrameCnt) % ioPtr->n;
//cmThUIntIncr(&cp->fn,ioPtr->dspFrameCnt);
std::atomic_fetch_add<unsigned>(&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<unsigned>(&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; i<chCnt; ++i)
{
cmApIO* ip = _theBuf.devArray[iDevIdx ].ioArray + kInApIdx;
cmApIO* op = _theBuf.devArray[oDevIdx].ioArray + kOutApIdx;
cwAssert( ip->dspFrameCnt == 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; j<kIoApCnt; ++j)
{
cmApIO* ip = _theBuf.devArray[i].ioArray + j;
unsigned ii = 0;
unsigned oi = 0;
unsigned fn = 0;
for(k=0; k<ip->chCnt; ++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; i<sigN; ++i)
{
iSig[i] = i;
oSig[i] = 0;
}
pkt.devIdx = 0;
pkt.begChIdx = 0;
pkt.chCnt = inChCnt;
pkt.audioFramesCnt = framesPerCycle;
pkt.bitsPerSample = 32;
pkt.flags = 0;
time::get(pkt.timeStamp);
// initialize a the audio buffer
initialize(devCnt,meterMs);
// setup the buffer with the specific parameters to by used by the host audio ports
setup(devIdx,srate,dspFrameCnt,cycleCnt,inChCnt,framesPerCycle,outChCnt,framesPerCycle);
// simulate cylcing through sigN buffer transactions
for(i=0; i<sigN; i+=framesPerCycle*inChCnt)
{
// setup an incoming audio packet
pkt.audioFramesCnt = framesPerCycle;
pkt.audioBytesPtr = iSig+i;
// simulate a call from the audio port with incoming samples
// (fill the audio buffers internal input buffers)
update(&pkt,1,NULL,0);
// if all devices need to be serviced
while( isDeviceReady( devIdx, kInFl | kOutFl ))
{
// get pointers to full internal input buffers
get(devIdx, kInFl, inBufArray, bufChCnt );
// get pointers to empty internal output buffers
get(devIdx, kOutFl, outBufArray, bufChCnt );
// Warning: pointers to disabled channels will be set to NULL.
// simulate a play through by copying the incoming samples to the outgoing buffers.
for(j=0; j<bufChCnt; ++j)
if( outBufArray[j] != NULL )
{
// if the input is disabled - but the output is not then zero the output buffer
if( inBufArray[j] == NULL )
memset( outBufArray[j], 0, dspFrameCnt*sizeof(sample_t));
else
// copy the input to the output
memcpy( outBufArray[j], inBufArray[j], dspFrameCnt*sizeof(sample_t));
}
// signal the buffer that this cycle is complete.
// (marks current internal input/output buffer empty/full)
advance( devIdx, kInFl | kOutFl );
}
pkt.audioBytesPtr = os;
// simulate a call from the audio port picking up outgoing samples
// (empties the audio buffers internal output buffers)
update(NULL,0,&pkt,1);
os += pkt.audioFramesCnt * pkt.chCnt;
}
for(i=0; i<sigN; ++i)
cwLogInfo("%f ",oSig[i]);
cwLogInfo("\n");
finalize();
}
/// [cwAudioBufExample]

241
cwAudioBuf.h Normal file
View File

@ -0,0 +1,241 @@
//( {file_desc: "Thread safe audio buffer class." kw:[rt audio]}
//
// This file defines an audio buffer class which handles
// buffering incoming (recording) and outgoing (playback)
// samples in a thread-safe manner.
//
// Usage example and testing code:
// See cmApBufTest() and cmAudioSysTest().
// \snippet cmApBuf.c cmApBufExample
//
// Notes on channel flags:
// Disabled channels: kChFl is cleared
// cmApBufGet()
// in - return NULL buffer pointers
// out - return NULL buffer points
//
// cmApBufUpdate()
// in - incoming samples are set to 0.
// out - outgoing samples are set to 0.
//
// Muted channels: kMuteFl is set
// cmApBufUpdate()
// in - incoming samples are set to 0.
// out - outgoing samples are set to 0.
//
// Tone channels: kToneFl is set
// cmApBufUpdate()
// in - incoming samples are filled with a 1k sine tone
// out - outgoing samples are filled with a 1k sine tone
//)
#ifndef cmApBuf_H
#define cmApBuf_H
namespace cw
{
namespace audio
{
namespace buf
{
//(
typedef device::sample_t sample_t;
enum
{
kOkAbRC = 0
};
// Allocate and initialize an audio buffer.
// devCnt - count of devices this buffer will handle.
// meterMs - length of the meter buffers in milliseconds (automatically limit to the range:10 to 1000)
rc_t initialize( unsigned devCnt, unsigned meterMs );
// Deallocate and release any resource held by an audio buffer allocated via initialize().
rc_t finalize();
// Configure a buffer for a given device.
rc_t setup(
unsigned devIdx, //< device to setup
double srate, //< device sample rate (only required for synthesizing the correct test-tone frequency)
unsigned dspFrameCnt, // dspFrameCnt - count of samples in channel buffers returned via get()
unsigned cycleCnt, //< number of audio port cycles to store
unsigned inChCnt, //< input channel count on this device
unsigned inFramesPerCycle, //< maximum number of incoming sample frames on an audio port cycle
unsigned outChCnt, //< output channel count on this device
unsigned outFramesPerCycle //< maximum number of outgoing sample frames in an audio port cycle
);
// Prime the buffer with 'audioCycleCnt' * outFramesPerCycle samples ready to be played
rc_t primeOutput( unsigned devIdx, unsigned audioCycleCnt );
// Notify the audio buffer that a device is being enabled or disabled.
void onPortEnable( unsigned devIdx, bool enabelFl );
// This function is called asynchronously by the audio device driver to transfer incoming samples to the
// the buffer and to send outgoing samples to the DAC. This function is
// intended to be called from the audio port callback function (\see cmApCallbackPtr_t).
// This function is thread-safe under the condition where the audio device uses
// different threads for input and output.
//
// Enable Flag:
// Input: If an input channel is disabled then the incoming samples are replaced with zeros.
// Output: If an output channel is disabled then the packet samples are set to zeros.
//
// Tone Flag:
// Input: If the tone flag is set on an input channel then the incoming samples are set to a sine tone.
// Output: If the tone flag is set on an output channel then the packet samples are set to a sine tone.
//
// The enable flag has higher precedence than the tone flag therefore disabled channels
// will be set to zero even if the tone flag is set.
rc_t update(
device::audioPacket_t* inPktArray, //< full audio packets from incoming audio (from ADC)
unsigned inPktCnt, //< count of incoming audio packets
device::audioPacket_t* outPktArray, //< empty audio packet for outgoing audio (to DAC)
unsigned outPktCnt); //< count of outgoing audio packets
// Channel flags
enum
{
kInFl = 0x01, //< Identify an input channel
kOutFl = 0x02, //< Identify an output channel
kEnableFl = 0x04, //< Set to enable a channel, Clear to disable.
kChFl = 0x08, //< Used to enable/disable a channel
kMuteFl = 0x10, //< Mute this channel
kToneFl = 0x20, //< Generate a tone on this channel
kMeterFl = 0x40, //< Turn meter's on/off
kPassFl = 0x80 //< Pass input channels throught to the output. Must use getIO() to implement this functionality.
};
// Return the meter window period as set by initialize()
unsigned meterMs();
// Set the meter update period. THis function limits the value to between 10 and 1000.
void setMeterMs( unsigned meterMs );
// Returns the channel count set via setup().
unsigned channelCount( unsigned devIdx, unsigned flags );
// Set chIdx to -1 to enable all channels on this device.
// Set flags to {kInFl | kOutFl} | {kChFl | kToneFl | kMeterFl} | { kEnableFl=on | 0=off }
void setFlag( unsigned devIdx, unsigned chIdx, unsigned flags );
// Return true if the the flags is set.
bool isFlag( unsigned devIdx, unsigned chIdx, unsigned flags );
// Set chIdx to -1 to enable all channels on this device.
void enableChannel( unsigned devIdx, unsigned chIdx, unsigned flags );
// Returns true if an input/output channel is enabled on the specified device.
bool isChannelEnabled(unsigned devIdx, unsigned chIdx, unsigned flags );
// Set the state of the tone generator on the specified channel.
// Set chIdx to -1 to apply the change to all channels on this device.
// Set flags to {kInFl | kOutFl} | { kEnableFl=on | 0=off }
void enableTone( unsigned devIdx, unsigned chIdx, unsigned flags );
// Returns true if an input/output tone is enabled on the specified device.
bool isToneEnabled(unsigned devIdx, unsigned chIdx, unsigned flags );
// Mute a specified channel.
// Set chIdx to -1 to apply the change to all channels on this device.
// Set flags to {kInFl | kOutFl} | { kEnableFl=on | 0=off }
void enableMute( unsigned devIdx, unsigned chIdx, unsigned flags );
// Returns true if an input/output channel is muted on the specified device.
bool isMuteEnabled(unsigned devIdx, unsigned chIdx, unsigned flags );
// Set the specified channel to pass through.
// Set chIdx to -1 to apply the change to all channels on this device.
// Set flags to {kInFl | kOutFl} | { kEnableFl=on | 0=off }
void enablePass( unsigned devIdx, unsigned chIdx, unsigned flags );
// Returns true if pass through is enabled on the specified channel.
bool isPassEnabled(unsigned devIdx, unsigned chIdx, unsigned flags );
// Turn meter data collection on and off.
// Set chIdx to -1 to apply the change to all channels on this device.
// Set flags to {kInFl | kOutFl} | { kEnableFl=on | 0=off }
void enableMeter( unsigned devIdx, unsigned chIdx, unsigned flags );
// Returns true if an input/output tone is enabled on the specified device.
bool isMeterEnabled(unsigned devIdx, unsigned chIdx, unsigned flags );
// Return the meter value for the requested channel.
// Set flags to kInFl | kOutFl.
sample_t meter(unsigned devIdx, unsigned chIdx, unsigned flags );
// Set chIdx to -1 to apply the gain to all channels on the specified device.
void setGain( unsigned devIdx, unsigned chIdx, unsigned flags, double gain );
// Return the current gain seting for the specified channel.
double gain( unsigned devIdx, unsigned chIdx, unsigned flags );
// Get the meter and fault status of the channel input or output channel array of a device.
// Set 'flags' to { kInFl | kOutFl }.
// The returns value is the count of channels actually written to meterArray.
// If 'faultCntPtr' is non-NULL then it is set to the faultCnt of the associated devices input or output buffer.
unsigned getStatus( unsigned devIdx, unsigned flags, double* meterArray, unsigned meterCnt, unsigned* faultCntPtr );
// Do all enabled input/output channels on this device have samples available?
// 'flags' can be set to either or both kInFl and kOutFl
bool isDeviceReady( unsigned devIdx, unsigned flags );
// This function is called by the application to get full incoming sample buffers and
// to fill empty outgoing sample buffers.
// Upon return each element in bufArray[bufChCnt] holds a pointer to a buffer assoicated
// with an audio channel or to NULL if the channel is disabled.
// 'flags' can be set to kInFl or kOutFl but not both.
// The buffers pointed to by bufArray[] each contain 'dspFrameCnt' samples. Where
// 'dspFrameCnt' was set in the earlier call to setup() for this device.
// (see initialize()).
// Note that this function just returns audio information it does not
// change any internal states.
void get( unsigned devIdx, unsigned flags, sample_t* bufArray[], unsigned bufChCnt );
// This function replaces calls to get() and implements pass-through and output
// buffer zeroing:
//
// 1) get(in);
// 2) get(out);
// 3) Copy through channels marked for 'pass' and set the associated oBufArray[i] channel to NULL.
// 4) Zero all other enabled output channels.
//
// Notes:
// 1) The oBufArray[] channels that are disabled or marked for pass-through will
// be set to NULL.
// 2) The client is required to use this function to implement pass-through internally.
// 3) This function just returns audio information it does not
// change any internal states.
// 4) The timestamp pointers are optional.
void getIO( unsigned iDevIdx, sample_t* iBufArray[], unsigned iBufChCnt, time::spec_t* iTimeStampPtr,
unsigned oDevIdx, sample_t* oBufArray[], unsigned oBufChCnt, time::spec_t* oTimeStampPtr );
// The application calls this function each time it completes processing of a bufArray[]
// returned from get(). 'flags' can be set to either or both kInFl and kOutFl.
// This function should only be called from the client thread.
void advance( unsigned devIdx, unsigned flags );
// Copy all available samples incoming samples from an input device to an output device.
// The source code for this example is a good example of how an application should use get()
// and advance().
void inputToOutput( unsigned inDevIdx, unsigned outDevIdx );
// Print the current buffer state.
void report( textBuf::handle_t tbH );
// Run a buffer usage simulation to test the class. cmAudioPortTest.c calls this function.
void test();
//)
}
}
}
#endif

848
cwAudioPort.cpp Normal file
View File

@ -0,0 +1,848 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwTextBuf.h"
#include "cwTime.h"
#include "cwAudioPort.h"
#include "cwAudioBuf.h"
#ifdef cwLINUX
#include "cwAudioPortAlsa.h"
#endif
#ifdef cwOSX
#include "cmAudioPortOsx.h"
#endif
namespace cw
{
namespace audio
{
namespace device
{
typedef struct
{
unsigned begDevIdx;
unsigned endDevIdx;
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 sr, unsigned frmPerCycle, cbFunc_t cb, void* cbData );
rc_t (*deviceStart)( unsigned devIdx );
rc_t (*deviceStop)( unsigned devIdx );
bool (*deviceIsStarted)( unsigned devIdx );
} cmApDriver_t;
typedef struct
{
cmApDriver_t* drvArray;
unsigned drvCnt;
unsigned devCnt;
} cmAp_t;
cmAp_t* _ap = NULL;
rc_t _cmApIndexToDev( unsigned devIdx, cmApDriver_t** drvPtrPtr, unsigned* devIdxPtr )
{
assert( drvPtrPtr != NULL && devIdxPtr != NULL );
*drvPtrPtr = NULL;
*devIdxPtr = kInvalidIdx;
unsigned i;
for(i=0; i<_ap->drvCnt; ++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<cmAp_t>(1);
_ap->drvCnt = 1;
_ap->drvArray = memAllocZ<cmApDriver_t>(_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; i<n; ++i)
{
const char* s = deviceLabel(i);
if( s!=NULL && strcmp(s,label)==0)
return i;
}
return kInvalidIdx;
}
unsigned cw::audio::device::deviceChannelCount( 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->deviceChannelCount(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; i<n; ++i,++k)
{
cwLogInfo( "%i %f in:%i (%i) out:%i (%i) %s",
k, drvPtr->deviceSampleRate(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 <return value> describe a block of app buffer channels which will send/recv samples.
// *pktChIdxPtr and <return value> 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; i<bufIdx+bufChCnt; ++i)
{
unsigned j;
float* op = p + i;
ph = phs;
for(j=0; j<frmCnt; j++, op+=chCnt, ph++)
{
*op = (float)(0.9 * sin( 2.0 * M_PI * hz * ph / r->srate ));
}
}
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; i<srcFrameCnt; ++i)
{
for(j=0; j<chCnt; ++j)
r->buf[ r->bufInIdx + j ] = sp[ (i*srcChCnt) + j ];
for(; j<r->chCnt; ++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; i<dstFrameCnt; ++i)
{
// copy the first chCnt samples from the internal buf to the output buf
for(j=0; j<chCnt; ++j)
dp[ (i*dstChCnt) + j ] = r->buf[ r->bufOutIdx + j ];
// zero any output ch's for which there is no internal buf channel
for(; j<dstChCnt; ++j)
dp[ (i*dstChCnt) + j ] = 0;
// advance the internal buffer
r->bufOutIdx = (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; i<inPktCnt; ++i)
{
cmApPortTestRecd* r = (cmApPortTestRecd*)inPktArray[i].userCbPtr;
if( inPktArray[i].devIdx == r->inDevIdx )
{
// 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; i<outPktCnt; ++i)
{
cmApPortTestRecd* r = (cmApPortTestRecd*)outPktArray[i].userCbPtr;
if( outPktArray[i].devIdx == r->outDevIdx )
{
// 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 <srate> -c <chcnt> -b <bufcnt> -f <frmcnt> -i <idevidx> -o <odevidx> -t -p -h \n"
"\n"
"-r <srate> = sample rate\n"
"-a <chidx> = first channel\n"
"-c <chcnt> = audio channels\n"
"-b <bufcnt> = count of buffers\n"
"-f <frmcnt> = count of samples per buffer\n"
"-i <idevidx> = input device index\n"
"-o <odevidx> = 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<argc; ++i)
if( strcmp(label,argv[i]) == 0 )
{
if(boolFl)
return 1;
if( i == (argc-1) )
return defaultVal;
return atoi(argv[i+1]);
}
return defaultVal;
}
unsigned _cmGlobalInDevIdx = 0;
unsigned _cmGlobalOutDevIdx = 0;
void _cmApPortCb2( audioPacket_t* inPktArray, unsigned inPktCnt, audioPacket_t* outPktArray, unsigned outPktCnt )
{
buf::inputToOutput( _cmGlobalInDevIdx, _cmGlobalOutDevIdx );
buf::update( inPktArray, inPktCnt, outPktArray, outPktCnt );
}
}
}
}
// Audio Port testing function
cw::rc_t cw::audio::device::test( bool runFl, int argc, const char** argv )
{
cmApPortTestRecd r;
unsigned i;
int result = 0;
textBuf::handle_t tbH = textBuf::create();
if( _cmApGetOpt(argc,argv,"-h",0,true) )
_cmApPrintUsage();
runFl = _cmApGetOpt(argc,argv,"-p",!runFl,true)?false:true;
r.srate = _cmApGetOpt(argc,argv,"-r",44100,false);
r.chIdx = _cmApGetOpt(argc,argv,"-a",0,false);
r.chCnt = _cmApGetOpt(argc,argv,"-c",2,false);
r.bufCnt = _cmApGetOpt(argc,argv,"-b",3,false);
r.framesPerCycle = _cmApGetOpt(argc,argv,"-f",512,false);
r.bufFrmCnt = (r.bufCnt*r.framesPerCycle);
r.bufSmpCnt = (r.chCnt * r.bufFrmCnt);
r.logCnt = 100;
r.meterMs = 50;
sample_t buf[r.bufSmpCnt];
char log[r.logCnt];
unsigned ilog[r.logCnt];
r.inDevIdx = _cmGlobalInDevIdx = _cmApGetOpt(argc,argv,"-i",0,false);
r.outDevIdx = _cmGlobalOutDevIdx = _cmApGetOpt(argc,argv,"-o",2,false);
r.phase = 0;
r.frqHz = 2000;
r.bufInIdx = 0;
r.bufOutIdx = 0;
r.bufFullCnt = 0;
r.logIdx = 0;
r.buf = buf;
r.log = log;
r.ilog = ilog;
r.cbCnt = 0;
cwLogInfo("%s in:%i out:%i chidx:%i chs:%i bufs=%i frm=%i rate=%f",runFl?"exec":"rpt",r.inDevIdx,r.outDevIdx,r.chIdx,r.chCnt,r.bufCnt,r.framesPerCycle,r.srate);
/*
if( cmApFileAllocate(rpt) != kOkRC )
{
cwLogInfo("Audio port file allocation failed.");
result = -1;
goto errLabel;
}
// allocate the non-real-time port
if( cmApNrtAllocate(rpt) != kOkRC )
{
cwLogInfo("Non-real-time system allocation failed.");
result = 1;
goto errLabel;
}
*/
// initialize the audio device interface
if( initialize() != kOkRC )
{
cwLogInfo("Initialize failed.");
result = 1;
goto errLabel;
}
// report the current audio device configuration
for(i=0; i<deviceCount(); ++i)
{
cwLogInfo("%i [in: chs=%i frames=%i] [out: chs=%i frames=%i] srate:%f %s",i,deviceChannelCount(i,true),deviceFramesPerCycle(i,true),deviceChannelCount(i,false),deviceFramesPerCycle(i,false),deviceSampleRate(i),deviceLabel(i));
}
// report the current audio devices using the audio port interface function
report();
if( runFl )
{
// initialize the audio bufer
buf::initialize( deviceCount(), r.meterMs );
// setup the buffer for the output device
buf::setup( r.outDevIdx, r.srate, r.framesPerCycle, r.bufCnt, deviceChannelCount(r.outDevIdx,true), r.framesPerCycle, deviceChannelCount(r.outDevIdx,false), r.framesPerCycle );
// setup the buffer for the input device
if( r.inDevIdx != r.outDevIdx )
buf::setup( r.inDevIdx, r.srate, r.framesPerCycle, r.bufCnt, deviceChannelCount(r.inDevIdx,true), r.framesPerCycle, deviceChannelCount(r.inDevIdx,false), r.framesPerCycle );
// setup an output device
if(deviceSetup(r.outDevIdx,r.srate,r.framesPerCycle,_cmApPortCb2,&r) != kOkRC )
cwLogInfo("Out device setup failed.");
else
// setup an input device
if( deviceSetup(r.inDevIdx,r.srate,r.framesPerCycle,_cmApPortCb2,&r) != kOkRC )
cwLogInfo("In device setup failed.");
else
// start the input device
if( deviceStart(r.inDevIdx) != kOkRC )
cwLogInfo("In device start failed.");
else
// start the output device
if( deviceStart(r.outDevIdx) != kOkRC )
cwLogInfo("Out Device start failed.");
cwLogInfo("q=quit O/o output tone, I/i input tone P/p pass");
char c;
while((c=getchar()) != 'q')
{
//cmApAlsaDeviceRtReport(rpt,r.outDevIdx);
switch(c)
{
case 'i':
case 'I':
buf::enableTone(r.inDevIdx,-1,buf::kInFl | (c=='I'?buf::kEnableFl:0));
break;
case 'o':
case 'O':
buf::enableTone(r.outDevIdx,-1,buf::kOutFl | (c=='O'?buf::kEnableFl:0));
break;
case 'p':
case 'P':
buf::enablePass(r.outDevIdx,-1,buf::kOutFl | (c=='P'?buf::kEnableFl:0));
break;
case 's':
buf::report(tbH);
cwLogInfo("%s",textBuf::text(tbH));
textBuf::clear(tbH);
break;
}
}
// stop the input device
if( deviceIsStarted(r.inDevIdx) )
if( deviceStop(r.inDevIdx) != kOkRC )
cwLogInfo("In device stop failed.");
// stop the output device
if( deviceIsStarted(r.outDevIdx) )
if( deviceStop(r.outDevIdx) != kOkRC )
cwLogInfo("Out device stop failed.");
}
errLabel:
// release any resources held by the audio port interface
if( finalize() != kOkRC )
cwLogInfo("Finalize failed.");
buf::finalize();
//cmApNrtFree();
//cmApFileFree();
// report the count of audio buffer callbacks
cwLogInfo("cb count:%i", r.cbCnt );
//for(i=0; i<_logCnt; ++i)
// cwLogInfo("%c(%i)",_log[i],_ilog[i]);
//cwLogInfo("\n");
textBuf::destroy(tbH);
return result;
}
/// [cmAudioPortExample]

119
cwAudioPort.h Normal file
View File

@ -0,0 +1,119 @@
//( { file_desc: "Cross platform audio device interface." kw:[audio rt] }
//
// This interface provides data declarations for platform dependent
// audio I/O functions. The implementation for the functions are
// in platform specific modules. See cmAudioPortOsx.c and cmAudioPortAlsa.c.
//
// ALSA Notes:
// Assign capture device to line or mic input:
// amixer -c 0 cset iface=MIXER,name='Input Source',index=0 Mic
// amixer -c 0 cset iface=MIXER,name='Input Source',index=0 Line
//
// -c 0 select the first card
// -iface=MIXER the cset is targetting the MIXER component
// -name='Input Source',index=0 the control to set is the first 'Input Source'
// Note that the 'Capture' control sets the input gain.
//
// See alsamixer for a GUI to accomplish the same thing.
//
//
//)
#ifndef cwAudioPort_H
#define cwAudioPort_H
namespace cw
{
namespace audio
{
namespace device
{
typedef float sample_t;
// audioPacket_t flags
enum
{
kInterleavedApFl = 0x01, // The audio samples are interleaved.
kFloatApFl = 0x02 // The audio samples are single precision floating point values.
};
// Audio packet record used by the audioPacket_t callback.
// Audio ports send and receive audio using this data structure.
typedef struct
{
unsigned devIdx; // device associated with packet
unsigned begChIdx; // first device channel
unsigned chCnt; // count of channels
unsigned audioFramesCnt; // samples per channel (see note below)
unsigned bitsPerSample; // bits per sample word
unsigned flags; // kInterleavedApFl | kFloatApFl
void* audioBytesPtr; // pointer to sample data
void* userCbPtr; // user defined argument passed in via deviceSetup()
time::spec_t timeStamp; // Packet time stamp.
} audioPacket_t;
// Audio port callback signature.
// inPktArray[inPktCnt] are full packets of audio coming from the ADC to the application.
// outPktArray[outPktCnt] are empty packets of audio which will be filled by the application
// and then sent to the DAC.
//
// The value of audioFrameCnt gives the number of samples per channel which are available
// in the packet data buffer 'audioBytesPtr'. The callback function may decrease this number in
// output packets if the number of samples available is less than the size of the buffer.
// It is the responsibility of the calling audio port to notice this change and pass the new,
// decreased number of samples to the hardware.
//
// In general it should be assmed that this call is made from a system thread which is not
// the same as the application thread.
// The usual thread safety precautions should therefore be taken if this function implementation
// interacts with data structures also handled by the application. The audio buffer class (\see cmApBuf.h)
// is designed to provide a safe and efficient way to communicate between
// the audio thread and the application.
typedef void (*cbFunc_t)( audioPacket_t* inPktArray, unsigned inPktCnt, audioPacket_t* outPktArray, unsigned outPktCnt );
rc_t initialize();
rc_t finalize();
unsigned deviceCount();
const char* deviceLabel( unsigned devIdx );
unsigned deviceLabelToIndex( const char* label );
unsigned deviceChannelCount( unsigned devIdx, bool inputFl );
// Get the current sample rate of a device. Note that if the device has both
// input and output capability then the sample rate is the same for both.
double deviceSampleRate( unsigned devIdx );
unsigned deviceFramesPerCycle( unsigned devIdx, bool inputFl );
// Configure a device.
// All devices must be setup before they are started.
// framesPerCycle is the requested number of samples per audio callback. The
// actual number of samples made from a callback may be smaller. See the note
// regarding this in audioPacket_t.
// If the device cannot support the requested configuration then the function
// will return an error code.
// If the device is started when this function is called then it will be
// automatically stopped and then restarted following the reconfiguration.
// If the reconfiguration fails then the device may not be restared.
rc_t deviceSetup( unsigned devIdx, double srate, unsigned framesPerCallback, cbFunc_t cbFunc, void* cbArg );
// Start a device. Note that the callback may be made prior to this function returning.
rc_t deviceStart( unsigned devIdx );
rc_t deviceStop( unsigned devIdx );
bool deviceIsStarted( unsigned devIdx );
// Print a report of all the current audio device configurations.
void report();
// Test the audio port by synthesizing a sine signal or passing audio through
// from the input to the output. This is also a good example of how to
// use all of the functions in the interface.
// Set runFl to false to print a report without starting any audio devices.
// See cmAudiotPortTest.c for usage example for this function.
rc_t test( bool runFl, int argc, const char** argv );
}
}
}
#endif

1715
cwAudioPortAlsa.cpp Normal file

File diff suppressed because it is too large Load Diff

53
cwAudioPortAlsa.h Normal file
View File

@ -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

View File

@ -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; }
};
}

View File

@ -10,9 +10,11 @@
#include <climits>
#include <cinttypes>
#include <cfloat>
#include <cmath>
#include <algorithm> // std::min,std::max
#include <utility> // std::forward
#include <limits> // std::numeric_limits<
#include <atomic>
#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;

View File

@ -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);

10
cwLog.h
View File

@ -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__ )

View File

@ -11,13 +11,17 @@ namespace cw
{
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<unsigned*>(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;

283
cwMidi.cpp Normal file
View File

@ -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 );
}

174
cwMidi.h Normal file
View File

@ -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

892
cwMidiAlsa.cpp Normal file
View File

@ -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 <alsa/asoundlib.h>
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; i<p->devCnt; ++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; i<dev->iPortCnt; ++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<dev_t>(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(i<p->devCnt);
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<port_t>(p->devArray[i].iPortCnt);
if( p->devArray[i].oPortCnt > 0 )
p->devArray[i].oPortArray = memAllocZ<port_t>(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(j<p->devArray[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(k<p->devArray[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<cmMpRoot_t>(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<struct pollfd>(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; i<p->devCnt; ++i)
{
for(j=0; j<p->devArray[i].iPortCnt; ++j)
{
parser::destroy(p->devArray[i].iPortArray[j].parserH);
memRelease( p->devArray[i].iPortArray[j].nameStr );
}
for(j=0; j<p->devArray[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; di<dn; ++di)
if( di==devIdx || devIdx == kInvalidIdx )
{
unsigned pi;
unsigned pn = portCount(di,kInMpFl);
for(pi=0; pi<pn; ++pi)
if( pi==portIdx || portIdx == kInvalidIdx )
if( parser::installCallback( p->devArray[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; di<dn; ++di)
if( di==devIdx || devIdx == kInvalidIdx )
{
unsigned pi;
unsigned pn = portCount(di,kInMpFl);
for(pi=0; pi<pn; ++pi)
if( pi==portIdx || portIdx == kInvalidIdx )
if( parser::hasCallback( p->devArray[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; di<dn; ++di)
if( di==devIdx || devIdx == kInvalidIdx )
{
unsigned pi;
unsigned pn = portCount(di,kInMpFl);
for(pi=0; pi<pn; ++pi)
if( pi==portIdx || portIdx == kInvalidIdx )
if( parser::hasCallback( p->devArray[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; i<p->devCnt; ++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; j<d->iPortCnt; ++j)
_cmMpReportPort(tbH,d->iPortArray+j);
if(d->oPortCnt > 0 )
textBuf::print( tbH," Output:\n");
for(j=0; j<d->oPortCnt; ++j)
_cmMpReportPort(tbH,d->oPortArray+j);
}
}

563
cwMidiPort.cpp Normal file
View File

@ -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<handle_t,parser_t>(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<parser_t>( 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<byte_t>( 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<cbRecd_t>( 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; i<n; ++i)
if( strcmp(name(i),deviceName)==0)
return i;
return kInvalidIdx;
}
unsigned cw::midi::device::portNameToIndex( unsigned devIdx, unsigned flags, const char* portNameStr )
{
unsigned i;
unsigned n = portCount(devIdx,flags);
for(i=0; i<n; ++i)
if( strcmp(portName(devIdx,flags,i),portNameStr)==0)
return i;
return kInvalidIdx;
}
//====================================================================================================
//
//
namespace cw
{
namespace midi
{
namespace device
{
void testCallback( const packet_t* pktArray, unsigned pktCnt )
{
unsigned i,j;
for(i=0; i<pktCnt; ++i)
{
const packet_t* p = pktArray + i;
for(j=0; j<p->msgCnt; ++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 (<q>=quit)\n");
while((ch = getchar()) != 'q')
{
send(2,0,0x90,60,60);
}
errLabel:
textBuf::destroy(tbH);
finalize();
return rc;
}

97
cwMidiPort.h Normal file
View File

@ -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<struct parser_str> 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

81
cwMpScNbQueue.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef cwMpScNbQueue_h
#define cwMpScNbQueue_h
namespace cw
{
template<typename T>
class MpScNbQueue
{
public:
typedef struct node_str
{
std::atomic<struct node_str*> next = nullptr;
T* payload = nullptr;
} node_t;
MpScNbQueue()
{
node_t* stub = memAllocZ<node_t>();
_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<node_t>(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<node_t*> _head;
};
void mpScNbQueueTest();
}
#endif

View File

@ -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>(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 );

462
cwSerialPort.cpp Normal file
View File

@ -0,0 +1,462 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwSerialPort.h"
#include <poll.h>
#include <termios.h>
#include <unistd.h> // ::close()
#include <fcntl.h> // O_RDWR
#include <sys/ioctl.h> // 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<handle_t,port_t>(h); }
void _setClosedState( port_t* p )
{
if( p->_deviceStr != nullptr )
cw::memFree(const_cast<char*>(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<port_t>();
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<byteN );
return rc;
}
cw::rc_t cw::serialPort::receive( handle_t h, unsigned& readN_Ref)
{
rc_t rc = kOkRC;
port_t* p = _portHandleToPtr(h);
const unsigned bufN = 512;
char buf[ bufN ];
readN_Ref = 0;
if((rc = receive(h,buf,bufN,readN_Ref)) == kOkRC )
if( readN_Ref > 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;
}

73
cwSerialPort.h Normal file
View File

@ -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<struct port_str> 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

175
cwSerialPortSrv.cpp Normal file
View File

@ -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<handle_t,this_t>(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<this_t*>(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<this_t>();
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<const char*>(byteA);
for(unsigned i=0; i<byteN; ++i)
printf("%c:%i ",text[i],(int)text[i]);
if( byteN )
fflush(stdout);
}
}
cw::rc_t cw::serialPortSrvTest()
{
// Use this test an Arduino running study/serial/arduino_xmt_rcv/main.c
rc_t rc = kOkRC;
const char* device = "/dev/ttyACM0";
unsigned baud = 38400;
unsigned serialCfgFlags = serialPort::kDefaultCfgFlags;
unsigned pollPeriodMs = 50;
serialPortSrv::handle_t h;
rc = serialPortSrv::create(h,device,baud,serialCfgFlags,&serialPortSrvTestCb,nullptr,pollPeriodMs);
serialPortSrv::start(h);
bool quitFl = false;
printf("q=quit\n");
while(!quitFl)
{
char c = getchar();
if( c == 'q')
quitFl = true;
else
if( '0' <= c and c <= 'z' )
serialPortSrv::send(h,&c,1);
}
serialPortSrv::destroy(h);
return rc;
}

28
cwSerialPortSrv.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef cwSerialPortSrv_H
#define cwSerialPortSrv_H
namespace cw
{
namespace serialPortSrv
{
typedef handle<struct this_str> 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

View File

@ -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);
}

View File

@ -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

View File

@ -6,6 +6,8 @@
namespace cw
{
namespace textBuf
{
typedef struct textBuf_str
{
char* buf;
@ -18,102 +20,126 @@ namespace cw
unsigned intFlags;
int floatWidth;
int floatDecPlN;
} textBuf_t;
} this_t;
}
}
#define _textBufHandleToPtr(h) handleToPtr<textBufH_t,textBuf_t>(h)
#define _handleToPtr(h) handleToPtr<handle_t,this_t>(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<textBuf_t>();
this_t* p = memAllocZ<this_t>();
p->buf = memAllocZ<char>(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( n > 0 )
{
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->allocCharN += expandCharN;
p->buf = memResizeZ<char>( 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);
}

View File

@ -4,27 +4,32 @@
namespace cw
{
typedef handle<struct textBuf_str> textBufH_t;
namespace textBuf
{
typedef handle<struct textBuf_str> 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 );
const char* textBufText( textBufH_t h);
rc_t destroy(handle_t& hRef );
rc_t textBufPrintf( textBufH_t h, const char* fmt, va_list vl );
rc_t textBufPrintf( textBufH_t h, const char* fmt, ... );
void clear( handle_t h );
const char* text( handle_t h);
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 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 print( handle_t h, const char* fmt, va_list vl );
rc_t print( handle_t h, const char* fmt, ... );
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 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

View File

@ -9,6 +9,8 @@
namespace cw
{
namespace thread
{
enum
{
kDoExitThFl = 0x01,
@ -19,8 +21,8 @@ namespace cw
typedef struct thread_str
{
pthread_t pThreadH;
kThreadStateId_t stateId;
threadFunc_t func;
stateId_t stateId;
cbFunc_t func;
void* funcArg;
unsigned doFlags;
unsigned stateMicros;
@ -28,7 +30,7 @@ namespace cw
unsigned sleepMicros = 15000;
} thread_t;
#define _threadHandleToPtr(h) handleToPtr<threadH_t,thread_t>(h)
inline thread_t* _handleToPtr(handle_t h) { return handleToPtr<handle_t,thread_t>(h); }
rc_t _waitForState( thread_t* p, unsigned stateId )
{
@ -94,16 +96,17 @@ namespace cw
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;
pthread_attr_t attr;
if((rc = threadDestroy(hRef)) != kOkRC )
if((rc = destroy(hRef)) != kOkRC )
return rc;
thread_t* p = memAllocZ<thread_t>();
@ -114,7 +117,19 @@ cw::rc_t cw::threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, in
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 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.");
@ -124,14 +139,14 @@ cw::rc_t cw::threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, in
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<unsigned>(pthread_self()); }
namespace cw
{
@ -198,15 +219,15 @@ namespace cw
cw::rc_t cw::threadTest()
{
threadH_t h;
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;
}

View File

@ -3,27 +3,35 @@
namespace cw
{
namespace thread
{
typedef enum
{
kNotInitThId,
kPausedThId,
kRunningThId,
kExitedThId
} kThreadStateId_t;
} stateId_t;
typedef handle<struct thread_str> threadH_t;
typedef handle<struct thread_str> 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
// 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 threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, int stateTimeOutMicros=100000, int pauseMicros=10000 );
rc_t threadDestroy( threadH_t& hRef );
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();
}

114
cwTime.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTime.h"
#ifdef cwOSX
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <unistd.h>
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;
}

56
cwTime.h Normal file
View File

@ -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

463
cwWebSock.cpp Normal file
View File

@ -0,0 +1,463 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwWebSock.h"
#include "cwMpScNbQueue.h"
#include <libwebsockets.h>
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<msg_t>* _q; // Thread safe, non-blocking, protocol independent msg queue.
} websock_t;
inline websock_t* _handleToPtr(handle_t h){ return handleToPtr<handle_t,websock_t>(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<protocolState_t*>(p->user);
if( ps == nullptr || ps->thisPtr == nullptr )
return 0; // TODO: issue a warning
session_t* sess = static_cast<session_t*>(user);
const struct lws_protocols* proto = lws_get_protocol(wsi);
protocolState_t* protoState = static_cast<protocolState_t*>(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<p->_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<char*>(p->_protocolA[i].name));
// TODO: delete any msgs in the protocol state here
auto ps = static_cast<protocolState_t*>(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<char*>(p->_mount->origin));
memFree(const_cast<char*>(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<websock_t>();
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<struct lws_protocols>(p->_protocolN);
// Setup the websocket internal protocol state array
for(unsigned i=0; i<protocolN; ++i)
{
// Allocate the application protocol state array where this application can keep protocol related info
auto protocolState = memAllocZ<protocolState_t>(1);
auto dummy = memAllocZ<msg_t>(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<struct lws_http_mount>(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<msg_t>();
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<msg_t>(1);
m->msg = memAllocZ<unsigned char>(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<protocolState_t*>(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<unsigned char>(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;
}

79
cwWebSock.h Normal file
View File

@ -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<struct websock_str> 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

205
cwWebSockSvr.cpp Normal file
View File

@ -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<handle_t,websockSrv_t>(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<websockSrv_t*>(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<websockSrv_t>();
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<appCtx_t*>(cbArg);
const char* msg = static_cast<const char*>(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;
}

38
cwWebSockSvr.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef cwWebsockSvr_H
#define cwWebsockSvr_H
namespace cw {
namespace websockSrv
{
typedef handle<struct websockSrv_str> 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

View File

@ -0,0 +1,96 @@
<meta charset="UTF-8">
<html>
<body>
<div id="titleDivId">Websock Test App</div><br>
Incoming text messages from the server.
<br>
<br>
<textarea id=r readonly cols=40 rows=10></textarea><br>
<br>
Outgoing text message to the server.
<input type="text" id="msgTextId" cols=40 rows=1>
<button id="sendBtnId", onclick="sendmsg();">Send</button>
<br>
<button id="quitBtnId", onclick="sendquit();">Quit</button>
</body>
<script>
function new_ws(urlpath, protocol)
{
if (typeof MozWebSocket != "undefined")
return new MozWebSocket(urlpath, protocol);
return new WebSocket(urlpath, protocol);
}
subscriber_ws = new_ws("ws://localhost:7681/", "websocksrv_test_protocol");
try {
subscriber_ws.onopen = function()
{
document.getElementById("quitBtnId").disabled = 0;
document.getElementById("sendBtnId").disabled = 0;
}
subscriber_ws.onmessage = function got_packet(msg)
{
document.getElementById("r").value = document.getElementById("r").value + msg.data + "\n";
document.getElementById("r").scrollTop = document.getElementById("r").scrollHeight;
}
subscriber_ws.onclose = function()
{
document.getElementById("quitBtnId").disabled = 1;
document.getElementById("sendBtnId").disabled = 1;
}
} catch(exception)
{
alert('<p>Error' + exception);
}
publisher_ws = new_ws("ws://localhost:7681//publisher", "websocksrv_test_protocol");
try {
publisher_ws.onopen = function()
{
document.getElementById("msgTextId").disabled = 0;
document.getElementById("titleDivId").style.color = '#00ff00'
}
publisher_ws.onmessage = function got_packet(msg)
{
console.log("rcv:"+msg.data)
}
publisher_ws.onclose = function()
{
document.getElementById("msgTextId").disabled = 1;
document.getElementById("titleDivId").style.color = '#ff0000'
}
} catch(exception)
{
alert('<p>Error' + exception);
}
function sendmsg()
{
var val = document.getElementById("msgTextId").value
publisher_ws.send(val);
document.getElementById("msgTextId").value = "";
}
function sendquit()
{
publisher_ws.send("quit");
}
</script>
</html>

View File

@ -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 <iostream>
@ -17,12 +27,12 @@ void print()
printf("\n");
}
template<typename T0, typename T1, typename... ARGS>
void print(T0 t0, T1 t1, ARGS ...args)
template<typename T0, typename T1, typename ...ARGS>
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>(args)...);
}
void get(int)
@ -31,18 +41,31 @@ void get(int)
}
template<typename T0, typename T1, typename... ARGS>
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>(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);
typedef struct v_str
{
int x = 1;
int y = 2;
void* z = nullptr;
} v_t;
int v0=0,v1=0,v2=0;
get(0, "a", &v0, "b", &v1, "c", &v2);
printf("%i %i %i",v0,v1,v2);
printf("\n");
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 }
};

2
setup.sh Normal file
View File

@ -0,0 +1,2 @@
export LD_LIBRARY_PATH=~/sdk/libwebsockets/build/out/lib

4
study/serial/Makefile Normal file
View File

@ -0,0 +1,4 @@
serial : serial.c
gcc -g -Wall -o $@ serial.c

View File

@ -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

View File

@ -0,0 +1,123 @@
#define BAUD 38400
#include <util/setbaud.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#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);
}
}
}

Binary file not shown.

View File

@ -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

152
study/serial/serial.c Normal file
View File

@ -0,0 +1,152 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
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);
}

View File

@ -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