Many changes and additions.
Added serial port, websocket, midi and initial audio functionality.
This commit is contained in:
parent
3954d794f1
commit
d6e0f5e675
25
Makefile
25
Makefile
@ -2,17 +2,32 @@
|
||||
HDR = cwCommon.h cwCommonImpl.h cwMem.h cwLog.h
|
||||
SRC = main.cpp cwCommonImpl.cpp cwMem.cpp cwLog.cpp
|
||||
|
||||
HDR += cwFileSys.h cwText.h cwFile.h cwLex.h cwNumericConvert.h
|
||||
SRC += cwFileSys.cpp cwText.cpp cwFile.cpp cwLex.cpp
|
||||
HDR += cwFileSys.h cwText.h cwFile.h cwTime.h cwLex.h cwNumericConvert.h
|
||||
SRC += cwFileSys.cpp cwText.cpp cwFile.cpp cwTime.cpp cwLex.cpp
|
||||
|
||||
HDR += cwObject.h cwTextBuf.h cwThread.h
|
||||
HDR += cwObject.h cwTextBuf.h cwThread.h cwMpScNbQueue.h
|
||||
SRC += cwObject.cpp cwTextBuf.cpp cwThread.cpp
|
||||
|
||||
HDR += cwWebSock.h cwWebSockSvr.h
|
||||
SRC += cwWebSock.cpp cwWebSockSvr.cpp
|
||||
|
||||
LIBS = -lpthread
|
||||
HDR += cwSerialPort.h cwSerialPortSrv.h
|
||||
SRC += cwSerialPort.cpp cwSerialPortSrv.cpp
|
||||
|
||||
HDR += cwMidi.h cwMidiPort.h
|
||||
SRC += cwMidi.cpp cwMidiPort.cpp cwMidiAlsa.cpp
|
||||
|
||||
HDR += cwAudioBuf.h cwAudioPort.h cwAudioPortAlsa.h
|
||||
SRC += cwAudioBuf.cpp cwAudioPort.cpp cwAudioPortAlsa.cpp
|
||||
|
||||
|
||||
LIBS = -lpthread -lwebsockets -lasound
|
||||
|
||||
WS_DIR = /home/kevin/sdk/libwebsockets/build/out
|
||||
INC_PATH = $(WS_DIR)/include
|
||||
LIB_PATH = $(WS_DIR)/lib
|
||||
|
||||
cw_rt : $(SRC) $(HDR)
|
||||
g++ -g --std=c++17 $(LIBS) -Wall -DcwLINUX -o $@ $(SRC)
|
||||
g++ -g --std=c++17 $(LIBS) -Wall -L$(LIB_PATH) -I$(INC_PATH) -DcwLINUX -o $@ $(SRC)
|
||||
|
||||
|
||||
|
10
README.md
10
README.md
@ -1,9 +1,19 @@
|
||||
|
||||
|
||||
- Clean up the cwObject namespace - add an 'object' namespace inside 'cw'
|
||||
|
||||
- Add underscore to the member variables of object_t.
|
||||
|
||||
-
|
||||
|
||||
- logDefaultFormatter() in cwLog.cpp uses stack allocated memory in a way that could easily be exploited.
|
||||
|
||||
- lexIntMatcher() in cwLex.cpp doesn't handle 'e' notation correctly. See note in code.
|
||||
|
||||
- numeric_convert() in cwNumericConvert.h could be made more efficient using type_traits.
|
||||
|
||||
- thread needs setters and getters for internal variables
|
||||
|
||||
- change cwMpScNbQueue so that it does not require 'new'.
|
||||
|
||||
|
||||
|
991
cwAudioBuf.cpp
Normal file
991
cwAudioBuf.cpp
Normal 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
241
cwAudioBuf.h
Normal 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
848
cwAudioPort.cpp
Normal 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
119
cwAudioPort.h
Normal 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
1715
cwAudioPortAlsa.cpp
Normal file
File diff suppressed because it is too large
Load Diff
53
cwAudioPortAlsa.h
Normal file
53
cwAudioPortAlsa.h
Normal 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
|
@ -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; }
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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 <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;
|
||||
|
@ -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
10
cwLog.h
@ -49,8 +49,8 @@ namespace cw
|
||||
#define cwLogVDebugH(h,rc,fmt, vl) cw::logMsg( h, cw::kDebug_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, vl )
|
||||
#define cwLogDebugH( h,rc,fmt,...) cw::logMsg( h, cw::kDebug_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, ##__VA_ARGS__ )
|
||||
|
||||
#define cwLogVInfoH(h,rc,fmt, vl) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, vl )
|
||||
#define cwLogInfoH( h,rc,fmt,...) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, ##__VA_ARGS__ )
|
||||
#define cwLogVInfoH(h,fmt, vl) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, cw::kOkRC, fmt, vl )
|
||||
#define cwLogInfoH( h,fmt,...) cw::logMsg( h, cw::kInfo_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, cw::kOkRC, fmt, ##__VA_ARGS__ )
|
||||
|
||||
#define cwLogVWarningH(h,rc,fmt, vl) cw::logMsg( h, cw::kWarning_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, vl )
|
||||
#define cwLogWarningH( h,rc,fmt,...) cw::logMsg( h, cw::kWarning_LogLevel, __FUNCTION__, __FILE__, __LINE__, 0, rc, fmt, ##__VA_ARGS__ )
|
||||
@ -82,11 +82,9 @@ namespace cw
|
||||
|
||||
#endif
|
||||
|
||||
#define cwLogVInfoRC(rc,fmt, vl) cwLogVInfoH( cw::logGlobalHandle(), (rc), (fmt), (vl) )
|
||||
#define cwLogInfoRC( rc,fmt,...) cwLogInfoH( cw::logGlobalHandle(), (rc), (fmt), ##__VA_ARGS__ )
|
||||
|
||||
#define cwLogVInfo(fmt, vl) cwLogVInfoH( cw::logGlobalHandle(), cw::kOkRC, (fmt), (vl) )
|
||||
#define cwLogInfo( fmt,...) cwLogInfoH( cw::logGlobalHandle(), cw::kOkRC, (fmt), ##__VA_ARGS__ )
|
||||
#define cwLogVInfo(fmt, vl) cwLogVInfoH( cw::logGlobalHandle(), (fmt), (vl) )
|
||||
#define cwLogInfo( fmt,...) cwLogInfoH( cw::logGlobalHandle(), (fmt), ##__VA_ARGS__ )
|
||||
|
||||
#define cwLogVWarningRC(rc,fmt, vl) cwLogVWarningH( cw::logGlobalHandle(), (rc), (fmt), (vl) )
|
||||
#define cwLogWarningRC( rc,fmt,...) cwLogWarningH( cw::logGlobalHandle(), (rc), (fmt), ##__VA_ARGS__ )
|
||||
|
20
cwMem.cpp
20
cwMem.cpp
@ -9,15 +9,19 @@ namespace cw
|
||||
|
||||
void* _memAlloc( void* p0, unsigned n, bool zeroFl )
|
||||
{
|
||||
void* p = nullptr; // ptr to new block
|
||||
unsigned p0N = 0; // size of existing block
|
||||
void* p = nullptr; // ptr to new block
|
||||
unsigned p0N = 0; // size of existing block
|
||||
unsigned* p0_1 = nullptr; // pointer to base of existing block
|
||||
|
||||
n += sizeof(unsigned); // add space for the size of the block
|
||||
|
||||
// if there is no existing block
|
||||
if( p0 == nullptr )
|
||||
n += sizeof(unsigned); // add space for the size of the block
|
||||
else
|
||||
if( p0 != nullptr )
|
||||
{
|
||||
p0N = ((unsigned*)p0)[-1]; // get size of existing block
|
||||
// get a pointer to the base of the exsting block
|
||||
p0_1 = ((unsigned*)p0) - 1;
|
||||
|
||||
p0N = p0_1[0]; // get size of existing block
|
||||
|
||||
// if the block is shrinking
|
||||
if( p0N >= n )
|
||||
@ -29,7 +33,7 @@ namespace cw
|
||||
// if expanding then copy in data from existing block
|
||||
if( p0 != nullptr )
|
||||
{
|
||||
memcpy(p,p0,p0N);
|
||||
memcpy(p,p0_1,p0N);
|
||||
memFree(p0); // free the existing block
|
||||
}
|
||||
|
||||
@ -40,7 +44,7 @@ namespace cw
|
||||
// get pointer to base of new block
|
||||
unsigned* p1 = static_cast<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
283
cwMidi.cpp
Normal 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
174
cwMidi.h
Normal 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
892
cwMidiAlsa.cpp
Normal 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
563
cwMidiPort.cpp
Normal 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
97
cwMidiPort.h
Normal 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
81
cwMpScNbQueue.h
Normal 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
|
@ -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
462
cwSerialPort.cpp
Normal 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
73
cwSerialPort.h
Normal 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
175
cwSerialPortSrv.cpp
Normal 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
28
cwSerialPortSrv.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
1
cwText.h
1
cwText.h
@ -8,6 +8,7 @@ namespace cw
|
||||
|
||||
// if both s0 and s1 are nullptr then a match is indicated
|
||||
int textCompare( const char* s0, const char* s1 );
|
||||
int textCompare( const char* s0, const char* s1, unsigned n);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
153
cwTextBuf.cpp
153
cwTextBuf.cpp
@ -6,114 +6,140 @@
|
||||
|
||||
namespace cw
|
||||
{
|
||||
typedef struct textBuf_str
|
||||
namespace textBuf
|
||||
{
|
||||
char* buf;
|
||||
unsigned expandCharN; // count of characters to expand buf
|
||||
unsigned allocCharN; // current allocated size of buf
|
||||
unsigned endN; // current count of character in buf
|
||||
char* boolTrueText;
|
||||
char* boolFalseText;
|
||||
int intWidth;
|
||||
unsigned intFlags;
|
||||
int floatWidth;
|
||||
int floatDecPlN;
|
||||
} textBuf_t;
|
||||
|
||||
typedef struct textBuf_str
|
||||
{
|
||||
char* buf;
|
||||
unsigned expandCharN; // count of characters to expand buf
|
||||
unsigned allocCharN; // current allocated size of buf
|
||||
unsigned endN; // current count of character in buf
|
||||
char* boolTrueText;
|
||||
char* boolFalseText;
|
||||
int intWidth;
|
||||
unsigned intFlags;
|
||||
int floatWidth;
|
||||
int floatDecPlN;
|
||||
} this_t;
|
||||
}
|
||||
}
|
||||
|
||||
#define _textBufHandleToPtr(h) handleToPtr<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>();
|
||||
p->buf = memAllocZ<char>(initCharN);
|
||||
p->expandCharN = expandCharN;
|
||||
p->boolTrueText = memDuplStr("true");
|
||||
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( p->endN + n > p->allocCharN )
|
||||
if( n > 0 )
|
||||
{
|
||||
unsigned minExpandCharN = (p->endN + n) - p->allocCharN;
|
||||
unsigned expandCharN = std::max(minExpandCharN,p->expandCharN);
|
||||
p->allocCharN =+ expandCharN;
|
||||
p->buf = memResizeZ<char>( p->buf, p->allocCharN );
|
||||
n += 1; // add one to make space for the terminating zero
|
||||
|
||||
if( p->endN + n > p->allocCharN )
|
||||
{
|
||||
unsigned minExpandCharN = (p->endN + n) - p->allocCharN;
|
||||
unsigned expandCharN = std::max(minExpandCharN,p->expandCharN);
|
||||
p->allocCharN += expandCharN;
|
||||
p->buf = memResizeZ<char>( p->buf, p->allocCharN );
|
||||
}
|
||||
|
||||
int m = vsnprintf(p->buf + p->endN, n, fmt, vl );
|
||||
|
||||
cwAssert(m==(n-1));
|
||||
p->endN += n-1; // subtract 1 to no count the terminating zero in the character count
|
||||
}
|
||||
|
||||
int m = snprintf(p->buf + p->endN, n, fmt, vl );
|
||||
|
||||
cwAssert(m=n);
|
||||
p->endN += n;
|
||||
|
||||
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);
|
||||
}
|
||||
|
33
cwTextBuf.h
33
cwTextBuf.h
@ -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
|
||||
|
223
cwThread.cpp
223
cwThread.cpp
@ -9,101 +9,104 @@
|
||||
namespace cw
|
||||
{
|
||||
|
||||
enum
|
||||
namespace thread
|
||||
{
|
||||
kDoExitThFl = 0x01,
|
||||
kDoPauseThFl = 0x02,
|
||||
kDoRunThFl = 0x04
|
||||
};
|
||||
|
||||
typedef struct thread_str
|
||||
{
|
||||
pthread_t pThreadH;
|
||||
kThreadStateId_t stateId;
|
||||
threadFunc_t func;
|
||||
void* funcArg;
|
||||
unsigned doFlags;
|
||||
unsigned stateMicros;
|
||||
unsigned pauseMicros;
|
||||
unsigned sleepMicros = 15000;
|
||||
} thread_t;
|
||||
|
||||
#define _threadHandleToPtr(h) handleToPtr<threadH_t,thread_t>(h)
|
||||
|
||||
rc_t _waitForState( thread_t* p, unsigned stateId )
|
||||
{
|
||||
unsigned waitTimeMicroSecs = 0;
|
||||
|
||||
while( p->stateId != stateId && waitTimeMicroSecs < p->stateMicros )
|
||||
enum
|
||||
{
|
||||
sleepUs( p->sleepMicros );
|
||||
waitTimeMicroSecs += p->sleepMicros;
|
||||
kDoExitThFl = 0x01,
|
||||
kDoPauseThFl = 0x02,
|
||||
kDoRunThFl = 0x04
|
||||
};
|
||||
|
||||
typedef struct thread_str
|
||||
{
|
||||
pthread_t pThreadH;
|
||||
stateId_t stateId;
|
||||
cbFunc_t func;
|
||||
void* funcArg;
|
||||
unsigned doFlags;
|
||||
unsigned stateMicros;
|
||||
unsigned pauseMicros;
|
||||
unsigned sleepMicros = 15000;
|
||||
} thread_t;
|
||||
|
||||
inline thread_t* _handleToPtr(handle_t h) { return handleToPtr<handle_t,thread_t>(h); }
|
||||
|
||||
rc_t _waitForState( thread_t* p, unsigned stateId )
|
||||
{
|
||||
unsigned waitTimeMicroSecs = 0;
|
||||
|
||||
while( p->stateId != stateId && waitTimeMicroSecs < p->stateMicros )
|
||||
{
|
||||
sleepUs( p->sleepMicros );
|
||||
waitTimeMicroSecs += p->sleepMicros;
|
||||
}
|
||||
|
||||
return p->stateId==stateId ? kOkRC : kTimeOutRC;
|
||||
}
|
||||
|
||||
return p->stateId==stateId ? kOkRC : kTimeOutRC;
|
||||
}
|
||||
|
||||
void _threadCleanUpCallback(void* p)
|
||||
{
|
||||
((thread_t*)p)->stateId = kExitedThId;
|
||||
}
|
||||
|
||||
|
||||
void* _threadCallback(void* param)
|
||||
{
|
||||
thread_t* p = (thread_t*)param;
|
||||
|
||||
// set a clean up handler - this will be called when the
|
||||
// thread terminates unexpectedly or pthread_cleanup_pop() is called.
|
||||
pthread_cleanup_push(_threadCleanUpCallback,p);
|
||||
|
||||
while( cwIsFlag(p->doFlags,kDoExitThFl) == false )
|
||||
void _threadCleanUpCallback(void* p)
|
||||
{
|
||||
|
||||
// if we are in the pause state
|
||||
if( p->stateId == kPausedThId )
|
||||
{
|
||||
|
||||
sleepUs( p->pauseMicros );
|
||||
|
||||
// check if we have been requested to leave the pause state
|
||||
if( cwIsFlag(p->doFlags,kDoRunThFl) )
|
||||
{
|
||||
p->doFlags = cwClrFlag(p->doFlags,kDoRunThFl);
|
||||
p->stateId = kRunningThId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// call the user-defined function
|
||||
if( p->func(p->funcArg)==false )
|
||||
break;
|
||||
|
||||
// check if we have been requested to enter the pause state
|
||||
if( cwIsFlag(p->doFlags,kDoPauseThFl) )
|
||||
{
|
||||
p->doFlags = cwClrFlag(p->doFlags,kDoPauseThFl);
|
||||
p->stateId = kPausedThId;
|
||||
}
|
||||
}
|
||||
((thread_t*)p)->stateId = kExitedThId;
|
||||
}
|
||||
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
pthread_exit(NULL);
|
||||
void* _threadCallback(void* param)
|
||||
{
|
||||
thread_t* p = (thread_t*)param;
|
||||
|
||||
return p;
|
||||
// set a clean up handler - this will be called when the
|
||||
// thread terminates unexpectedly or pthread_cleanup_pop() is called.
|
||||
pthread_cleanup_push(_threadCleanUpCallback,p);
|
||||
|
||||
while( cwIsFlag(p->doFlags,kDoExitThFl) == false )
|
||||
{
|
||||
|
||||
// if we are in the pause state
|
||||
if( p->stateId == kPausedThId )
|
||||
{
|
||||
|
||||
sleepUs( p->pauseMicros );
|
||||
|
||||
// check if we have been requested to leave the pause state
|
||||
if( cwIsFlag(p->doFlags,kDoRunThFl) )
|
||||
{
|
||||
p->doFlags = cwClrFlag(p->doFlags,kDoRunThFl);
|
||||
p->stateId = kRunningThId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// call the user-defined function
|
||||
if( p->func(p->funcArg)==false )
|
||||
break;
|
||||
|
||||
// check if we have been requested to enter the pause state
|
||||
if( cwIsFlag(p->doFlags,kDoPauseThFl) )
|
||||
{
|
||||
p->doFlags = cwClrFlag(p->doFlags,kDoPauseThFl);
|
||||
p->stateId = kPausedThId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
pthread_exit(NULL);
|
||||
|
||||
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,24 +117,36 @@ 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 create failed.");
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread attribute init failed.");
|
||||
}
|
||||
else
|
||||
if ((sysRC = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) != 0)
|
||||
{
|
||||
p->stateId = kNotInitThId;
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread set detach attribute failed.");
|
||||
}
|
||||
else
|
||||
if((sysRC = pthread_create(&p->pThreadH, &attr, _threadCallback, (void*)p )) != 0 )
|
||||
{
|
||||
p->stateId = kNotInitThId;
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread create failed.");
|
||||
}
|
||||
|
||||
hRef.set(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::threadDestroy( threadH_t& hRef )
|
||||
cw::rc_t cw::thread::destroy( handle_t& hRef )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if( !hRef.isValid() )
|
||||
return rc;
|
||||
|
||||
thread_t* p = _threadHandleToPtr(hRef);
|
||||
thread_t* p = _handleToPtr(hRef);
|
||||
|
||||
// tell the thread to exit
|
||||
p->doFlags = cwSetFlag(p->doFlags,kDoExitThFl);
|
||||
@ -140,18 +155,19 @@ cw::rc_t cw::threadDestroy( threadH_t& hRef )
|
||||
if((rc = _waitForState(p,kExitedThId)) != kOkRC )
|
||||
return cwLogError(rc,"Thread timed out waiting for destroy.");
|
||||
|
||||
hRef.release();
|
||||
memRelease(p);
|
||||
hRef.clear();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::threadPause( threadH_t& h, unsigned cmdFlags )
|
||||
cw::rc_t cw::thread::pause( handle_t h, unsigned cmdFlags )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
bool pauseFl = cwIsFlag(cmdFlags,kThreadPauseFl);
|
||||
bool waitFl = cwIsFlag(cmdFlags,kThreadWaitFl);
|
||||
thread_t* p = _threadHandleToPtr(h);
|
||||
bool pauseFl = cwIsFlag(cmdFlags,kPauseFl);
|
||||
bool waitFl = cwIsFlag(cmdFlags,kWaitFl);
|
||||
thread_t* p = _handleToPtr(h);
|
||||
bool isPausedFl = p->stateId == kPausedThId;
|
||||
unsigned waitId;
|
||||
|
||||
@ -179,12 +195,17 @@ cw::rc_t cw::threadPause( threadH_t& h, unsigned cmdFlags )
|
||||
|
||||
}
|
||||
|
||||
cw::kThreadStateId_t cw::threadState( threadH_t h )
|
||||
cw::rc_t cw::thread::unpause( handle_t h )
|
||||
{ return pause( h, kWaitFl); }
|
||||
|
||||
cw::thread::stateId_t cw::thread::state( handle_t h )
|
||||
{
|
||||
thread_t* p = _threadHandleToPtr(h);
|
||||
thread_t* p = _handleToPtr(h);
|
||||
return p->stateId;
|
||||
}
|
||||
|
||||
unsigned cw::thread::id()
|
||||
{ return static_cast<unsigned>(pthread_self()); }
|
||||
|
||||
namespace cw
|
||||
{
|
||||
@ -198,15 +219,15 @@ namespace cw
|
||||
|
||||
cw::rc_t cw::threadTest()
|
||||
{
|
||||
threadH_t h;
|
||||
unsigned val = 0;
|
||||
rc_t rc;
|
||||
char c = 0;
|
||||
thread::handle_t h;
|
||||
unsigned val = 0;
|
||||
rc_t rc;
|
||||
char c = 0;
|
||||
|
||||
if((rc = threadCreate(h,_threadTestCb,&val)) != kOkRC )
|
||||
if((rc = thread::create(h,_threadTestCb,&val)) != kOkRC )
|
||||
return rc;
|
||||
|
||||
if((rc = threadPause(h,0)) != kOkRC )
|
||||
if((rc = thread::pause(h,0)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
|
||||
@ -225,18 +246,18 @@ cw::rc_t cw::threadTest()
|
||||
break;
|
||||
|
||||
case 's':
|
||||
cwLogInfo("state=%i\n",threadState(h));
|
||||
cwLogInfo("state=%i\n",thread::state(h));
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
{
|
||||
if( threadState(h) == kPausedThId )
|
||||
rc = threadPause(h,kThreadWaitFl);
|
||||
if( thread::state(h) == thread::kPausedThId )
|
||||
rc = thread::pause(h,thread::kWaitFl);
|
||||
else
|
||||
rc = threadPause(h,kThreadPauseFl|kThreadWaitFl);
|
||||
rc = thread::pause(h,thread::kPauseFl|thread::kWaitFl);
|
||||
|
||||
if( rc == kOkRC )
|
||||
cwLogInfo("new state:%i\n", threadState(h));
|
||||
cwLogInfo("new state:%i\n", thread::state(h));
|
||||
else
|
||||
{
|
||||
cwLogError(rc,"threadPause() test failed.");
|
||||
@ -256,7 +277,7 @@ cw::rc_t cw::threadTest()
|
||||
}
|
||||
|
||||
errLabel:
|
||||
rc_t rc0 = rc = threadDestroy(h);
|
||||
rc_t rc0 = rc = thread::destroy(h);
|
||||
|
||||
return rc == kOkRC ? rc0 : rc;
|
||||
}
|
||||
|
38
cwThread.h
38
cwThread.h
@ -3,27 +3,35 @@
|
||||
|
||||
namespace cw
|
||||
{
|
||||
typedef enum
|
||||
namespace thread
|
||||
{
|
||||
kNotInitThId,
|
||||
kPausedThId,
|
||||
kRunningThId,
|
||||
kExitedThId
|
||||
} kThreadStateId_t;
|
||||
typedef enum
|
||||
{
|
||||
kNotInitThId,
|
||||
kPausedThId,
|
||||
kRunningThId,
|
||||
kExitedThId
|
||||
} stateId_t;
|
||||
|
||||
typedef handle<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
|
||||
// pauseMicros = duration of thread sleep interval when in paused state.
|
||||
rc_t threadCreate( threadH_t& hRef, threadFunc_t func, void* funcArg, int stateTimeOutMicros=100000, int pauseMicros=10000 );
|
||||
rc_t threadDestroy( threadH_t& hRef );
|
||||
// stateMicros = total time out duration for switching to the exit state or for switching in/out of pause state.
|
||||
// pauseMicros = duration of thread sleep interval when in paused state.
|
||||
rc_t create( handle_t& hRef, cbFunc_t func, void* funcArg, int stateTimeOutMicros=100000, int pauseMicros=10000 );
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
enum { kThreadPauseFl=0x01, kThreadWaitFl=0x02 };
|
||||
rc_t threadPause( threadH_t& h, unsigned cmdFlags );
|
||||
kThreadStateId_t threadState( threadH_t h );
|
||||
|
||||
enum { kPauseFl=0x01, kWaitFl=0x02 };
|
||||
rc_t pause( handle_t h, unsigned cmdFlags = kWaitFl );
|
||||
rc_t unpause( handle_t h ); // same as threadPause(h,kWaitFl)
|
||||
|
||||
stateId_t state( handle_t h );
|
||||
|
||||
// Return the thread id of the calling context.
|
||||
unsigned id();
|
||||
}
|
||||
rc_t threadTest();
|
||||
}
|
||||
|
||||
|
114
cwTime.cpp
Normal file
114
cwTime.cpp
Normal 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
56
cwTime.h
Normal 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
463
cwWebSock.cpp
Normal 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
79
cwWebSock.h
Normal 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
205
cwWebSockSvr.cpp
Normal 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
38
cwWebSockSvr.h
Normal 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
|
96
html/websockSrvTest/test_websocket.html
Normal file
96
html/websockSrvTest/test_websocket.html
Normal 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>
|
||||
|
83
main.cpp
83
main.cpp
@ -3,11 +3,21 @@
|
||||
#include "cwCommonImpl.h"
|
||||
#include "cwMem.h"
|
||||
#include "cwFileSys.h"
|
||||
#include "cwTextBuf.h"
|
||||
#include "cwLex.h"
|
||||
#include "cwNumericConvert.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwThread.h"
|
||||
#include "cwText.h"
|
||||
#include "cwWebSock.h"
|
||||
#include "cwWebSockSvr.h"
|
||||
#include "cwSerialPort.h"
|
||||
#include "cwSerialPortSrv.h"
|
||||
#include "cwMidi.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwMidiPort.h"
|
||||
#include "cwAudioPort.h"
|
||||
#include "cwAudioBuf.h"
|
||||
|
||||
#include <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
2
setup.sh
Normal file
@ -0,0 +1,2 @@
|
||||
export LD_LIBRARY_PATH=~/sdk/libwebsockets/build/out/lib
|
||||
|
4
study/serial/Makefile
Normal file
4
study/serial/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
serial : serial.c
|
||||
gcc -g -Wall -o $@ serial.c
|
37
study/serial/arduino_xmt_rcv/Makefile
Normal file
37
study/serial/arduino_xmt_rcv/Makefile
Normal 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
|
||||
|
||||
|
123
study/serial/arduino_xmt_rcv/main.c
Normal file
123
study/serial/arduino_xmt_rcv/main.c
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
BIN
study/serial/arduino_xmt_rcv/main.elf
Executable file
BIN
study/serial/arduino_xmt_rcv/main.elf
Executable file
Binary file not shown.
45
study/serial/arduino_xmt_rcv/main.hex
Normal file
45
study/serial/arduino_xmt_rcv/main.hex
Normal 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
152
study/serial/serial.c
Normal 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);
|
||||
}
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user