diff --git a/cwMidiAlsa.cpp b/cwMidiAlsa.cpp index ef6d860..b59bc59 100644 --- a/cwMidiAlsa.cpp +++ b/cwMidiAlsa.cpp @@ -3,10 +3,17 @@ #include "cwCommonImpl.h" #include "cwMem.h" #include "cwTime.h" -#include "cwMidi.h" +#include "cwText.h" +#include "cwObject.h" #include "cwTextBuf.h" -#include "cwMidiPort.h" -#include "cwThread.h" +#include "cwMidi.h" +#include "cwMidiDecls.h" +#include "cwMidiDevice.h" +#include "cwMidiParser.h" + +#include + +#include "cwMidiAlsa.h" #include @@ -16,108 +23,112 @@ namespace cw { namespace device { - typedef struct + namespace alsa { - 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 device_str - { - 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; - - bool latency_meas_enable_in_fl; - bool latency_meas_enable_out_fl; - latency_meas_result_t latency_meas_result; + const unsigned kCtoD_MapN = 256; - } device_t; + 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 alsa_device_str + { + 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 + 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; + + unsigned clientIdToDevIdxMap[ kCtoD_MapN ]; + + bool latency_meas_enable_in_fl; + bool latency_meas_enable_out_fl; + latency_meas_result_t latency_meas_result; + + } alsa_device_t; #define _cmMpErrMsg( rc, alsaRc, str ) cwLogError(kOpFailRC,"%s : ALSA Error:%i %s",(str),(alsaRc),snd_strerror(alsaRc)) #define _cmMpErrMsg1( rc, alsaRc, fmt, arg ) cwLogError(kOpFailRC, fmt"%s : ALSA Error:%i %s",(arg),(alsaRc),snd_strerror(alsaRc)) - device_t* _handleToPtr( handle_t h ){ return handleToPtr(h); } + alsa_device_t* _handleToPtr( handle_t h ){ return handleToPtr(h); } - 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( device_t* p, int clientId ) - { - unsigned i; - for(i=0; idevCnt; ++i) - if( p->devArray[i].clientId == clientId ) - return p->devArray + i; - - return NULL; - } - - port_t* _cmMpInPortIdToPort( dev_t* dev, int portId ) - { - unsigned i; - - for(i=0; iiPortCnt; ++i) - if( dev->iPortArray[i].alsa_addr.port == portId ) - return dev->iPortArray + i; - - return NULL; - } - - - void _cmMpSplit14Bits( unsigned v, uint8_t* d0, uint8_t* d1 ) - { - *d0 = (v & 0x3f80) >> 7; - *d1 = v & 0x7f; - } - - rc_t _handle_alsa_device( device_t* p ) - { - rc_t rc = kOkRC; - - snd_seq_event_t *ev; - - - do + 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( alsa_device_t* p, unsigned char clientId ) + { + unsigned devIdx = p->clientIdToDevIdxMap[ clientId ]; + + if( devIdx != kInvalidIdx ) + return p->devArray + devIdx; + + return NULL; + } + + port_t* _cmMpInPortIdToPort( dev_t* dev, int portId ) + { + unsigned i; + + for(i=0; iiPortCnt; ++i) + if( dev->iPortArray[i].alsa_addr.port == portId ) + return dev->iPortArray + i; + + return NULL; + } + + + void _cmMpSplit14Bits( unsigned v, uint8_t* d0, uint8_t* d1 ) + { + *d0 = (v & 0x3f80) >> 7; + *d1 = v & 0x7f; + } + + rc_t _handle_input_msg( alsa_device_t* p ) + { + rc_t rc = kOkRC; + + snd_seq_event_t *ev; + + do + { int alsa_rc = snd_seq_event_input(p->h,&ev); // if no input @@ -134,8 +145,6 @@ namespace cw break; } - // TODO: Improve performance of _cmMpClientIdToDev() and _cmMpInPortIdToPort(). - // They currently use linear searchs! // get the device this event arrived from if( p->prvRcvDev==NULL || p->prvRcvDev->clientId != ev->source.client ) @@ -256,6 +265,7 @@ namespace cw if( status != 0 ) { uint8_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; @@ -267,6 +277,11 @@ namespace cw //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 ); + */ + time::spec_t ts; + ts.tv_sec = ev->time.time.tv_sec; + ts.tv_nsec = ev->time.time.tv_nsec; parser::midiTriple(p->prvRcvPort->parserH, &ts, status | ch, d0, d1 ); p->prvTimeMicroSecs = microSecs1; @@ -278,320 +293,297 @@ namespace cw parser::transmit(p->prvRcvPort->parserH); return rc; - } - - rc_t _cmMpPoll(device_t* p) - { - rc_t rc = kOkRC; - int timeOutMs = 50; - - if (poll(p->alsa_fd, p->alsa_fdCnt, timeOutMs) > 0) - { - rc = _handle_alsa_device(p); - } - - return rc; - } - - - bool _threadCbFunc(void* arg) - { - device_t* p = static_cast(arg); - _cmMpPoll(p); - return true; - } - - rc_t _cmMpAllocStruct( device_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_t _cmMpAllocStruct( alsa_device_t* p, const char* appNameStr, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt ) { - rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA seq port info allocation failed."); - goto errLabel; - } + 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; - if((p->alsa_queue = snd_seq_alloc_queue(p->h)) < 0 ) - { - rc = _cmMpErrMsg(kOpFailRC,p->alsa_queue,"ALSA queue allocation failed."); - goto errLabel; - } + for(i=0; iclientIdToDevIdxMap[i] = kInvalidIdx; - // 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); - */ + // alloc the subscription recd on the stack + snd_seq_port_subscribe_alloca(&subs); - // 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 = mem::allocZ(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 = mem::duplStr(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 ) + // alloc the client recd + if((arc = snd_seq_client_info_malloc(&cip)) < 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 = mem::allocZ(p->devArray[i].iPortCnt); - - if( p->devArray[i].oPortCnt > 0 ) - p->devArray[i].oPortArray = mem::allocZ(p->devArray[i].oPortCnt); - - - snd_seq_port_info_set_client(pip,client); // set the ports client id - snd_seq_port_info_set_port(pip,-1); - - // fill in the port information - for(j=0,k=0; snd_seq_query_next_port(p->h,pip) == 0; ) - { - const char* port = snd_seq_port_info_get_name(pip); - unsigned type = snd_seq_port_info_get_type(pip); - unsigned caps = snd_seq_port_info_get_capability(pip); - snd_seq_addr_t addr = *snd_seq_port_info_get_addr(pip); - - if( cwIsFlag(caps,SND_SEQ_PORT_CAP_READ) ) - { - assert(jdevArray[i].iPortCnt); - p->devArray[i].iPortArray[j].inputFl = true; - p->devArray[i].iPortArray[j].nameStr = mem::duplStr(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 = _cmMpErrMsg1(kOpFailRC,arc,"Input port to app. subscription failed on port '%s'.",cwStringNullGuard(port)); - - ++j; - } - - if( cwIsFlag(caps,SND_SEQ_PORT_CAP_WRITE) ) - { - assert(kdevArray[i].oPortCnt); - p->devArray[i].oPortArray[k].inputFl = false; - p->devArray[i].oPortArray[k].nameStr = mem::duplStr(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 = _cmMpErrMsg1(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"); - - } - - rc_t _destroy( device_t* p ) - { - rc_t rc = kOkRC; - - if( p != NULL ) - { - int arc; - - // stop the thread first - if((rc = thread::destroy(p->thH)) != kOkRC ) - { - rc = _cmMpErrMsg(rc,0,"Thread destroy failed."); + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA seq client info allocation 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 ) + // alloc the port recd + if((arc = snd_seq_port_info_malloc(&pip)) < 0 ) { - 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; + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA seq port info allocation failed."); + goto errLabel; } - // release the alsa system handle - if( p->h != NULL ) + if((p->alsa_queue = snd_seq_alloc_queue(p->h)) < 0 ) { - if( (arc = snd_seq_close(p->h)) < 0 ) - rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA sequencer close failed."); - else - p->h = NULL; + rc = _cmMpErrMsg(kOpFailRC,p->alsa_queue,"ALSA queue allocation failed."); + goto errLabel; } - unsigned i,j; - for(i=0; idevCnt; ++i) - { - for(j=0; jdevArray[i].iPortCnt; ++j) - { - parser::destroy(p->devArray[i].iPortArray[j].parserH); - mem::release( p->devArray[i].iPortArray[j].nameStr ); - } + // 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); + */ - for(j=0; jdevArray[i].oPortCnt; ++j) + // 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 = mem::allocZ(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 = mem::duplStr(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; + + assert( (unsigned)client < kCtoD_MapN ); + p->clientIdToDevIdxMap[ client ] = i; + + 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 ) { - mem::release( p->devArray[i].oPortArray[j].nameStr ); - } + 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; - mem::release(p->devArray[i].iPortArray); - mem::release(p->devArray[i].oPortArray); - mem::release(p->devArray[i].nameStr); - + } + + // allocate the device port arrays + if( p->devArray[i].iPortCnt > 0 ) + p->devArray[i].iPortArray = mem::allocZ(p->devArray[i].iPortCnt); + + if( p->devArray[i].oPortCnt > 0 ) + p->devArray[i].oPortArray = mem::allocZ(p->devArray[i].oPortCnt); + + + snd_seq_port_info_set_client(pip,client); // set the ports client id + snd_seq_port_info_set_port(pip,-1); + + // fill in the port information + for(j=0,k=0; snd_seq_query_next_port(p->h,pip) == 0; ) + { + const char* port = snd_seq_port_info_get_name(pip); + unsigned type = snd_seq_port_info_get_type(pip); + unsigned caps = snd_seq_port_info_get_capability(pip); + snd_seq_addr_t addr = *snd_seq_port_info_get_addr(pip); + + if( cwIsFlag(caps,SND_SEQ_PORT_CAP_READ) ) + { + assert(jdevArray[i].iPortCnt); + p->devArray[i].iPortArray[j].inputFl = true; + p->devArray[i].iPortArray[j].nameStr = mem::duplStr(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 = _cmMpErrMsg1(kOpFailRC,arc,"Input port to app. subscription failed on port '%s'.",cwStringNullGuard(port)); + + ++j; + } + + if( cwIsFlag(caps,SND_SEQ_PORT_CAP_WRITE) ) + { + assert(kdevArray[i].oPortCnt); + p->devArray[i].oPortArray[k].inputFl = false; + p->devArray[i].oPortArray[k].nameStr = mem::duplStr(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 = _cmMpErrMsg1(kOpFailRC,arc,"App to output port subscription failed on port '%s'.",cwStringNullGuard(port)); + + ++k; + } + } } - mem::release(p->devArray); - - mem::free(p->alsa_fd); + errLabel: + if( pip != NULL) + snd_seq_port_info_free(pip); - mem::release(p); - + if( cip != NULL ) + snd_seq_client_info_free(cip); + + return rc; + } - errLabel: - 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"); + + } + + rc_t _destroy( alsa_device_t* p ) + { + rc_t rc = kOkRC; + + if( p != NULL ) + { + int arc; + + + // stop the queue + if( p->h != NULL ) + if((arc = snd_seq_stop_queue(p->h,p->alsa_queue, NULL)) < 0 ) + { + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA queue stop failed."); + goto errLabel; + } + + // release the alsa queue + if( p->alsa_queue != -1 ) + { + if((arc = snd_seq_free_queue(p->h,p->alsa_queue)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA queue release failed."); + else + p->alsa_queue = -1; + } + + // release the alsa system handle + if( p->h != NULL ) + { + if( (arc = snd_seq_close(p->h)) < 0 ) + rc = _cmMpErrMsg(kOpFailRC,arc,"ALSA sequencer close failed."); + else + p->h = NULL; + } + + unsigned i,j; + for(i=0; idevCnt; ++i) + { + for(j=0; jdevArray[i].iPortCnt; ++j) + { + parser::destroy(p->devArray[i].iPortArray[j].parserH); + mem::release( p->devArray[i].iPortArray[j].nameStr ); + } + + for(j=0; jdevArray[i].oPortCnt; ++j) + { + mem::release( p->devArray[i].oPortArray[j].nameStr ); + } + mem::release(p->devArray[i].iPortArray); + mem::release(p->devArray[i].oPortArray); + mem::release(p->devArray[i].nameStr); + + } + + mem::release(p->devArray); + + mem::free(p->alsa_fd); + + mem::release(p); + + } + errLabel: + return rc; + } + } // alsa } // device } // midi } // cw -cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, unsigned parserBufByteCnt, const char* appNameStr ) +cw::rc_t cw::midi::device::alsa::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, unsigned parserBufByteCnt, const char* appNameStr ) { rc_t rc = kOkRC; int arc = 0; @@ -599,18 +591,11 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u if((rc = destroy(h)) != kOkRC ) return rc; - device_t* p = mem::allocZ(1); + alsa_device_t* p = mem::allocZ(1); p->h = NULL; p->alsa_queue = -1; - // create the listening thread - if((rc = thread::create( p->thH, _threadCbFunc, p)) != 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 ) { @@ -627,7 +612,7 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u // allocate the file descriptors used for polling p->alsa_fdCnt = snd_seq_poll_descriptors_count(p->h, POLLIN); - p->alsa_fd = mem::allocZ(p->alsa_fdCnt); + p->alsa_fd = mem::allocZ(p->alsa_fdCnt); snd_seq_poll_descriptors(p->h, p->alsa_fd, p->alsa_fdCnt, POLLIN); p->cbFunc = cbFunc; @@ -646,9 +631,6 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u // 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."); - h.set(p); errLabel: @@ -661,14 +643,14 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u } -cw::rc_t cw::midi::device::destroy( handle_t& h ) +cw::rc_t cw::midi::device::alsa::destroy( handle_t& h ) { rc_t rc = kOkRC; if( !h.isValid() ) return rc; - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); if((rc = _destroy(p)) != kOkRC ) return rc; @@ -678,18 +660,32 @@ cw::rc_t cw::midi::device::destroy( handle_t& h ) return rc; } -bool cw::midi::device::isInitialized(handle_t h) +bool cw::midi::device::alsa::isInitialized(handle_t h) { return h.isValid(); } -unsigned cw::midi::device::count(handle_t h) +unsigned cw::midi::device::alsa::count(handle_t h) { - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); return p->devCnt; } -const char* cw::midi::device::name( handle_t h, unsigned devIdx ) +struct pollfd* cw::midi::device::alsa::pollFdArray( handle_t h, unsigned& arrayCntRef ) +{ + alsa_device_t* p = _handleToPtr(h); + arrayCntRef = p->alsa_fdCnt; + return p->alsa_fd; +} + +cw::rc_t cw::midi::device::alsa::handleInputMsg( handle_t h ) +{ + alsa_device_t* p = _handleToPtr(h); + return _handle_input_msg(p); +} + + +const char* cw::midi::device::alsa::name( handle_t h, unsigned devIdx ) { - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); if( p==NULL || devIdx>=p->devCnt) return NULL; @@ -697,9 +693,20 @@ const char* cw::midi::device::name( handle_t h, unsigned devIdx ) return p->devArray[devIdx].nameStr; } -unsigned cw::midi::device::portCount( handle_t h, unsigned devIdx, unsigned flags ) +unsigned cw::midi::device::alsa::nameToIndex(handle_t h, const char* deviceName) { - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); + for(unsigned i=0; idevCnt; ++i) + if( textIsEqual(p->devArray[i].nameStr,deviceName) ) + return i; + + return kInvalidIdx; +} + + +unsigned cw::midi::device::alsa::portCount( handle_t h, unsigned devIdx, unsigned flags ) +{ + alsa_device_t* p = _handleToPtr(h); if( p==NULL || devIdx>=p->devCnt) return 0; @@ -710,9 +717,9 @@ unsigned cw::midi::device::portCount( handle_t h, unsigned devIdx, unsigned return p->devArray[devIdx].oPortCnt; } -const char* cw::midi::device::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ) +const char* cw::midi::device::alsa::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ) { - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); if( p==NULL || devIdx>=p->devCnt) return 0; @@ -731,13 +738,42 @@ const char* cw::midi::device::portName( handle_t h, unsigned devIdx, unsign return p->devArray[devIdx].oPortArray[portIdx].nameStr; } +unsigned cw::midi::device::alsa::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName ) +{ + alsa_device_t* p = _handleToPtr(h); -cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 ) + if( p==nullptr || devIdx>=p->devCnt ) + return kInvalidIdx; + + if( cwIsFlag(flags,kInMpFl) ) + { + for(unsigned i=0; idevArray[devIdx].iPortCnt; ++i) + if( textIsEqual(p->devArray[devIdx].iPortArray[i].nameStr,portName) ) + return i; + + } + else + { + for(unsigned i=0; idevArray[devIdx].oPortCnt; ++i) + if( textIsEqual(p->devArray[devIdx].oPortArray[i].nameStr,portName) ) + return i; + } + + return kInvalidIdx; +} + +cw::rc_t cw::midi::device::alsa::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl ) +{ + return cwLogError(kNotImplementedRC,"Port enable/disable has not been implemneted on ALSA MIDI ports."); +} + + +cw::rc_t cw::midi::device::alsa::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 ) { rc_t rc = kOkRC; snd_seq_event_t ev; int arc; - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); assert( p!=NULL && devIdx < p->devCnt && portIdx < p->devArray[devIdx].oPortCnt ); @@ -826,110 +862,34 @@ cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx, return rc; } -cw::rc_t cw::midi::device::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ) +cw::rc_t cw::midi::device::alsa::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ) { return cwLogError(kInvalidOpRC,"cmMpDeviceSendData() has not yet been implemented for ALSA."); } -cw::rc_t cw::midi::device::installCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) +void cw::midi::device::alsa::latency_measure_reset(handle_t h) { - rc_t rc = kOkRC; - unsigned di; - unsigned dn = count(h); - device_t* p = _handleToPtr(h); - - for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkRC ) - goto errLabel; - } - - errLabel: - return rc; -} - -cw::rc_t cw::midi::device::removeCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) -{ - rc_t rc = kOkRC; - unsigned di; - unsigned dn = count(h); - unsigned remCnt = 0; - device_t* p = _handleToPtr(h); - - for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) ) - { - if( parser::removeCallback( p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkRC ) - goto errLabel; - else - ++remCnt; - } - } - - if( remCnt == 0 && dn > 0 ) - rc = _cmMpErrMsg(kInvalidArgRC,0,"The callback was not found on any of the specified devices or ports."); - - errLabel: - return rc; -} - -bool cw::midi::device::usesCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) -{ - unsigned di; - unsigned dn = count(h); - device_t* p = _handleToPtr(h); - - for(di=0; didevArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) ) - return true; - } - - return false; -} - -void cw::midi::device::latency_measure_setup(handle_t h) -{ - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); - p->latency_meas_result.note_on_input_ts = {0}; - p->latency_meas_result.note_on_output_ts = {0}; + p->latency_meas_result.note_on_input_ts = {}; + p->latency_meas_result.note_on_output_ts = {}; p->latency_meas_enable_in_fl = true; p->latency_meas_enable_out_fl = true; } -cw::midi::device::latency_meas_result_t cw::midi::device::latency_measure_result(handle_t h) +cw::midi::device::latency_meas_result_t cw::midi::device::alsa::latency_measure_result(handle_t h) { - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); return p->latency_meas_result; } -void cw::midi::device::report( handle_t h, textBuf::handle_t tbH ) +void cw::midi::device::alsa::report( handle_t h, textBuf::handle_t tbH ) { - device_t* p = _handleToPtr(h); + alsa_device_t* p = _handleToPtr(h); 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)); + textBuf::print( tbH,"ALSA 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) { diff --git a/cwMidiAlsa.h b/cwMidiAlsa.h new file mode 100644 index 0000000..4c74913 --- /dev/null +++ b/cwMidiAlsa.h @@ -0,0 +1,44 @@ +namespace cw +{ + namespace midi + { + namespace device + { + namespace alsa + { + + typedef handle< struct alsa_device_str> handle_t; + + rc_t create( handle_t& h, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr ); + rc_t destroy( handle_t& h); + bool isInitialized( handle_t h ); + + struct pollfd* pollFdArray( handle_t h, unsigned& arrayCntRef ); + rc_t handleInputMsg( handle_t h ); + + unsigned count( handle_t h ); + const char* name( handle_t h, unsigned devIdx ); + unsigned nameToIndex(handle_t h, const char* deviceName); + unsigned portCount( handle_t h, unsigned devIdx, unsigned flags ); + const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ); + unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName ); + rc_t portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl ); + + rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 ); + rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ); + + + // Latency measurment: + // Record the time of the next incoming note-on msg + // and the time of the next outgoing note-on msg + + // Reset the latency measurement process. + void latency_measure_reset(handle_t h); + latency_meas_result_t latency_measure_result(handle_t h); + + void report( handle_t h, textBuf::handle_t tbH); + + } + } + } +} diff --git a/cwMidiDevice.cpp b/cwMidiDevice.cpp index 69b8cb3..9935610 100644 --- a/cwMidiDevice.cpp +++ b/cwMidiDevice.cpp @@ -3,506 +3,20 @@ #include "cwCommonImpl.h" #include "cwMem.h" #include "cwTime.h" -#include "cwMidi.h" +#include "cwObject.h" +#include "cwText.h" #include "cwTextBuf.h" +#include "cwThread.h" -#include "cwMidiPort.h" +#include "cwMidi.h" +#include "cwMidiDecls.h" +#include "cwMidiFile.h" +#include "cwMidiDevice.h" +#include -//=================================================================================================== -// -// +#include "cwMidiAlsa.h" +#include "cwMidiFileDev.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 - uint8_t status; // running status - uint8_t data0; // data byte 0 - unsigned dataCnt; // data byte cnt for current status - unsigned dataIdx; // index (0 or 1) of next data byte - uint8_t* buf; // output buffer - unsigned bufByteCnt; // output buffer byte cnt - unsigned bufIdx; // next output buffer index - unsigned msgCnt; // count of channel messages in the buffer - } parser_t; - - parser_t* _handleToPtr( handle_t h ) - { - return handleToPtr(h); - } - - void _cmMpParserCb( parser_t* p, packet_t* pkt, unsigned pktCnt ) - { - cbRecd_t* c = p->cbChain; - for(; c!=NULL; c=c->linkPtr) - { - pkt->cbDataPtr = c->cbDataPtr; - c->cbFunc( pkt, pktCnt ); - } - } - - void _cmMpTransmitChMsgs( parser_t* p ) - { - if( p->msgCnt > 0 ) - { - p->pkt.msgArray = (msg_t*)p->buf; - p->pkt.msgCnt = p->msgCnt; - p->pkt.sysExMsg = NULL; - - //p->cbFunc( &p->pkt, 1 ); - _cmMpParserCb(p,&p->pkt,1); - - p->bufIdx = 0; - p->msgCnt = 0; - } - } - - void _cmMpTransmitSysEx( parser_t* p ) - { - p->pkt.msgArray = NULL; - p->pkt.sysExMsg = p->buf; - p->pkt.msgCnt = p->bufIdx; - //p->cbFunc( &p->pkt, 1 ); - _cmMpParserCb(p,&p->pkt,1); - p->bufIdx = 0; - - } - - void _cmMpParserStoreChMsg( parser_t* p, const time::spec_t* timeStamp, uint8_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 ) - { - mem::release(p->buf); - - cbRecd_t* c = p->cbChain; - while(c != NULL) - { - cbRecd_t* nc = c->linkPtr; - mem::release(c); - c = nc; - } - - mem::release(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 = mem::allocZ( 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 = mem::allocZ( 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 uint8_t* iBuf, unsigned iByteCnt ) -{ - - parser_t* p = _handleToPtr(h); - - if( p == NULL ) - return; - - const uint8_t* ip = iBuf; - const uint8_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, uint8_t status, uint8_t d0, uint8_t d1 ) -{ - rc_t rc = kOkRC; - parser_t* p = _handleToPtr(h); - uint8_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 = mem::allocZ( 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; - - mem::release(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(handle_t h, const char* deviceName) -{ - assert(deviceName!=NULL); - unsigned i; - unsigned n = count(h); - for(i=0; imsgCnt; ++j) - if( p->msgArray != NULL ) - { - if( ((p->msgArray[j].status & 0xf0) == kNoteOnMdId) && (p->msgArray[j].d1>0)) - 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]); - } - } + alsa::handle_t alsaDevH; + unsigned alsaPollfdN; + struct pollfd* alsaPollfdA; + unsigned alsa_dev_cnt; + + file_dev::handle_t fileDevH; + unsigned file_dev_cnt; + + unsigned total_dev_cnt; + + + unsigned thread_timeout_microsecs; + thread::handle_t threadH; + + transportStateId_t fileDevStateId; + + unsigned long long offset_micros; + unsigned long long last_posn_micros; + time::spec_t start_time; + + } device_t; + + device_t* _handleToPtr( handle_t h ) + { return handleToPtr(h); } + + rc_t _validate_dev_index( device_t* p, unsigned devIdx ) + { + rc_t rc = kOkRC; + if( devIdx >= p->total_dev_cnt ) + rc = cwLogError(kInvalidArgRC,"Invalid MIDI device index (%i >= %i).",devIdx,p->total_dev_cnt); + + return rc; } + + unsigned _devIdxToAlsaDevIdx( device_t* p, unsigned devIdx ) + { + return devIdx >= p->alsa_dev_cnt ? kInvalidIdx : devIdx; + } + + unsigned _devIdxToFileDevIdx( device_t* p, unsigned devIdx ) + { + return devIdx==kInvalidIdx || devIdx < p->alsa_dev_cnt ? kInvalidIdx : devIdx - p->alsa_dev_cnt; + } + + unsigned _alsaDevIdxToDevIdx( device_t* p, unsigned alsaDevIdx ) + { return alsaDevIdx; } + + unsigned _fileDevIdxToDevIdx( device_t* p, unsigned fileDevIdx ) + { return fileDevIdx == kInvalidIdx ? kInvalidIdx : p->alsa_dev_cnt + fileDevIdx; } + + bool _isAlsaDevIdx( device_t* p, unsigned devIdx ) + { return devIdx==kInvalidIdx ? false : devIdx < p->alsa_dev_cnt; } + + bool _isFileDevIdx( device_t* p, unsigned devIdx ) + { return devIdx==kInvalidIdx ? false : (p->alsa_dev_cnt <= devIdx && devIdx < p->total_dev_cnt); } + + + rc_t _destroy( device_t* p ) + { + rc_t rc = kOkRC; + + if((rc = destroy(p->threadH)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI port thread destroy failed."); + goto errLabel; + } + + destroy(p->alsaDevH); + destroy(p->fileDevH); + mem::release(p); + + errLabel: + return rc; + } + + bool _thread_func( void* arg ) + { + device_t* p = (device_t*)arg; + + unsigned max_sleep_micros = p->thread_timeout_microsecs/2; + unsigned sleep_millis = max_sleep_micros/1000; + + if( p->fileDevStateId == kPlayingStateId ) + { + time::spec_t cur_time = time::current_time(); + unsigned elapsed_micros = time::elapsedMicros(p->start_time,cur_time); + + unsigned long long file_posn_micros = p->offset_micros + elapsed_micros; + + // Send any messages whose time has expired and get the + // wait time for the next message. + file_dev::exec_result_t r = exec(p->fileDevH,file_posn_micros); + + // If the file dev has no more messages to play then sleep for the maximum time. + unsigned file_dev_sleep_micros = r.eof_fl ? max_sleep_micros : r.next_msg_wait_micros; + + // Prevent the wait time from being longer than the thread state change timeout. + unsigned sleep_micros = std::min( max_sleep_micros, file_dev_sleep_micros ); + + p->last_posn_micros = file_posn_micros + sleep_micros; + + // If the wait time is less than one millisecond then make it one millisecond. + // (remember that we allowed the file device to go 3 milliseconds ahead and + // and so it is safe, and better for preventing many very short timeout's, + // to wait at least 1 millisecond) + sleep_millis = std::max(1U, sleep_micros/1000 ); + } + + + // Block here waiting for ALSA events or timeout when the next file msg should be sent + int sysRC = poll( p->alsaPollfdA, p->alsaPollfdN, sleep_millis ); + + if(sysRC == 0 ) + { + // time-out + } + else + { + if( sysRC > 0 ) + { + rc_t rc; + if((rc = handleInputMsg(p->alsaDevH)) != kOkRC ) + { + cwLogError(rc,"ALSA MIDI dev. input failed"); + } + } + else + { + cwLogSysError(kOpFailRC,sysRC,"MIDI device poll failed."); + } + } + + return true; + } + + } // device } // midi } // cw - -cw::rc_t cw::midi::device::test() -{ - rc_t rc = kOkRC; - char ch; - unsigned parserBufByteCnt = 1024; - textBuf::handle_t tbH; - handle_t h; - // initialie the MIDI system - if((rc = create(h,testCallback,NULL,parserBufByteCnt,"app")) != kOkRC ) + + +cw::rc_t cw::midi::device::create( handle_t& hRef, + cbFunc_t cbFunc, + void* cbArg, + const char* filePortLabelA[], + unsigned max_file_cnt, + const char* appNameStr, + const char* fileDevName, + unsigned fileDevReadAheadMicros, + unsigned parserBufByteCnt ) +{ + rc_t rc = kOkRC; + rc_t rc1 = kOkRC; + + if((rc = destroy(hRef)) != kOkRC ) return rc; - // create a text buffer to hold the MIDI system report text + device_t* p = mem::allocZ(); + + if((rc = create( p->alsaDevH, cbFunc, cbArg, parserBufByteCnt, appNameStr )) != kOkRC ) + { + rc = cwLogError(rc,"ALSA MIDI device create failed."); + goto errLabel; + } + + p->alsa_dev_cnt = count(p->alsaDevH); + + if((rc = create( p->fileDevH, cbFunc, cbArg, p->alsa_dev_cnt, filePortLabelA, max_file_cnt, fileDevName, fileDevReadAheadMicros )) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file device create failed."); + goto errLabel; + } + + p->file_dev_cnt = count(p->fileDevH); + p->total_dev_cnt = p->alsa_dev_cnt + p->file_dev_cnt; + p->alsaPollfdA = pollFdArray(p->alsaDevH,p->alsaPollfdN); + p->fileDevStateId = kStoppedStateId; + + if((rc = thread::create(p->threadH, + _thread_func, + p)) != kOkRC ) + { + rc = cwLogError(rc,"The MIDI file device thread create failed."); + goto errLabel; + } + + p->thread_timeout_microsecs = stateTimeOutMicros(p->threadH); + + hRef.set(p); + + if((rc = unpause(p->threadH)) != kOkRC ) + { + rc = cwLogError(rc,"Initial thread un-pause failed."); + goto errLabel; + } + + +errLabel: + if(rc != kOkRC ) + rc1 = _destroy(p); + + if((rc = rcSelect(rc,rc1)) != kOkRC ) + rc = cwLogError(rc,"MIDI device mgr. create failed."); + + return rc; +} + +cw::rc_t cw::midi::device::create( handle_t& h, + cbFunc_t cbFunc, + void* cbArg, + const object_t* args ) +{ + rc_t rc = kOkRC; + const char* appNameStr = nullptr; + const char* fileDevName = "file_dev"; + unsigned fileDevReadAheadMicros = 3000; + unsigned parseBufByteCnt = 1024; + const object_t* file_ports = nullptr; + const object_t* port = nullptr; + + if((rc = args->getv("appNameStr",appNameStr, + "fileDevName",fileDevName, + "fileDevReadAheadMicros",fileDevReadAheadMicros, + "parseBufByteCnt",parseBufByteCnt, + "file_ports",file_ports)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI port parse args. failed."); + } + else + { + unsigned fpi = 0; + unsigned filePortArgCnt = file_ports->child_count(); + const char* labelArray[ filePortArgCnt ]; + memset(labelArray,0,sizeof(labelArray)); + + for(unsigned i=0; ichild_ele(i)) != nullptr ) + { + if((rc = port->getv("label",labelArray[fpi])) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file dev. port arg parse failed."); + goto errLabel; + } + + fpi += 1; + + } + } + + rc = create(h,cbFunc,cbArg,labelArray,fpi,appNameStr,fileDevName,fileDevReadAheadMicros,parseBufByteCnt); + + } + + +errLabel: + return rc; +} + + +cw::rc_t cw::midi::device::destroy( handle_t& hRef) +{ + rc_t rc = kOkRC; + if( !hRef.isValid() ) + return rc; + + device_t* p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI device mgr. destroy failed."); + goto errLabel; + } + + hRef.clear(); +errLabel: + return rc; +} + +bool cw::midi::device::isInitialized( handle_t h ) +{ return h.isValid(); } + +unsigned cw::midi::device::count( handle_t h ) +{ + device_t* p = _handleToPtr(h); + return p->total_dev_cnt; +} + +const char* cw::midi::device::name( handle_t h, unsigned devIdx ) +{ + device_t* p = _handleToPtr(h); + const char* ret_name = nullptr; + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + ret_name = name(p->alsaDevH,alsaDevIdx); + else + { + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx) + ret_name = name(p->fileDevH,fileDevIdx); + else + cwLogError(kInvalidArgRC,"%i is an invalid device index.",devIdx); + } + + if( ret_name == nullptr ) + cwLogError(kOpFailRC,"The name of device index %i could not be found.",devIdx); + + return ret_name; +} + +unsigned cw::midi::device::nameToIndex(handle_t h, const char* deviceName) +{ + device_t* p = _handleToPtr(h); + unsigned devIdx = kInvalidIdx; + + if((devIdx = nameToIndex(p->alsaDevH,deviceName)) != kInvalidIdx ) + devIdx = _alsaDevIdxToDevIdx(p,devIdx); + else + { + if((devIdx = nameToIndex(p->fileDevH,deviceName)) != kInvalidIdx ) + devIdx = _fileDevIdxToDevIdx(p,devIdx); + } + + if( devIdx == kInvalidIdx ) + cwLogError(kOpFailRC,"MIDI device name to index failed on '%s'.",cwStringNullGuard(deviceName)); + + return devIdx; +} + +unsigned cw::midi::device::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portNameStr ) +{ + device_t* p = _handleToPtr(h); + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + unsigned portIdx = kInvalidIdx; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + portIdx = portNameToIndex(p->alsaDevH,alsaDevIdx,flags,portNameStr); + else + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx ) + portIdx = portNameToIndex(p->fileDevH,fileDevIdx,flags,portNameStr); + + if( portIdx == kInvalidIdx ) + cwLogError(kInvalidArgRC,"The MIDI port name '%s' could not be found.",cwStringNullGuard(portNameStr)); + + return portIdx; +} + +cw::rc_t cw::midi::device::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + rc = portEnable(p->alsaDevH,alsaDevIdx,flags,portIdx,enableFl); + else + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx ) + rc = portEnable(p->fileDevH,fileDevIdx,flags,portIdx,enableFl); + + if( rc != kOkRC ) + rc = cwLogError(rc,"The MIDI port %s failed on dev '%s' port '%s'.",enableFl ? "enable" : "disable", cwStringNullGuard(name(h,devIdx)), cwStringNullGuard(portName(h,devIdx,flags,portIdx))); + + return rc; + +} + +unsigned cw::midi::device::portCount( handle_t h, unsigned devIdx, unsigned flags ) +{ + device_t* p = _handleToPtr(h); + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + unsigned portCnt = 0; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + portCnt = portCount(p->alsaDevH,alsaDevIdx,flags); + else + { + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx ) + portCnt = portCount(p->fileDevH,fileDevIdx,flags); + else + cwLogError(kInvalidArgRC,"The device index %i is not valid. Port count access failed.",devIdx); + } + + return portCnt; +} + +const char* cw::midi::device::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ) +{ + device_t* p = _handleToPtr(h); + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + const char* name = nullptr; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + name = portName(p->alsaDevH,alsaDevIdx,flags,portIdx); + else + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx ) + name = portName(p->fileDevH,fileDevIdx,flags,portIdx); + else + cwLogError(kInvalidArgRC,"The device index %i is not valid."); + + if( name == nullptr ) + cwLogError(kOpFailRC,"The access to port name on device index %i port index %i failed.",devIdx,portIdx); + + return name; +} + + +cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + rc = send(p->alsaDevH,alsaDevIdx,portIdx,st,d0,d1); + else + { + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx ) + rc = send(p->fileDevH,fileDevIdx,portIdx,st,d0,d1); + else + rc = cwLogError(kInvalidArgRC,"The device %i is not valid.",devIdx); + } + + if( rc != kOkRC ) + rc = cwLogError(rc,"The MIDI msg (0x%x %i %i) transmit failed.",st,d0,d1); + + return rc; +} + +cw::rc_t cw::midi::device::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + unsigned alsaDevIdx = kInvalidIdx; + unsigned fileDevIdx = kInvalidIdx; + + if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx ) + rc = sendData(p->alsaDevH,alsaDevIdx,portIdx,dataPtr,byteCnt); + else + { + if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx ) + rc = sendData(p->fileDevH,fileDevIdx,portIdx,dataPtr,byteCnt); + else + rc = cwLogError(kInvalidArgRC,"The device %i is not valid.",devIdx); + } + + if( rc != kOkRC ) + rc = cwLogError(rc,"The MIDI msg transmit data failed."); + + return rc; +} + +cw::rc_t cw::midi::device::openMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + + if( _devIdxToFileDevIdx(p,devIdx) == kInvalidIdx ) + { + cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx); + goto errLabel; + } + + if((rc = open_midi_file( p->fileDevH, portIdx, fname)) != kOkRC ) + goto errLabel; + + +errLabel: + return rc; + +} + +cw::rc_t cw::midi::device::seekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + + if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx ) + { + cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx); + goto errLabel; + } + + if((rc = seek_to_msg_index( p->fileDevH, portIdx, msgIdx)) != kOkRC ) + goto errLabel; + + +errLabel: + return rc; + +} + +cw::rc_t cw::midi::device::setEndMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + + if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx ) + { + cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx); + goto errLabel; + } + + if((rc = set_end_msg_index( p->fileDevH, portIdx, msgIdx)) != kOkRC ) + goto errLabel; + + +errLabel: + return rc; + +} + + +cw::rc_t cw::midi::device::start( handle_t h ) +{ + rc_t rc; + device_t* p = _handleToPtr(h); + + if( p->fileDevStateId != kPlayingStateId ) + { + + if((rc = rewind(p->fileDevH)) != kOkRC ) + { + rc = cwLogError(rc,"Rewind failed on MIDI file device."); + goto errLabel; + } + + p->start_time = time::current_time(); + p->offset_micros = 0; + p->last_posn_micros = 0; + p->fileDevStateId = kPlayingStateId; + } +errLabel: + + if( rc != kOkRC ) + rc = cwLogError(rc,"MIDI port start failed."); + + return rc; +} + +cw::rc_t cw::midi::device::stop( handle_t h ) +{ + device_t* p = _handleToPtr(h); + + p->fileDevStateId = kStoppedStateId; + + return kOkRC; +} + +cw::rc_t cw::midi::device::pause( handle_t h, bool pause_fl ) +{ + rc_t rc = kOkRC; + device_t* p = _handleToPtr(h); + + switch( p->fileDevStateId ) + { + case kStoppedStateId: + // unpausing does nothing from a 'stopped' state + break; + + case kPausedStateId: + if( !pause_fl ) + { + p->start_time = time::current_time(); + p->fileDevStateId = kPlayingStateId; + } + break; + + case kPlayingStateId: + if( pause_fl ) + { + p->offset_micros = p->last_posn_micros; + p->fileDevStateId = kPausedStateId; + } + break; + } + + return rc; +} + + +cw::rc_t cw::midi::device::report( handle_t h ) +{ + rc_t rc = kOkRC; + textBuf::handle_t tbH; + if((rc = textBuf::create(tbH)) != kOkRC ) goto errLabel; - // generate and print the MIDI system report report(h,tbH); - cwLogInfo("%s",textBuf::text(tbH)); - cwLogInfo("any key to send note-on (=quit)\n"); - - while((ch = getchar()) != 'q') - { - send(h,2,0,0x90,60,60); - } - - errLabel: - textBuf::destroy(tbH); - destroy(h); +errLabel: + destroy(tbH); return rc; } + +void cw::midi::device::report( handle_t h, textBuf::handle_t tbH) +{ + device_t* p = _handleToPtr(h); + report(p->alsaDevH,tbH); + report(p->fileDevH,tbH); +} + + +void cw::midi::device::latency_measure_reset(handle_t h) +{ + device_t* p = _handleToPtr(h); + latency_measure_reset(p->alsaDevH); + latency_measure_reset(p->fileDevH); +} + +cw::midi::device::latency_meas_combined_result_t cw::midi::device::latency_measure_result(handle_t h) +{ + device_t* p = _handleToPtr(h); + latency_meas_combined_result_t r; + r.alsa_dev = latency_measure_result(p->alsaDevH); + r.file_dev = latency_measure_result(p->fileDevH); + return r; +} + + + diff --git a/cwMidiDevice.h b/cwMidiDevice.h index 93902b8..4fa87f8 100644 --- a/cwMidiDevice.h +++ b/cwMidiDevice.h @@ -1,15 +1,12 @@ #ifndef cwMidiPort_H #define cwMidiPort_H -#include "cwMidiDecls.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 { @@ -17,60 +14,29 @@ namespace cw kOutMpFl = 0x02 }; - typedef void (*cbFunc_t)( const packet_t* pktArray, unsigned pktCnt ); - //) - //( { label:parser file_desc:"MIDI event parser converts raw MIDI events into packet_t messages." kw:[midi]} - - //=============================================================================================== - // MIDI Parser - // - - namespace parser - { - typedef handle handle_t; - - // 'cbFunc' and 'cbDataPtr' are optional. If 'cbFunc' is not supplied in the call to - // create() it may be supplied later by installCallback(). - // 'bufByteCnt' defines is the largest complete system-exclusive message the parser will - // by able to transmit. System-exclusive messages larger than this will be broken into - // multiple sequential callbacks. - rc_t create( handle_t& hRef, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbArg, unsigned bufByteCnt ); - rc_t destroy( handle_t& hRef ); - unsigned errorCount( handle_t h ); - void parseMidiData( handle_t h, const time::spec_t* timestamp, const uint8_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, uint8_t status, uint8_t d0, uint8_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 { typedef handle< struct device_str> handle_t; + + + + rc_t create( handle_t& h, + cbFunc_t cbFunc, + void* cbArg, + const char* filePortLabelA[], // filePortLabelA[ maxFileCnt ] + unsigned maxFileCnt, // count of file dev ports + const char* appNameStr, + const char* fileDevName = "file_dev", + unsigned fileDevReadAheadMicros = 3000, + unsigned parserBufByteCnt = 1024 ); + + rc_t create( handle_t& h, + cbFunc_t cbFunc, + void* cbArg, + const object_t* args ); - // '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 create( handle_t& h, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr ); rc_t destroy( handle_t& h); bool isInitialized( handle_t h ); @@ -80,15 +46,19 @@ namespace cw unsigned portCount( handle_t h, unsigned devIdx, unsigned flags ); const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ); unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName ); + rc_t portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl ); + rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 ); rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_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( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ); - rc_t removeCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ); - bool usesCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ); + rc_t openMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname ); + rc_t seekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx ); + rc_t setEndMsg( handle_t h, unsigned devIdx, unsigned portidx, unsigned msgIdx ); + + rc_t start( handle_t h ); + rc_t stop( handle_t h ); + rc_t pause( handle_t h, bool pause_fl ); + typedef struct { @@ -96,13 +66,21 @@ namespace cw time::spec_t note_on_output_ts; } latency_meas_result_t; - // Reset the latency measurement process. - void latency_measure_setup(handle_t h); - latency_meas_result_t latency_measure_result(handle_t h); - + typedef struct + { + latency_meas_result_t alsa_dev; + latency_meas_result_t file_dev; + } latency_meas_combined_result_t; + + // Reset the latency measurement process. Record the time of the first + // incoming note-on msg and the first outgoing note-on msg. + void latency_measure_reset(handle_t h); + latency_meas_combined_result_t latency_measure_result(handle_t h); + + rc_t report( handle_t h ); void report( handle_t h, textBuf::handle_t tbH); - rc_t test(); + rc_t testReport(); } } } diff --git a/cwMidiFileDev.cpp b/cwMidiFileDev.cpp index 7bcd96d..03148e1 100644 --- a/cwMidiFileDev.cpp +++ b/cwMidiFileDev.cpp @@ -6,232 +6,559 @@ #include "cwFile.h" #include "cwObject.h" #include "cwFileSys.h" -#include "cwThread.h" +#include "cwText.h" +#include "cwTextBuf.h" #include "cwMidi.h" #include "cwMidiDecls.h" #include "cwMidiFile.h" -#include "cwMidiFileDev.h" +#include "cwMidiDevice.h" #include +#include "cwMidiFileDev.h" namespace cw { namespace midi { - namespace file_dev + namespace device { - - typedef struct file_str + namespace file_dev { - char* label; - midi::file::handle_t mfH; - bool enable_fl; - } file_t; - - typedef struct file_dev_str - { - thread::handle_t threadH; // - int pipefdA[2]; - file_t* fileA; // fileA[ fileN ] - unsigned fileN; // - - msg_t* msgA; // msgA[ msgN ] - unsigned msgN; // - time::spec_t start_ts; - unsigned next_msg_idx; - unsigned next_rd_msg_idx; - - unsigned thread_timeout_microsecs; - unsigned msg_extra_microsecs; - } file_dev_t; - - file_dev_t * _handleToPtr(handle_t h) - { return handleToPtr(h); } - - rc_t _validate_file_index(file_dev_t* p, unsigned file_idx) - { - rc_t rc = kOkRC; - - if( file_idx >= p->fileN ) - rc = cwLogError(kInvalidArgRC,"The MIDI device file index %i is invalid.",file_idx); - - return rc; - } - - rc_t _close_midi_file( file_dev_t* p, unsigned file_idx ) - { - rc_t rc = kOkRC; - - if((rc = _validate_file_index(p,file_idx)) != kOkRC ) - goto errLabel; - - if((rc = close(p->fileA[file_idx].mfH)) != kOkRC ) + typedef struct file_msg_str { - rc = cwLogError(rc,"MIDI file close failed on MIDI file device index %i.",file_idx); - goto errLabel; - } - - errLabel: - return rc; - } + unsigned long long amicro; // + msg_t* msg; // msg_t as declared in cwMidiDecls.h + unsigned file_idx; + unsigned msg_idx; + } file_msg_t; - rc_t _destroy( file_dev_t* p ) - { - rc_t rc = kOkRC; - - if((rc = destroy(p->threadH)) != kOkRC ) + + typedef struct file_str { - rc = cwLogError(rc,"MIDI file device thread destroy failed."); - goto errLabel; - } - - for(unsigned i=0; ifileN; ++i) - { - if( p->fileA[i].mfH.isValid() ) - close( p->fileA[i].mfH ); + char* label; + char* fname; - mem::release(p->fileA[i].label); - } + msg_t* msgA; + unsigned msgN; - mem::release(p->fileA); - mem::release(p->msgA); - errLabel: - return rc; - } - - unsigned _calc_msg_count( file_dev_t* p ) - { - unsigned msgN = 0; - for(unsigned i=0; ifileN; ++i) - if( p->fileA[i].mfH.isValid() ) - msgN += msgCount( p->fileA[i].mfH ); - - return msgN; - } - - void _fill_msg_array( file_dev_t* p ) - { - for(unsigned i=0,k=0; ifileN; ++i) + bool enable_fl; + } file_t; + + typedef struct file_dev_str { - if( p->fileA[i].mfH.isValid() ) - { - unsigned fileMsgN = msgCount(p->fileA[i].mfH); - const file::trackMsg_t** fileMsgPtrA = msgArray(p->fileA[i].mfH); + cbFunc_t cbFunc; + void* cbArg; + + file_t* fileA; // fileA[ fileN ] + unsigned fileN; // + + file_msg_t* msgA; // msgA[ msgN ] + unsigned msgAllocN; // + unsigned msgN; + + unsigned devCnt; // always 1 + unsigned base_dev_idx; + char* dev_name; + + bool is_activeFl; + + // The following indexes are all int p->msgA[ p->msgN ] + unsigned beg_msg_idx; // beg_msg_idx is the first msg to transmit + unsigned end_msg_idx; // end_msg_idx indicates the last msg to transmit, end_msg_idx+1 will not be transmitted + unsigned next_wr_msg_idx; // next_wr_msg_idx is the next msg to transmit + unsigned next_rd_msg_idx; // next_rd_msg_idx is the last msg transmitted + + unsigned long long start_delay_micros; + + unsigned long long read_ahead_micros; + + bool latency_meas_enable_in_fl; + bool latency_meas_enable_out_fl; + latency_meas_result_t latency_meas_result; + + } file_dev_t; + + file_dev_t * _handleToPtr(handle_t h) + { return handleToPtr(h); } + + rc_t _validate_file_index(file_dev_t* p, unsigned file_idx) + { + rc_t rc = kOkRC; + + if( file_idx >= p->fileN ) + rc = cwLogError(kInvalidArgRC,"The MIDI device file/port index %i is invalid.",file_idx); + + return rc; + } + + bool _file_exists( const file_t& r ) + { return r.msgN > 0; } + + rc_t _validate_file_existence(file_dev_t* p, unsigned file_idx ) + { + rc_t rc; + if((rc = _validate_file_index(p,file_idx)) == kOkRC ) + if( !_file_exists(p->fileA[file_idx]) ) + rc = cwLogError(kInvalidArgRC,"The MIDI device file at file/port index %i does not exist.",file_idx); - for(unsigned j=0; j= p->devCnt ) + rc = cwLogError(kInvalidArgRC,"The MIDI file device index %i is invalid.",dev_idx ); + return rc; + } + + rc_t _validate_port_index(file_dev_t* p, unsigned port_idx ) + { return _validate_file_index(p,port_idx); } + + void _reset_indexes( file_dev_t* p ) + { + p->beg_msg_idx = kInvalidIdx; + p->end_msg_idx = kInvalidIdx; + p->next_wr_msg_idx = kInvalidIdx; + p->next_rd_msg_idx = kInvalidIdx; + } + + rc_t _close_file( file_dev_t* p, unsigned file_idx ) + { + rc_t rc = kOkRC; + + + if((rc = _validate_file_index(p,file_idx)) != kOkRC ) + goto errLabel; + + if( _file_exists(p->fileA[file_idx] ) ) + { + // if the beg/end msg index refers to the file being closed then invalidate the beg/end msg idx + if( p->beg_msg_idx != kInvalidIdx && p->beg_msg_idx < p->msgN && p->msgA[ p->beg_msg_idx ].file_idx == file_idx ) + p->beg_msg_idx = kInvalidIdx; + + if( p->end_msg_idx != kInvalidIdx && p->end_msg_idx < p->msgN && p->msgA[ p->end_msg_idx ].file_idx == file_idx ) + p->end_msg_idx = kInvalidIdx; + + mem::release(p->fileA[file_idx].fname); + p->fileA[file_idx].msgN = 0; + } + + + errLabel: + return rc; + } + + rc_t _destroy( file_dev_t* p ) + { + rc_t rc = kOkRC; + + for(unsigned i=0; ifileN; ++i) + { + mem::release(p->fileA[i].msgA); + mem::release(p->fileA[i].label); + mem::release(p->fileA[i].fname); + } + + mem::release(p->fileA); + mem::release(p->msgA); + mem::release(p->dev_name); + mem::release(p); + //errLabel: + return rc; + } + + + rc_t _open_midi_file( file_dev_t* p, unsigned file_idx, const char* fname ) + { + rc_t rc = kOkRC; + midi::file::handle_t mfH; + + if((rc = open(mfH, fname)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file open failed on '%s'.",cwStringNullGuard(fname)); + goto errLabel; + } + else + { + unsigned msg_idx = 0; + unsigned msgN = msgCount(mfH);; + const file::trackMsg_t** fileMsgPtrA = msgArray(mfH); + + p->fileA[file_idx].msgA = mem::resizeZ(p->fileA[file_idx].msgA,msgN); + + for(unsigned j=0; jstatus) ) + { + msg_t* m = p->fileA[file_idx].msgA + msg_idx; + + m->uid = j; + m->ch = fileMsgPtrA[j]->u.chMsgPtr->ch; + m->status = fileMsgPtrA[j]->status; + m->d0 = fileMsgPtrA[j]->u.chMsgPtr->d0; + m->d1 = fileMsgPtrA[j]->u.chMsgPtr->d1; + m->timeStamp = time::microsecondsToSpec(fileMsgPtrA[j]->amicro); + + msg_idx += 1; + } + + p->fileA[file_idx].msgN = msg_idx; + p->fileA[file_idx].fname = mem::duplStr(fname); + close( mfH ); + + } + + + errLabel: + return rc; + } + + + unsigned _calc_msg_count( file_dev_t* p ) + { + unsigned msgAllocN = 0; + for(unsigned i=0; ifileN; ++i) + if( p->fileA[i].msgA != nullptr ) + msgAllocN += p->fileA[i].msgN; + + return msgAllocN; + } + + // Set msg_idx to kInvalidIdx to seek to the current value of p->beg_msg_idx + // or 0 if p->beg_msg_idx was never set. + // If msg_idx is a valid msg index then it will be assigned to p->beg_msg_idx + rc_t _seek_to_msg_index( file_dev_t* p, unsigned file_idx, unsigned msg_idx ) + { + rc_t rc; + unsigned i = 0; + unsigned beg_msg_idx = p->beg_msg_idx; + + if((rc = _validate_file_existence(p,file_idx)) != kOkRC ) + goto errLabel; + + // if no target msg was given ... + if( msg_idx == kInvalidIdx ) + { + // ... then use the previous target msg or 0 + beg_msg_idx = p->beg_msg_idx == kInvalidIdx ? 0 : p->beg_msg_idx; + } + else // if a target msg was given .. + { + // locate the msg in p->msgA[] + for(i=0; imsgN; ++i) + if( p->msgA[i].file_idx == file_idx && p->msgA[i].msg_idx == msg_idx ) + { + p->beg_msg_idx = i; + beg_msg_idx = i; + break; + } + + if( i == p->msgN ) { - p->msgA[k].msg = fileMsgPtrA[j]; - p->msgA[k].file_idx = i; + rc = cwLogError(kEleNotFoundRC,"The 'begin' MIDI file event at index %i in %s was not found.",msg_idx,cwStringNullGuard(p->fileA[file_idx].label)); + goto errLabel; + } + } + + p->next_wr_msg_idx = beg_msg_idx; + p->next_rd_msg_idx = beg_msg_idx; + + errLabel: + return rc; + } + + // Set file_idx and msg_idx to kInvalidIdx to make the p->msgN the end index. + // Set msg_idx to kInvalidIdx to make the last p->fileA[file_idx].msgN the end index. + rc_t _set_end_msg_index( file_dev_t* p, unsigned file_idx, unsigned msg_idx ) + { + rc_t rc; + unsigned i = 0; + + if( file_idx == kInvalidIdx ) + p->end_msg_idx = p->msgN - 1; + else + { + if((rc = _validate_file_existence(p,file_idx)) != kOkRC ) + goto errLabel; + + if( msg_idx == kInvalidIdx ) + msg_idx = p->fileA[ file_idx ].msgN - 1; + + // locate the msg in p->msgA[] + for(i=0; imsgN; ++i) + if( p->msgA[i].file_idx == file_idx && p->msgA[i].msg_idx == msg_idx ) + { + p->end_msg_idx = i; + break; + } + + if( i == p->msgN ) + { + rc = cwLogError(kEleNotFoundRC,"The 'end' MIDI file event at index %i in %s was not found.",msg_idx,cwStringNullGuard(p->fileA[file_idx].label)); + goto errLabel; + } + } + + errLabel: + return rc; + } + + + unsigned _fill_msg_array_from_msg( file_dev_t* p, unsigned file_idx, msg_t* msgA, unsigned msgN, unsigned msg_idx ) + { + unsigned k = msg_idx; + + for(unsigned j=0; jmsgA[k].msg_idx = j; + p->msgA[k].amicro = time::specToMicroseconds(msgA[j].timeStamp); + p->msgA[k].msg = msgA + j; + p->msgA[k].file_idx = file_idx; ++k; } - } - } - } - - rc_t _prepare_msg_array( file_dev_t* p ) - { - rc_t rc = kOkRC; - p->msgN = _calc_msg_count(p); - - p->msgA = mem::resize(p->msgA,p->msgN); - - _fill_msg_array(p); - - auto f = [](const msg_t& a0,const msg_t& a1) -> bool { return a0.msg->amicro < a1.msg->amicro; }; - std::sort(p->msgA,p->msgA+p->msgN,f); - - p->next_msg_idx = 0; - - return rc; - } - - - rc_t _enable_file( handle_t h, unsigned file_idx, bool enable_fl ) - { - rc_t rc; - - file_dev_t* p = _handleToPtr(h); - - if((rc = _validate_file_index(p, file_idx)) != kOkRC ) - goto errLabel; - - p->fileA[ file_idx ].enable_fl = enable_fl; - - errLabel: - - if(rc != kOkRC ) - rc = cwLogError(rc,"MIDI file device %s failed on file index %i.", enable_fl ? "enable" : "disable", file_idx ); - - return rc; - } - - bool _thread_func( void* arg ) - { - file_dev_t* p = (file_dev_t*)arg; - unsigned max_sleep_micros = p->thread_timeout_microsecs/2; - unsigned sleep_micros = max_sleep_micros; - int sysRC = 0; - - if( p->next_msg_idx < p->msgN ) - { - unsigned cur_time = time::elapsedMicros(p->start_ts); - unsigned msg_time = p->msgA[ p->next_msg_idx ].msg->amicro; - unsigned write_cnt= 0; - - // for all msgs before the current time - while( msg_time <= cur_time || msg_time - cur_time < p->msg_extra_microsecs ) - { - // - // consume msg here - // - - // advance to next msg - p->next_msg_idx += 1; // TODO: should be an increment 'release' memory barrier here - - if( p->next_msg_idx >= p->msgN ) - break; - msg_time = p->msgA[ p->next_msg_idx ].msg->amicro; + return k; + + } + + void _fill_msg_array( file_dev_t* p ) + { + unsigned msg_idx = 0; + for(unsigned i=0; ifileN; ++i) + if( p->fileA[i].msgA != nullptr ) + msg_idx = _fill_msg_array_from_msg(p,i,p->fileA[i].msgA,p->fileA[i].msgN,msg_idx); - write_cnt += 1; - } - - sleep_micros = msg_time - cur_time; - - if( write_cnt ) - if((sysRC = write(p->pipefdA[1],&write_cnt,sizeof(write_cnt))) < (int)sizeof(write_cnt) ) - { - cwLogSysError(kWriteFailRC,errno,"Pipe write failed."); - } - - //printf("%i %i %i %i %i %i %i\n",p->next_msg_idx,p->msgN,write_cnt,sleep_micros,cur_time,msg_time-cur_time,max_sleep_micros); + p->msgN = msg_idx; + } - sleepUs(std::min(sleep_micros,max_sleep_micros)); + // Combine all the file msg's into a single array and sort them on file_msg_t.amicro. + rc_t _prepare_msg_array( file_dev_t* p ) + { + rc_t rc = kOkRC; + + // save the current starting message + unsigned beg_file_idx = p->beg_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->beg_msg_idx ].file_idx; + unsigned beg_msg_idx = p->beg_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->beg_msg_idx ].msg_idx; + + // if the 'beg' file does not exist + if( beg_file_idx != kInvalidIdx && _file_exists(p->fileA[ beg_file_idx ])==false ) + { + beg_file_idx = kInvalidIdx; + beg_msg_idx = kInvalidIdx; + } + + // save the current ending message + unsigned end_file_idx = p->end_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->end_msg_idx ].file_idx; + unsigned end_msg_idx = p->end_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->end_msg_idx ].msg_idx; + + // if the 'end' file does not exist + if( end_file_idx != kInvalidIdx && _file_exists(p->fileA[ end_file_idx ])==false ) + { + end_file_idx = kInvalidIdx; + end_msg_idx = kInvalidIdx; + } + + + // calc the count of message in all the files + p->msgAllocN = _calc_msg_count(p); + + // allocate a single array to hold the messages from all the files + p->msgA = mem::resize(p->msgA,p->msgAllocN); + + // fill p->msgA[] from each of the files + _fill_msg_array(p); + + // sort p->msgA[] on msgA[].amicro + auto f = [](const file_msg_t& a0,const file_msg_t& a1) -> bool { return a0.amicro < a1.amicro; }; + std::sort(p->msgA,p->msgA+p->msgN,f); + + // by default we rewind to the first msg + p->next_wr_msg_idx = 0; + p->next_rd_msg_idx = 0; + + // if a valid seek position exists then reset it + if( beg_file_idx != kInvalidIdx && beg_msg_idx != kInvalidIdx ) + if((rc = _seek_to_msg_index( p, beg_file_idx, beg_msg_idx )) != kOkRC ) + rc = cwLogError(rc,"The MIDI file device starting output message could not be restored."); + + if( end_file_idx != kInvalidIdx && end_msg_idx != kInvalidIdx ) + if((rc = _set_end_msg_index(p, end_file_idx, end_msg_idx )) != kOkRC ) + rc = cwLogError(rc,"The MIDI file device ending output message could not be restored."); + + + return rc; + } + + void _update_active_flag( file_dev_t* p ) + { + unsigned i=0; + for(; ifileN; ++i) + if( _file_exists(p->fileA[i]) && p->fileA[i].enable_fl ) + break; + + p->is_activeFl = i < p->fileN; + } + + rc_t _enable_file( handle_t h, unsigned file_idx, bool enable_fl ) + { + rc_t rc; + + file_dev_t* p = _handleToPtr(h); + + if((rc = _validate_file_existence(p, file_idx)) != kOkRC ) + goto errLabel; + + p->fileA[ file_idx ].enable_fl = enable_fl; + + _update_active_flag(p); + + errLabel: + + if(rc != kOkRC ) + rc = cwLogError(rc,"MIDI file device %s failed on file index %i.", enable_fl ? "enable" : "disable", file_idx ); + + return rc; + } + + void _callback( file_dev_t* p, unsigned file_idx, msg_t* msgA, unsigned msgN ) + { + if( p->cbFunc != nullptr ) + { + packet_t pkt = { + .cbArg = p->cbArg, + .devIdx = p->base_dev_idx, + .portIdx = file_idx, + .msgArray = msgA, + .msgCnt = msgN + }; + + p->cbFunc( &pkt, 1 ); + } + } + + void _packetize_and_transmit_msgs( file_dev_t* p, unsigned xmt_msg_cnt ) + { + msg_t msgA[ xmt_msg_cnt ]; + + unsigned pkt_msg_idx = 0; + unsigned file_idx_0 = kInvalidIdx; + + assert( p->next_rd_msg_idx != kInvalidIdx && p->next_wr_msg_idx != kInvalidIdx ); + + for(; p->next_rd_msg_idx < p->next_wr_msg_idx && pkt_msg_idx < xmt_msg_cnt; ++p->next_rd_msg_idx) + { + const file_msg_t& m = p->msgA[ p->next_rd_msg_idx ]; + + if( p->fileA[ m.file_idx ].enable_fl && m.msg != nullptr ) + { + // + if( p->latency_meas_enable_in_fl && isNoteOn(m.msg->status,m.msg->d1) ) + { + p->latency_meas_enable_in_fl = false; + time::get(p->latency_meas_result.note_on_input_ts); + } + + // if the file_idx is not the same as the previous messages then + // send the currently stored messages from msgA[] - because packet + // messages must all belong to the same port + if( file_idx_0 != kInvalidIdx && m.file_idx != file_idx_0 ) + { + _callback(p,file_idx_0,msgA,pkt_msg_idx); + pkt_msg_idx = 0; + } + + msgA[pkt_msg_idx] = *m.msg; + file_idx_0 = m.file_idx; + pkt_msg_idx += 1; + } + } + + if( pkt_msg_idx > 0 ) + _callback(p,file_idx_0,msgA,pkt_msg_idx); + } + + rc_t _load_messages( handle_t h, unsigned file_idx, const char* fname, const msg_t* msgA, unsigned msgN ) + { + rc_t rc; + file_dev_t* p = _handleToPtr(h); + + if((rc = _validate_file_index(p,file_idx)) != kOkRC ) + goto errLabel; + + if((rc = _close_file(p,file_idx)) != kOkRC ) + goto errLabel; + + if( fname != nullptr ) + { + if((rc = _open_midi_file(p,file_idx,fname)) != kOkRC ) + goto errLabel; + } + else + { + if( msgA != nullptr ) + { + p->fileA[ file_idx ].msgA = mem::allocZ(msgN); + p->fileA[ file_idx ].msgN = msgN; + memcpy(p->fileA[ file_idx ].msgA, msgA, msgN*sizeof(msg_t)); + } + else + { + assert(0); + } + } + + if((rc = _prepare_msg_array(p)) != kOkRC ) + goto errLabel; + + p->fileA[ file_idx ].enable_fl = true; + + errLabel: + + _update_active_flag(p); + + if( rc != kOkRC ) + rc = cwLogError(rc,"MIDI file device msg. load port failed %i.",file_idx); + + return rc; + } - return true; } } } } -cw::rc_t cw::midi::file_dev::create( handle_t& hRef, const char* labelA[], unsigned max_file_cnt ) +cw::rc_t cw::midi::device::file_dev::create( handle_t& hRef, + cbFunc_t cbFunc, + void* cbArg, + unsigned baseDevIdx, + const char* labelA[], + unsigned max_file_cnt, + const char* dev_name, + unsigned read_ahead_micros) { rc_t rc; - int sysRC = 0; if((rc = destroy(hRef)) != kOkRC ) return rc; file_dev_t* p = mem::allocZ(); - + + p->cbFunc = cbFunc; + p->cbArg = cbArg; p->fileN = max_file_cnt; p->fileA = mem::allocZ(p->fileN); - p->next_msg_idx = 0; - p->thread_timeout_microsecs = 20000; - p->msg_extra_microsecs = 3000; + p->devCnt = 1; + p->is_activeFl = false; + p->read_ahead_micros = read_ahead_micros; + p->base_dev_idx = baseDevIdx; + p->dev_name = mem::duplStr(dev_name); + _reset_indexes(p); + for(unsigned i=0; ifileN; ++i) { if( labelA[i] != nullptr ) @@ -245,22 +572,6 @@ cw::rc_t cw::midi::file_dev::create( handle_t& hRef, const char* labelA[], unsig } } - if((sysRC = pipe(p->pipefdA)) != 0 ) - { - rc = cwLogSysError(kOpFailRC,sysRC,"Pipe create failed."); - goto errLabel; - } - - if((rc = thread::create(p->threadH, - _thread_func, - p, - p->thread_timeout_microsecs, - p->thread_timeout_microsecs)) != kOkRC ) - { - rc = cwLogError(rc,"The MIDI file device thread create failed."); - goto errLabel; - } - hRef.set(p); errLabel: @@ -269,7 +580,7 @@ errLabel: return rc; } -cw::rc_t cw::midi::file_dev::destroy( handle_t& hRef ) +cw::rc_t cw::midi::device::file_dev::destroy( handle_t& hRef ) { rc_t rc = kOkRC; if(!hRef.isValid() ) @@ -289,211 +600,321 @@ errLabel: return rc; } -unsigned cw::midi::file_dev::file_count( handle_t h ) +bool cw::midi::device::file_dev::is_active( handle_t h ) +{ + file_dev_t* p = _handleToPtr(h); + return p->is_activeFl; +} + +unsigned cw::midi::device::file_dev::file_count( handle_t h ) { file_dev_t* p = _handleToPtr(h); return p->fileN; } -cw::rc_t cw::midi::file_dev::open_midi_file( handle_t h, unsigned file_idx, const char* fname ) +cw::rc_t cw::midi::device::file_dev::open_midi_file( handle_t h, unsigned file_idx, const char* fname ) { - rc_t rc; - file_dev_t* p = _handleToPtr(h); - - if((rc = _close_midi_file(p,file_idx)) != kOkRC ) - goto errLabel; - - if((rc = open(p->fileA[file_idx].mfH, fname )) != kOkRC ) - goto errLabel; - - if((rc = _prepare_msg_array(p)) != kOkRC ) - goto errLabel; - - if(0) - { - unsigned level = log::level( log::globalHandle()); - log::setLevel( log::globalHandle(), log::kPrint_LogLevel ); - printMsgs( p->fileA[file_idx].mfH, log::globalHandle()); - log::setLevel( log::globalHandle(), level ); - } - - p->fileA[ file_idx ].enable_fl = true; - -errLabel: - if( rc != kOkRC ) - rc = cwLogError(rc,"MIDI file device open failed on '%s'.",cwStringNullGuard(fname)); - - return rc; -} - -cw::rc_t cw::midi::file_dev::seek_to_event( handle_t h, unsigned file_idx, unsigned msg_idx ) -{ - rc_t rc = kOkRC; - return rc; + return _load_messages(h,file_idx,fname,nullptr,0); } -cw::rc_t cw::midi::file_dev::start( handle_t h ) +cw::rc_t cw::midi::device::file_dev::load_messages( handle_t h, unsigned file_idx, const msg_t* msgA, unsigned msgN ) { - rc_t rc; - file_dev_t* p = _handleToPtr(h); - - time::get(p->start_ts); - - if((rc = unpause(p->threadH)) != kOkRC ) - { - rc = cwLogError(rc,"Thread un-pause failed."); - goto errLabel; - } - -errLabel: - return rc; -} - -cw::rc_t cw::midi::file_dev::stop( handle_t h ) -{ - rc_t rc; - file_dev_t* p = _handleToPtr(h); - if((rc = pause(p->threadH)) != kOkRC ) - { - rc = cwLogError(rc,"Thread un-pause failed."); - goto errLabel; - } - -errLabel: - return rc; + return _load_messages(h,file_idx,nullptr,msgA,msgN); } -cw::rc_t cw::midi::file_dev::enable_file( handle_t h, unsigned file_idx ) + +cw::rc_t cw::midi::device::file_dev::enable_file( handle_t h, unsigned file_idx, bool enableFl ) +{ return _enable_file(h,file_idx,enableFl); } + +cw::rc_t cw::midi::device::file_dev::enable_file( handle_t h, unsigned file_idx ) { return _enable_file(h,file_idx,true); } -cw::rc_t cw::midi::file_dev::disable_file( handle_t h,unsigned file_idx ) +cw::rc_t cw::midi::device::file_dev::disable_file( handle_t h,unsigned file_idx ) { return _enable_file(h,file_idx,false); } -int cw::midi::file_dev::file_descriptor( handle_t h ) + +unsigned cw::midi::device::file_dev::count( handle_t h ) { file_dev_t* p = _handleToPtr(h); - return p->pipefdA[0]; + return p->devCnt; } -cw::rc_t cw::midi::file_dev::read( handle_t h, msg_t* buf, unsigned buf_msg_cnt, unsigned& actual_msg_cnt_ref ) +const char* cw::midi::device::file_dev::name( handle_t h, unsigned devIdx ) { - rc_t rc = kOkRC; - file_dev_t* p = _handleToPtr(h); - unsigned cur_wr_msg_idx = p->next_msg_idx; /// TODO: should be an 'aquire' here + file_dev_t* p = _handleToPtr(h); - actual_msg_cnt_ref = 0; + return _validate_dev_index(p,devIdx)==kOkRC ? p->dev_name : nullptr; +} + +unsigned cw::midi::device::file_dev::nameToIndex(handle_t h, const char* deviceName) +{ + file_dev_t* p = _handleToPtr(h); + return textIsEqual(deviceName,p->dev_name) ? 0 : kInvalidIdx; +} + +unsigned cw::midi::device::file_dev::portCount( handle_t h, unsigned devIdx, unsigned flags ) +{ + file_dev_t* p = _handleToPtr(h); + + if(_validate_dev_index(p,devIdx) != kOkRC ) + return 0; - for(unsigned i=0; p->next_rd_msg_idxnext_rd_msg_idx) - if( p->fileA[ p->msgA[i].file_idx ].enable_fl ) - { - memcpy(buf + i, p->msgA + p->next_rd_msg_idx, sizeof(msg_t)); - actual_msg_cnt_ref += 1; - ++i; - } - - return rc; + return flags & kInMpFl ? p->fileN : 0; } -cw::rc_t cw::midi::file_dev::test( const object_t* cfg ) +const char* cw::midi::device::file_dev::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ) { - handle_t h; - rc_t rc = kOkRC; - rc_t rc1 = kOkRC; - const char* labelA[] = { "file0" }; - const char* fname = nullptr; - unsigned pollfdN = 1; - struct pollfd* pollfdA = mem::allocZ(pollfdN); - int poll_timeout_ms = 50; - unsigned limitN = 0; + file_dev_t* p = _handleToPtr(h); - if((rc = cfg->getv("fname",fname)) != kOkRC || fname == nullptr) + if( _validate_dev_index(p,devIdx) != kOkRC ) + return nullptr; + + if( _validate_port_index(p,portIdx) != kOkRC ) + return nullptr; + + return p->fileA[portIdx].label; +} + +unsigned cw::midi::device::file_dev::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName ) +{ + file_dev_t* p = _handleToPtr(h); + + if( _validate_dev_index(p,devIdx) != kOkRC ) + return kInvalidIdx; + + if( flags & kInMpFl ) { - cwLogError(rc,"MIDI file dev test arg. parse failed."); + for(unsigned i=0; ifileN; ++i) + if( textIsEqual(p->fileA[i].label,portName) ) + return i; + } + return kInvalidIdx; +} + +cw::rc_t cw::midi::device::file_dev::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl ) +{ + rc_t rc = kOkRC; + file_dev_t* p = _handleToPtr(h); + + if((rc = _validate_dev_index(p,devIdx)) != kOkRC ) goto errLabel; + + if( flags & kInMpFl ) + { + rc = enable_file(h,portIdx,enableFl); + } + else + { + rc = cwLogError(kNotImplementedRC,"MIDI file dev output enable/disable has not been implemented."); } - cwLogInfo("MIDI file dev testing with '%s'.",fname); - - if((rc = create(h,labelA,1)) != kOkRC ) - goto errLabel; - - if((rc = open_midi_file(h,0,fname)) != kOkRC ) - goto errLabel; - - if((rc = start(h)) != kOkRC ) - goto errLabel; - - pollfdA[0].fd = file_descriptor(h); - pollfdA[0].events = POLLIN; - pollfdA[0].revents = 0; - - cwLogInfo("Starting"); - - while( rc == kOkRC && limitN < 1000 ) - { - // block here waiting for a midi file dev event - int poll_res = poll(pollfdA, pollfdN, poll_timeout_ms ); - - // if wait timed out - if( poll_res == 0 ) - { - poll_res = kOkRC; - continue; - } - - // if error - if( poll_res < 0 ) - rc = cwLogSysError(kOpFailRC,poll_res,"Poll failed."); - else // if ready to ready - { - if( pollfdA[0].revents & POLLIN ) - { - int sysRC; - unsigned rd_buf_cnt = 0; - - // read the pipe to get the count of msgs - if((sysRC = ::read(pollfdA[0].fd,&rd_buf_cnt,sizeof(rd_buf_cnt))) != sizeof(rd_buf_cnt)) - { - cwLogSysError(kReadFailRC,errno,"Pipe read failed."); - goto errLabel; - } - else - { - if( rd_buf_cnt > 0 ) - { - unsigned actual_cnt = 0; - msg_t buf[ rd_buf_cnt ]; - - // read the file dev messages - if((rc = read(h,buf,rd_buf_cnt, actual_cnt)) != kOkRC ) - { - rc = cwLogError(kReadFailRC,"File device read failed."); - goto errLabel; - } - else - { - printf("rd:%i %i\n",rd_buf_cnt, actual_cnt); - limitN += 1; - } - } - } - } - } - } - - - if((rc = stop(h)) != kOkRC ) - goto errLabel; - errLabel: - rc1 = destroy(h); - - rc = rcSelect(rc,rc1); - if(rc != kOkRC ) - rc = cwLogError(rc,"MIDI file dev test failed."); + rc = cwLogError(rc,"MIDI file dev port enable/disable failed."); + + return rc; +} + +cw::rc_t cw::midi::device::file_dev::seek_to_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx ) +{ + file_dev_t* p = _handleToPtr(h); + + return _seek_to_msg_index(p,file_idx,msg_idx); +} + +cw::rc_t cw::midi::device::file_dev::set_end_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx ) +{ + file_dev_t* p = _handleToPtr(h); + return _set_end_msg_index(p, file_idx, msg_idx ); +} + + + +cw::rc_t cw::midi::device::file_dev::rewind( handle_t h ) +{ + file_dev_t* p = _handleToPtr(h); + + if( p->beg_msg_idx == kInvalidIdx ) + { + p->next_wr_msg_idx = 0; + p->next_rd_msg_idx = 0; + } + else + { + p->next_wr_msg_idx = p->beg_msg_idx; + p->next_rd_msg_idx = p->beg_msg_idx; + } + + return kOkRC; +} + +cw::rc_t cw::midi::device::file_dev::set_start_delay( handle_t h, unsigned start_delay_micros ) +{ + file_dev_t* p = _handleToPtr(h); + + p->start_delay_micros = start_delay_micros; + + return kOkRC; + +} + +cw::midi::device::file_dev::exec_result_t cw::midi::device::file_dev::exec( handle_t h, unsigned long long cur_time_us ) +{ + exec_result_t r; + + file_dev_t*p = _handleToPtr(h); + + // p->end_msg_idx indicates the last msg to transmit, but we wait until the msg following p->end_msg_idx to + // actually stop transmitting and set r.eof_fl. This will facilitate providing a natural time gap to loop to the beginning. + unsigned end_msg_idx = p->end_msg_idx == kInvalidIdx ? p->msgN-1 : p->end_msg_idx; + + + // if there are no messages left to send + if( p->next_wr_msg_idx==kInvalidIdx || p->next_wr_msg_idx >= p->msgN || p->next_wr_msg_idx > p->end_msg_idx ) + { + r.next_msg_wait_micros = 0; + r.xmit_cnt = 0; + r.eof_fl = true; + } + else + { + + if( cur_time_us < p->start_delay_micros ) + { + r.xmit_cnt = 0; + r.eof_fl = false; + r.next_msg_wait_micros = p->start_delay_micros - cur_time_us; + } + else + { + cur_time_us -= p->start_delay_micros; + + unsigned base_msg_idx = p->beg_msg_idx == kInvalidIdx ? 0 : p->beg_msg_idx; + + assert( base_msg_idx <= p->next_wr_msg_idx ); + + unsigned long long msg_time_us = p->msgA[ p->next_wr_msg_idx ].amicro - p->msgA[ base_msg_idx ].amicro; + unsigned xmit_cnt = 0; + unsigned long long end_time_us = cur_time_us + p->read_ahead_micros; + + // for all msgs <= current time + read_ahead_micros + while( msg_time_us <= end_time_us ) + { + // + // consume msg here + // + + xmit_cnt += 1; + + // advance to next msg + p->next_wr_msg_idx += 1; + + // check for EOF + if( p->next_wr_msg_idx >= p->msgN) + break; + + // time of next msg + msg_time_us = p->msgA[ p->next_wr_msg_idx ].amicro - p->msgA[ base_msg_idx ].amicro; + + // if we went past the end msg then stop + if( p->next_wr_msg_idx > end_msg_idx ) + break; + } + + assert( p->next_wr_msg_idx >= p->msgN || msg_time_us > cur_time_us ); + + r.xmit_cnt = xmit_cnt; + r.eof_fl = p->next_wr_msg_idx >= p->msgN; + r.next_msg_wait_micros = r.eof_fl ? 0 : msg_time_us - cur_time_us; + + // callback with output msg's + if( xmit_cnt ) + _packetize_and_transmit_msgs( p, xmit_cnt ); + } + + } + + return r; + +} + + +cw::rc_t cw::midi::device::file_dev::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 ) +{ + return cwLogError(kNotImplementedRC,"MIDI file dev send() not implemented."); + + file_dev_t* p = _handleToPtr(h); + + if( p->latency_meas_enable_out_fl && isNoteOn(st,d1) ) + { + p->latency_meas_enable_out_fl = false; + time::get(p->latency_meas_result.note_on_output_ts); + } + +} + +cw::rc_t cw::midi::device::file_dev::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ) +{ + return cwLogError(kNotImplementedRC,"MIDI file dev send() not implemented."); +} + +void cw::midi::device::file_dev::latency_measure_reset(handle_t h) +{ + file_dev_t* p = _handleToPtr(h); + + p->latency_meas_result.note_on_input_ts = {}; + p->latency_meas_result.note_on_output_ts = {}; + p->latency_meas_enable_in_fl = true; + p->latency_meas_enable_out_fl = true; +} + +cw::midi::device::latency_meas_result_t cw::midi::device::file_dev::latency_measure_result(handle_t h) +{ + file_dev_t* p = _handleToPtr(h); + return p->latency_meas_result; +} + + +void cw::midi::device::file_dev::report( handle_t h, textBuf::handle_t tbH) +{ + file_dev_t* p = _handleToPtr(h); + + print(tbH,"%i : Device: '%s'\n",p->base_dev_idx,p->dev_name); + + if( p->fileN ) + print(tbH," Input:\n"); + + + for(unsigned i=0; ifileN; ++i) + { + const char* fname = ""; + + if( p->fileA[i].fname != nullptr ) + { + fname = p->fileA[i].fname; + } + else + if( p->fileA[i].msgA != nullptr ) + { + fname = "msg array"; + } + + print(tbH," port:%i '%s' ena:%i msg count:%i %s\n", + i, + cwStringNullGuard(p->fileA[i].label), + p->fileA[i].enable_fl, + p->fileA[i].msgN, + fname ); + } + +} + + +cw::rc_t cw::midi::device::file_dev::test( const object_t* cfg ) +{ + rc_t rc = kOkRC; return rc; diff --git a/cwMidiFileDev.h b/cwMidiFileDev.h index f680ef1..9973a06 100644 --- a/cwMidiFileDev.h +++ b/cwMidiFileDev.h @@ -2,37 +2,99 @@ namespace cw { namespace midi { - namespace file_dev + namespace device { - typedef handle handle_t; - - rc_t create( handle_t& hRef, const char* labelA[], unsigned max_file_cnt ); - rc_t destroy( handle_t& hRef ); - - unsigned file_count( handle_t h ); - - rc_t open_midi_file( handle_t h, unsigned file_idx, const char* fname ); - - rc_t seek_to_event( handle_t h, unsigned file_idx, unsigned msg_idx ); - - rc_t start( handle_t h ); - rc_t stop( handle_t h ); - - rc_t enable_file( handle_t h, unsigned file_idx ); - rc_t disable_file( handle_t h,unsigned file_idx ); - - int file_descriptor( handle_t h ); - - typedef struct msg_str + namespace file_dev { - const file::trackMsg_t* msg; - unsigned file_idx; - } msg_t; - - rc_t read( handle_t h, msg_t* buf, unsigned buf_msg_cnt, unsigned& actual_msg_cnt_ref ); + + typedef handle handle_t; - rc_t test( const object_t* cfg ); - + // + // Count of labels determines the count of ports. + // Note that port indexes and file indexes are synonomous. + rc_t create( handle_t& hRef, + cbFunc_t cbFunc, + void* cbArg, + unsigned baseDevIdx, // device index assigned to packet_t.devIndex in callbacks + const char* labelA[], // labelA[ max_file_cnt ] assigns a textual label to each port. + unsigned max_file_cnt, + const char* dev_name = "file_dev", // name of the file device + unsigned read_ahead_micros = 3000); // exec() will xmit events up to 'read_ahead_micros' after the current time + rc_t destroy( handle_t& hRef ); + + // Return true if at least one enabled file exists, otherwise return false. + bool is_active( handle_t h ); + + // Returns create(...,max_file_cnt...) + unsigned file_count( handle_t h ); + + // Assign a MIDI file to an input port. + rc_t open_midi_file( handle_t h, unsigned file_idx, const char* fname ); + rc_t load_messages( handle_t h, unsigned file_idx, const msg_t* msgA, unsigned msgN ); + + // Enable and disble the output of the specified file/port. + rc_t enable_file( handle_t h, unsigned file_idx, bool enableFl ); + rc_t enable_file( handle_t h, unsigned file_idx ); + rc_t disable_file( handle_t h,unsigned file_idx ); + + + // Device count: Always 1. + unsigned count( handle_t h ); + + // Device name as set in create() + const char* name( handle_t h, unsigned devIdx ); + + // Returns 0 if deviceName == name() else kInvalidIdx + unsigned nameToIndex(handle_t h, const char* deviceName); + + // The count of ports is determined by count of labels in create(...,labelA[],...). + unsigned portCount( handle_t h, unsigned devIdx, unsigned flags ); + + // Port name are provided by create(...,labelA[],...) + // Port indexes and file indexes are the same. + const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ); + unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName ); + rc_t portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl ); + + + typedef struct exec_result_str + { + unsigned next_msg_wait_micros; // microseconds before the next file msg must be transmitted + unsigned xmit_cnt; // count of msg's sent to callback during this exec + bool eof_fl; // true if there are no more file msg's to transmit + + } exec_result_t; + + + // Set the next msg to be returned. + rc_t seek_to_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx ); + rc_t set_end_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx ); + + // Seek to the start of the file or to the last msg_idx set by seek_to_event(). + rc_t rewind( handle_t h ); + + + // Delay the first MIDI msg by 'start_delay_micros'. + rc_t set_start_delay( handle_t h, unsigned start_delay_micros ); + + + + // Callback create(...,cbFunc,...) with msg's whose time has expired and return the + // time delay prior to the next message. + exec_result_t exec( handle_t h, unsigned long long elapsed_micros ); + + rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 ); + rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ); + + + // Reset the latency measurement process. + void latency_measure_reset(handle_t h); + latency_meas_result_t latency_measure_result(handle_t h); + + void report( handle_t h, textBuf::handle_t tbH ); + + rc_t test( const object_t* cfg ); + } } } }