diff --git a/Makefile.am b/Makefile.am index c40a919..d97f451 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,8 +25,8 @@ libcwSRC += src/libcw/cwThread.cpp src/libcw/cwMutex.cpp src/libcw/cwThreadMach libcwHDR += src/libcw/cwMpScNbQueue.h src/libcw/cwSpScBuf.h src/libcw/cwSpScQueueTmpl.h libcwSRC += src/libcw/cwSpScBuf.cpp src/libcw/cwSpScQueueTmpl.cpp -libcwHDR += src/libcw/cwAudioFile.h src/libcw/cwMidiFile.h -libcwSRC += src/libcw/cwAudioFile.cpp src/libcw/cwMidiFile.cpp +libcwHDR += src/libcw/cwAudioFile.h src/libcw/cwMidiFile.h src/libcw/cwMidiFileDev.h +libcwSRC += src/libcw/cwAudioFile.cpp src/libcw/cwMidiFile.cpp src/libcw/cwMidiFileDev.cpp libcwHDR += src/libcw/cwAudioFileOps.h src/libcw/cwAudioTransforms.h src/libcw/cwDspTransforms.h src/libcw/cwAudioFileProc.h src/libcw/cwPvAudioFileProc.h libcwSRC += src/libcw/cwAudioFileOps.cpp src/libcw/cwAudioTransforms.cpp src/libcw/cwDspTransforms.cpp src/libcw/cwAudioFileProc.cpp src/libcw/cwPvAudioFileProc.cpp diff --git a/cwMidiFileDev.cpp b/cwMidiFileDev.cpp new file mode 100644 index 0000000..7bcd96d --- /dev/null +++ b/cwMidiFileDev.cpp @@ -0,0 +1,501 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwTime.h" +#include "cwFile.h" +#include "cwObject.h" +#include "cwFileSys.h" +#include "cwThread.h" +#include "cwMidi.h" +#include "cwMidiDecls.h" +#include "cwMidiFile.h" +#include "cwMidiFileDev.h" +#include + +namespace cw +{ + namespace midi + { + namespace file_dev + { + + typedef struct file_str + { + 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 ) + { + rc = cwLogError(rc,"MIDI file close failed on MIDI file device index %i.",file_idx); + goto errLabel; + } + + errLabel: + return rc; + } + + rc_t _destroy( file_dev_t* p ) + { + rc_t rc = kOkRC; + + if((rc = destroy(p->threadH)) != kOkRC ) + { + 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 ); + + mem::release(p->fileA[i].label); + } + + 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) + { + if( p->fileA[i].mfH.isValid() ) + { + unsigned fileMsgN = msgCount(p->fileA[i].mfH); + const file::trackMsg_t** fileMsgPtrA = msgArray(p->fileA[i].mfH); + + for(unsigned j=0; jmsgA[k].msg = fileMsgPtrA[j]; + p->msgA[k].file_idx = i; + ++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; + + 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); + } + + sleepUs(std::min(sleep_micros,max_sleep_micros)); + + return true; + } + } + } +} + + +cw::rc_t cw::midi::file_dev::create( handle_t& hRef, const char* labelA[], unsigned max_file_cnt ) +{ + rc_t rc; + int sysRC = 0; + + if((rc = destroy(hRef)) != kOkRC ) + return rc; + + file_dev_t* p = mem::allocZ(); + + 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; + + for(unsigned i=0; ifileN; ++i) + { + if( labelA[i] != nullptr ) + { + p->fileA[i].label = mem::duplStr(labelA[i]); + } + else + { + rc = cwLogError(kInvalidArgRC,"Count of MIDI file device labels must match the max file count."); + goto errLabel; + } + } + + 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: + if(rc != kOkRC ) + _destroy(p); + return rc; +} + +cw::rc_t cw::midi::file_dev::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + if(!hRef.isValid() ) + return rc; + + file_dev_t* p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file device destroy failed."); + goto errLabel; + } + + hRef.clear(); + +errLabel: + return rc; +} + +unsigned cw::midi::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 ) +{ + 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; +} + +cw::rc_t cw::midi::file_dev::start( handle_t h ) +{ + 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; +} + +cw::rc_t cw::midi::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 ) +{ return _enable_file(h,file_idx,false); } + +int cw::midi::file_dev::file_descriptor( handle_t h ) +{ + file_dev_t* p = _handleToPtr(h); + return p->pipefdA[0]; +} + +cw::rc_t cw::midi::file_dev::read( handle_t h, msg_t* buf, unsigned buf_msg_cnt, unsigned& actual_msg_cnt_ref ) +{ + 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 + + actual_msg_cnt_ref = 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; +} + +cw::rc_t cw::midi::file_dev::test( const object_t* cfg ) +{ + 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; + + if((rc = cfg->getv("fname",fname)) != kOkRC || fname == nullptr) + { + cwLogError(rc,"MIDI file dev test arg. parse failed."); + goto errLabel; + } + + 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."); + + + + return rc; + +} diff --git a/cwMidiFileDev.h b/cwMidiFileDev.h new file mode 100644 index 0000000..f680ef1 --- /dev/null +++ b/cwMidiFileDev.h @@ -0,0 +1,38 @@ +namespace cw +{ + namespace midi + { + namespace file_dev + { + 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 + { + 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 ); + + rc_t test( const object_t* cfg ); + + } + } +}