From 7adaa96d90aa1b73c58e4e62ea68660d49f2a83c Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 24 Mar 2013 12:52:10 -0700 Subject: [PATCH 1/3] cmMidiAlsa.c: Implemented cmMidiPort() based on ALSA. --- linux/cmMidiAlsa.c | 825 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 780 insertions(+), 45 deletions(-) diff --git a/linux/cmMidiAlsa.c b/linux/cmMidiAlsa.c index 6d996b7..0f88b4c 100644 --- a/linux/cmMidiAlsa.c +++ b/linux/cmMidiAlsa.c @@ -3,62 +3,526 @@ #include "cmGlobal.h" #include "cmRpt.h" #include "cmErr.h" +#include "cmCtx.h" #include "cmMem.h" #include "cmMallocDebug.h" +#include "cmLinkedHeap.h" +#include "cmThread.h" #include "cmMidi.h" #include "cmMidiPort.h" -typedef struct -{ - bool inputFl; - char* nameStr; - //SInt32 uid; - //MIDIEndpointRef epr; - cmMpParserH_t parserH; - double prevMicroSecs; -} cmMpPort; +#include + typedef struct { - char* nameStr; + 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 + cmMpParserH_t parserH; // interface to the client callback function for this port +} cmMpPort_t; - unsigned iPortCnt; - cmMpPort* iPortArray; +// MIDI devices +typedef struct +{ + char* nameStr; // string label for this device + unsigned iPortCnt; // input ports on this device + cmMpPort_t* iPortArray; + unsigned oPortCnt; // output ports on this device + cmMpPort_t* oPortArray; + unsigned char clientId; // ALSA client id (all ports on this device use use this client id in their address) - unsigned oPortCnt; - cmMpPort* oPortArray; -} cmMpDev; +} cmMpDev_t; typedef struct { - cmErr_t err; + cmErr_t err; // error object + cmLHeapH_t lH; // linked heap used for all internal memory + + unsigned devCnt; // MIDI devices attached to this computer + cmMpDev_t* devArray; + + cmMpCallback_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 + cmThreadH_t thH; // MIDI input listening thread + + int alsa_fdCnt; // MIDI input driver file descriptor array + struct pollfd* alsa_fd; + + cmMpDev_t* prvRcvDev; // the last device and port to rcv MIDI + cmMpPort_t* prvRcvPort; + + unsigned prvTimeMicroSecs; // time of last recognized event in microseconds + unsigned eventCnt; // count of recognized events - unsigned devCnt; - cmMpDev* devArray; +} cmMpRoot_t; - cmMpCallback_t cbFunc; - void* cbDataPtr; +cmMpRoot_t* _cmMpRoot = NULL; - - -} cmMpRoot; - -cmMpRoot _cmMpRoot = { {NULL,NULL,kOkMpRC}, 0, NULL, NULL, NULL }; - - -cmMpRC_t cmMpInitialize( cmMpCallback_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr, cmRpt_t* rpt ) +cmMpRC_t _cmMpErrMsgV(cmErr_t* err, cmMpRC_t rc, int alsaRc, const cmChar_t* fmt, va_list vl ) { - cmMpRC_t rc = kOkMpRC; + if( alsaRc < 0 ) + cmErrMsg(err,kSysErrMpRC,"ALSA Error:%i %s",alsaRc,snd_strerror(alsaRc)); + + return cmErrVMsg(err,rc,fmt,vl); +} + +cmMpRC_t _cmMpErrMsg(cmErr_t* err, cmMpRC_t rc, int alsaRc, const cmChar_t* fmt, ... ) +{ + va_list vl; + va_start(vl,fmt); + rc = _cmMpErrMsgV(err,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( cmIsFlag(snd_seq_port_info_get_capability(pip),inputFl?SND_SEQ_PORT_CAP_READ:SND_SEQ_PORT_CAP_WRITE) ) + ++i; + + return i; +} + +cmMpDev_t* _cmMpClientIdToDev( int clientId ) +{ + cmMpRoot_t* p = _cmMpRoot; + unsigned i; + for(i=0; idevCnt; ++i) + if( p->devArray[i].clientId == clientId ) + return p->devArray + i; + + return NULL; +} + +cmMpPort_t* _cmMpInPortIdToPort( cmMpDev_t* dev, int portId ) +{ + unsigned i; + + for(i=0; iiPortCnt; ++i) + if( dev->iPortArray[i].alsa_addr.port == portId ) + return dev->iPortArray + i; + + return NULL; +} + + +void _cmMpSplit14Bits( unsigned v, cmMidiByte_t* d0, cmMidiByte_t* d1 ) +{ + *d0 = (v & 0x3f80) >> 7; + *d1 = v & 0x7f; +} + +cmMpRC_t cmMpPoll() +{ + cmMpRC_t rc = kOkMpRC; + 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; + cmMidiByte_t d0 = 0xff; + cmMidiByte_t d1 = 0xff; + cmMidiByte_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 ) + { + cmMidiByte_t ch = ev->data.note.channel; + + cmMpParserMidiTriple(p->prvRcvPort->parserH, deltaMicroSecs, status | ch, d0, d1 ); + + p->prvTimeMicroSecs = microSecs1; + p->eventCnt += 1; + } + + }while( snd_seq_event_input_pending(p->h,0)); + + cmMpParserTransmit(p->prvRcvPort->parserH); + } + + return rc; +} + + +bool _cmMpThreadFunc(void* param) +{ + cmMpPoll(); + return true; +} + +cmMpRC_t _cmMpAllocStruct( cmMpRoot_t* p, const cmChar_t* appNameStr, cmMpCallback_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, cmRpt_t* rpt ) +{ + cmMpRC_t rc = kOkMpRC; + 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(&p->err,kSysErrMpRC,arc,"ALSA seq client info allocation failed."); + goto errLabel; + } + + // alloc the port recd + if((arc = snd_seq_port_info_malloc(&pip)) < 0 ) + { + rc = _cmMpErrMsg(&p->err,kSysErrMpRC,arc,"ALSA seq port info allocation failed."); + goto errLabel; + } + + if((p->alsa_queue = snd_seq_alloc_queue(p->h)) < 0 ) + { + rc = _cmMpErrMsg(&p->err,kSysErrMpRC,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,cmStringNullGuard(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(&p->err,kSysErrMpRC,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 = cmLhAllocZ(p->lH,cmMpDev_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(idevCnt); + + int client = snd_seq_client_info_get_client(cip); + const char* name = snd_seq_client_info_get_name(cip); + + // initalize the device record + p->devArray[i].nameStr = cmLhAllocStr(p->lH,cmStringNullGuard(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( cmIsFlag(caps,SND_SEQ_PORT_CAP_READ) ) + p->devArray[i].iPortCnt += 1; + + if( cmIsFlag(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 = cmLhAllocZ(p->lH,cmMpPort_t,p->devArray[i].iPortCnt); + + if( p->devArray[i].oPortCnt > 0 ) + p->devArray[i].oPortArray = cmLhAllocZ(p->lH,cmMpPort_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( cmIsFlag(caps,SND_SEQ_PORT_CAP_READ) ) + { + assert(jdevArray[i].iPortCnt); + p->devArray[i].iPortArray[j].inputFl = true; + p->devArray[i].iPortArray[j].nameStr = cmLhAllocStr(p->lH,cmStringNullGuard(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; + p->devArray[i].iPortArray[j].parserH = cmMpParserCreate(i, j, cbFunc, cbDataPtr, parserBufByteCnt, rpt ); + + // 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(&p->err,kSysErrMpRC,arc,"Input port to app. subscription failed on port '%s'.",cmStringNullGuard(port)); + + + ++j; + } + + if( cmIsFlag(caps,SND_SEQ_PORT_CAP_WRITE) ) + { + assert(kdevArray[i].oPortCnt); + p->devArray[i].oPortArray[k].inputFl = false; + p->devArray[i].oPortArray[k].nameStr = cmLhAllocStr(p->lH,cmStringNullGuard(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(&p->err,kSysErrMpRC,arc,"App to output port subscription failed on port '%s'.",cmStringNullGuard(port)); + + ++k; + } + } + } + + errLabel: + if( pip != NULL) + snd_seq_port_info_free(pip); + + if( cip != NULL ) + snd_seq_client_info_free(cip); + + return rc; + +} + + +cmMpRC_t cmMpInitialize( cmCtx_t* ctx, cmMpCallback_t cbFunc, void* cbArg, unsigned parserBufByteCnt, const char* appNameStr ) +{ + cmMpRC_t rc = kOkMpRC; + int arc = 0; + cmMpRoot_t* p = NULL; if((rc = cmMpFinalize()) != kOkMpRC ) return rc; - cmErrSetup(&_cmMpRoot.err,rpt,"MIDI Port"); + // allocate the global root object + _cmMpRoot = p = cmMemAllocZ(cmMpRoot_t,1); + p->h = NULL; + p->alsa_queue = -1; - _cmMpRoot.cbFunc = cbFunc; - _cmMpRoot.cbDataPtr = cbDataPtr; + cmErrSetup(&p->err,&ctx->rpt,"MIDI Port"); + // setup the local linked heap manager + if(cmLHeapIsValid(p->lH = cmLHeapCreate(2048,ctx)) == false ) + { + rc = _cmMpErrMsg(&p->err,kLHeapErrMpRC,0,"Linked heap initialization failed."); + goto errLabel; + } + // create the listening thread + if( cmThreadCreate( &p->thH, _cmMpThreadFunc, NULL, &ctx->rpt) != kOkThRC ) + { + rc = _cmMpErrMsg(&p->err,kThreadErrMpRC,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(&p->err,kSysErrMpRC,arc,"ALSA Sequencer open failed."); + goto errLabel; + } + + // setup the device and port structures + if((rc = _cmMpAllocStruct(p,appNameStr,cbFunc,cbArg,parserBufByteCnt,&ctx->rpt)) != kOkMpRC ) + goto errLabel; + + // allocate the file descriptors used for polling + p->alsa_fdCnt = snd_seq_poll_descriptors_count(p->h, POLLIN); + p->alsa_fd = cmMemAllocZ(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(&p->err,kSysErrMpRC,arc,"ALSA queue start failed."); + goto errLabel; + } + + // send any pending commands to the driver + snd_seq_drain_output(p->h); + + if( cmThreadPause(p->thH,0) != kOkThRC ) + rc = _cmMpErrMsg(&p->err,kThreadErrMpRC,0,"Thread start failed."); + + errLabel: + + if( rc != kOkMpRC ) + cmMpFinalize(); return rc; @@ -66,66 +530,337 @@ cmMpRC_t cmMpInitialize( cmMpCallback_t cbFunc, void* cbDataPtr, unsigned parser cmMpRC_t cmMpFinalize() { - cmMpRC_t rc = kOkMpRC; + cmMpRC_t rc = kOkMpRC; + cmMpRoot_t* p = _cmMpRoot; + if( _cmMpRoot != NULL ) + { + int arc; + // stop the thread first + if( cmThreadDestroy(&p->thH) != kOkThRC ) + { + rc = _cmMpErrMsg(&p->err,kThreadErrMpRC,0,"Thread destroy failed."); + goto errLabel; + } + // stop the queue + if((arc = snd_seq_stop_queue(p->h,p->alsa_queue, NULL)) < 0 ) + { + rc = _cmMpErrMsg(&p->err,kSysErrMpRC,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(&p->err,kSysErrMpRC,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(&p->err,kSysErrMpRC,arc,"ALSA sequencer close failed."); + else + p->h = NULL; + } + + // release each parser + unsigned i,j; + for(i=0; idevCnt; ++i) + for(j=0; jdevArray[i].iPortCnt; ++j) + cmMpParserDestroy(&p->devArray[i].iPortArray[j].parserH); + + cmLHeapDestroy(&p->lH); + + cmMemFree(p->alsa_fd); + + cmMemPtrFree(&_cmMpRoot); + + } + + errLabel: return rc; } bool cmMpIsInitialized() -{ return false; } +{ return _cmMpRoot!=NULL; } unsigned cmMpDeviceCount() -{ - return 0; -} +{ return _cmMpRoot==NULL ? 0 : _cmMpRoot->devCnt; } -const char* cmMpDeviceName( unsigned devIdx ) -{ return NULL; +const char* cmMpDeviceName( unsigned devIdx ) +{ + if( _cmMpRoot==NULL || devIdx>=_cmMpRoot->devCnt) + return NULL; + + return _cmMpRoot->devArray[devIdx].nameStr; } unsigned cmMpDevicePortCount( unsigned devIdx, unsigned flags ) { - return 0; + if( _cmMpRoot==NULL || devIdx>=_cmMpRoot->devCnt) + return 0; + + if( cmIsFlag(flags,kInMpFl) ) + return _cmMpRoot->devArray[devIdx].iPortCnt; + + return _cmMpRoot->devArray[devIdx].oPortCnt; } const char* cmMpDevicePortName( unsigned devIdx, unsigned flags, unsigned portIdx ) { - return NULL; + if( _cmMpRoot==NULL || devIdx>=_cmMpRoot->devCnt) + return 0; + + if( cmIsFlag(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; } cmMpRC_t cmMpDeviceSend( unsigned devIdx, unsigned portIdx, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 ) { - return kOkMpRC; + cmMpRC_t rc = kOkMpRC; + snd_seq_event_t ev; + int arc; + cmMpRoot_t* p = _cmMpRoot; + + assert( p!=NULL && devIdx < p->devCnt && portIdx < p->devArray[devIdx].oPortCnt ); + + cmMpPort_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(&p->err,kInvalidArgMpRC,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(&p->err,kSysErrMpRC,arc,"MIDI event output failed."); + + if((arc = snd_seq_drain_output(p->h)) < 0 ) + rc = _cmMpErrMsg(&p->err,kSysErrMpRC,arc,"MIDI event output drain failed."); + + errLabel: + return rc; } cmMpRC_t cmMpDeviceSendData( unsigned devIdx, unsigned portIdx, const cmMidiByte_t* dataPtr, unsigned byteCnt ) { - return kOkMpRC; - + cmMpRoot_t* p = _cmMpRoot; + return cmErrMsg(&p->err,kNotImplMpRC,"cmMpDeviceSendData() has not yet been implemented for ALSA."); } cmMpRC_t cmMpInstallCallback( unsigned devIdx, unsigned portIdx, cmMpCallback_t cbFunc, void* cbDataPtr ) { - cmMpRC_t rc = kOkMpRC; + cmMpRC_t rc = kOkMpRC; + unsigned di; + unsigned dn = cmMpDeviceCount(); + cmMpRoot_t* p = _cmMpRoot; + + for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkMpRC ) + goto errLabel; + } + + errLabel: return rc; } cmMpRC_t cmMpRemoveCallback( unsigned devIdx, unsigned portIdx, cmMpCallback_t cbFunc, void* cbDataPtr ) { cmMpRC_t rc = kOkMpRC; + unsigned di; + unsigned dn = cmMpDeviceCount(); + unsigned remCnt = 0; + cmMpRoot_t* p = _cmMpRoot; + + for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) ) + { + if( cmMpParserRemoveCallback( p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkMpRC ) + goto errLabel; + else + ++remCnt; + } + } + + if( remCnt == 0 && dn > 0 ) + rc = _cmMpErrMsg(&p->err,kCbNotFoundMpRC,0,"The callback was not found on any of the specified devices or ports."); + + errLabel: return rc; } bool cmMpUsesCallback( unsigned devIdx, unsigned portIdx, cmMpCallback_t cbFunc, void* cbDataPtr ) { + unsigned di; + unsigned dn = cmMpDeviceCount(); + cmMpRoot_t* p = _cmMpRoot; + + for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) ) + return true; + } + return false; } + +void _cmMpReportPort( cmRpt_t* rpt, const cmMpPort_t* port ) +{ + cmRptPrintf(rpt," 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 ) cmRptPrintf(rpt,"Read " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_WRITE ) cmRptPrintf(rpt,"Writ " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SYNC_READ ) cmRptPrintf(rpt,"Syrd " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SYNC_WRITE ) cmRptPrintf(rpt,"Sywr " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_DUPLEX ) cmRptPrintf(rpt,"Dupl " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SUBS_READ ) cmRptPrintf(rpt,"Subr " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_SUBS_WRITE ) cmRptPrintf(rpt,"Subw " ); + if( port->alsa_cap & SND_SEQ_PORT_CAP_NO_EXPORT ) cmRptPrintf(rpt,"Nexp " ); + + cmRptPrintf(rpt,") type:("); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SPECIFIC ) cmRptPrintf(rpt,"Spec "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) cmRptPrintf(rpt,"Gnrc "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GM ) cmRptPrintf(rpt,"GM "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GS ) cmRptPrintf(rpt,"GS "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_XG ) cmRptPrintf(rpt,"XG "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_MT32 ) cmRptPrintf(rpt,"MT32 "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_MIDI_GM2 ) cmRptPrintf(rpt,"GM2 "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SYNTH ) cmRptPrintf(rpt,"Syn "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_DIRECT_SAMPLE) cmRptPrintf(rpt,"Dsmp "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SAMPLE ) cmRptPrintf(rpt,"Samp "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_HARDWARE ) cmRptPrintf(rpt,"Hwar "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SOFTWARE ) cmRptPrintf(rpt,"Soft "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_SYNTHESIZER ) cmRptPrintf(rpt,"Sizr "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_PORT ) cmRptPrintf(rpt,"Port "); + if( port->alsa_type & SND_SEQ_PORT_TYPE_APPLICATION ) cmRptPrintf(rpt,"Appl "); + + cmRptPrintf(rpt,")\n"); + +} + void cmMpReport( cmRpt_t* rpt ) { + cmMpRoot_t* p = _cmMpRoot; + unsigned i,j; + + cmRptPrintf(rpt,"Buffer size bytes in:%i out:%i\n",snd_seq_get_input_buffer_size(p->h),snd_seq_get_output_buffer_size(p->h)); + + for(i=0; idevCnt; ++i) + { + const cmMpDev_t* d = p->devArray + i; + + cmRptPrintf(rpt,"%i : Device: %s \n",i,cmStringNullGuard(d->nameStr)); + + if(d->iPortCnt > 0 ) + cmRptPrintf(rpt," Input:\n"); + + for(j=0; jiPortCnt; ++j) + _cmMpReportPort(rpt,d->iPortArray+j); + + if(d->oPortCnt > 0 ) + cmRptPrintf(rpt," Output:\n"); + + for(j=0; joPortCnt; ++j) + _cmMpReportPort(rpt,d->oPortArray+j); + } } From c07243c4fd213c8e1af17cd88a6994c6f15a74e6 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 24 Mar 2013 12:54:11 -0700 Subject: [PATCH 2/3] cmAudDsp.c,cmMidiFilePlay.c: Changed cmMpInitialize() signature to use cmCtx_t according to convention. --- cmAudDsp.c | 2 +- cmMidiFilePlay.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmAudDsp.c b/cmAudDsp.c index ddab770..0190307 100644 --- a/cmAudDsp.c +++ b/cmAudDsp.c @@ -650,7 +650,7 @@ cmAdRC_t cmAudDspAlloc( cmCtx_t* ctx, cmAdH_t* hp, cmMsgSendFuncPtr_t cbFunc, vo } // initialize the MIDI system - if( cmMpInitialize(NULL,NULL,p->midiPortBufByteCnt,"app",&ctx->rpt) != kOkMpRC ) + if( cmMpInitialize(ctx,NULL,NULL,p->midiPortBufByteCnt,"app") != kOkMpRC ) { rc = cmErrMsg(&p->err,kMidiSysFailAdRC,"The MIDI system initialization failed."); goto errLabel; diff --git a/cmMidiFilePlay.c b/cmMidiFilePlay.c index 449b1a7..6b04ab2 100644 --- a/cmMidiFilePlay.c +++ b/cmMidiFilePlay.c @@ -336,7 +336,7 @@ cmMfpRC_t cmMfpTest( const char* fn, cmCtx_t* ctx ) unsigned mdParserBufByteCnt = 1024; printf("Initializing MIDI Devices...\n"); - cmMpInitialize( _cmMpCallbackTest, NULL, mdParserBufByteCnt,"app", &ctx->rpt ); + cmMpInitialize( ctx, _cmMpCallbackTest, NULL, mdParserBufByteCnt,"app" ); //mdReport(); From 2171cf09ad7d6c3b2a8466b7d6de8deda1ef8859 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 24 Mar 2013 12:57:18 -0700 Subject: [PATCH 3/3] cmMidiPort.h/c: Changed cmMidiPort to support cmMidiAlsa.c. Updated cmMpInitialize() and cmMpTest() to use cmCtx_t. Added following public functions: cmMpDeviceNameToIndex() cmMpDevicePortNameToIndex() cmMpParserMidiTriple() and cmMpParserTransmit() to allow the MIDI parser to transmit pre-parsed MIDI messages. --- cmMidiPort.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++---- cmMidiPort.h | 15 +++++++-- 2 files changed, 101 insertions(+), 9 deletions(-) diff --git a/cmMidiPort.c b/cmMidiPort.c index 751dc41..55634fd 100644 --- a/cmMidiPort.c +++ b/cmMidiPort.c @@ -2,6 +2,7 @@ #include "cmGlobal.h" #include "cmRpt.h" #include "cmErr.h" +#include "cmCtx.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmMidi.h" @@ -180,7 +181,8 @@ void _cmMpTransmitSysEx( cmMpParser_t* p ) void _cmMpParserStoreChMsg( cmMpParser_t* p, unsigned deltaMicroSecs, cmMidiByte_t d ) { - // if there is not enough room left in the buffer then transmit the current messages + // if there is not enough room left in the buffer then transmit + // the current messages if( p->bufByteCnt - p->bufIdx < sizeof(cmMidiMsg) ) _cmMpTransmitChMsgs(p); @@ -346,6 +348,58 @@ void cmMpParseMidiData( cmMpParserH_t h, unsigned deltaMicroSecs, const cmMidiBy } +cmMpRC_t cmMpParserMidiTriple( cmMpParserH_t h, unsigned deltaMicroSecs, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 ) +{ + cmMpRC_t rc = kOkMpRC; + cmMpParser_t* p = _cmMpParserFromHandle(h); + cmMidiByte_t mb = -1; + + 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 = cmErrMsg(&p->err,kInvalidArgMpRC,"An invalid MIDI status byte (0x%x) was encountered by the MIDI data parser."); + goto errLabel; + break; + } + + if( mb != -1 ) + _cmMpParserStoreChMsg(p,deltaMicroSecs,mb); + + p->dataCnt = cmInvalidCnt; + + errLabel: + return rc; +} + +cmMpRC_t cmMpParserTransmit( cmMpParserH_t h ) +{ + cmMpParser_t* p = _cmMpParserFromHandle(h); + _cmMpTransmitChMsgs(p); + return kOkMpRC; +} + cmMpRC_t cmMpParserInstallCallback( cmMpParserH_t h, cmMpCallback_t cbFunc, void* cbDataPtr ) { cmMpParser_t* p = _cmMpParserFromHandle(h); @@ -418,6 +472,32 @@ bool cmMpParserHasCallback( cmMpParserH_t h, cmMpCallback_t cbFunc, void* cbData // // +unsigned cmMpDeviceNameToIndex(const cmChar_t* name) +{ + assert(name!=NULL); + unsigned i; + unsigned n = cmMpDeviceCount(); + for(i=0; irpt); - - cmRptPrintf(rpt," to continue\n"); + + cmRptPrintf(&ctx->rpt," to continue\n"); while((ch = getchar()) != 'q') { - cmMpDeviceSend(0,0,0x90,60,60); + cmMpDeviceSend(2,0,0x90,60,60); } + cmMpFinalize(); } diff --git a/cmMidiPort.h b/cmMidiPort.h index 57ef0ea..6243ee2 100644 --- a/cmMidiPort.h +++ b/cmMidiPort.h @@ -19,6 +19,8 @@ extern "C" { { kOkMpRC = cmOkRC, kCfStringErrMpRC, + kLHeapErrMpRC, + kThreadErrMpRC, kSysErrMpRC, kInvalidArgMpRC, kMemAllocFailMpRC, @@ -46,6 +48,13 @@ extern "C" { unsigned cmMpParserErrorCount( cmMpParserH_t h ); void cmMpParseMidiData( cmMpParserH_t h, unsigned deltaMicroSecs, const cmMidiByte_t* buf, unsigned bufByteCnt ); + // The following two functions are intended to be used togetther. + // Use cmMpParserMidiTriple() to insert pre-parsed msg's to the output buffer, + // and then use cmMpParserTransmit() to send the buffer via the parsers callback function. + // Set the data bytes to 0xff if they are not used by the message. + cmMpRC_t cmMpParserMidiTriple( cmMpParserH_t h, unsigned deltaMicroSecs, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 ); + cmMpRC_t cmMpParserTransmit( cmMpParserH_t h ); + // Install/Remove additional callbacks. cmMpRC_t cmMpParserInstallCallback( cmMpParserH_t h, cmMpCallback_t cbFunc, void* cbDataPtr ); cmMpRC_t cmMpParserRemoveCallback( cmMpParserH_t h, cmMpCallback_t cbFunc, void* cbDataPtr ); @@ -60,14 +69,16 @@ extern "C" { // '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(). - cmMpRC_t cmMpInitialize( cmMpCallback_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr, cmRpt_t* rpt ); + cmMpRC_t cmMpInitialize( cmCtx_t* ctx, cmMpCallback_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr ); cmMpRC_t cmMpFinalize(); bool cmMpIsInitialized(); unsigned cmMpDeviceCount(); const char* cmMpDeviceName( unsigned devIdx ); + unsigned cmMpDeviceNameToIndex(const cmChar_t* name); unsigned cmMpDevicePortCount( unsigned devIdx, unsigned flags ); const char* cmMpDevicePortName( unsigned devIdx, unsigned flags, unsigned portIdx ); + unsigned cmMpDevicePortNameToIndex( unsigned devIdx, unsigned flags, const cmChar_t* name ); cmMpRC_t cmMpDeviceSend( unsigned devIdx, unsigned portIdx, cmMidiByte_t st, cmMidiByte_t d0, cmMidiByte_t d1 ); cmMpRC_t cmMpDeviceSendData( unsigned devIdx, unsigned portIdx, const cmMidiByte_t* dataPtr, unsigned byteCnt ); @@ -80,7 +91,7 @@ extern "C" { void cmMpReport( cmRpt_t* rpt ); - void cmMpTest( cmRpt_t* rpt ); + void cmMpTest( cmCtx_t* ctx ); #ifdef __cplusplus }