libcm is a C development framework with an emphasis on audio signal processing applications.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cmMidiFilePlay.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. #include <sys/time.h> // gettimeofday()
  2. #include <unistd.h> // usleep()
  3. //#include <time.h> // clock_gettime()
  4. #include "cmPrefix.h"
  5. #include "cmGlobal.h"
  6. #include "cmRpt.h"
  7. #include "cmErr.h"
  8. #include "cmCtx.h"
  9. #include "cmMem.h"
  10. #include "cmMallocDebug.h"
  11. #include "cmFile.h"
  12. #include "cmMidi.h"
  13. #include "cmMidiPort.h"
  14. #include "cmMidiFile.h"
  15. #include "cmMidiFilePlay.h"
  16. #ifdef OS_OSX
  17. #include "osx/clock_gettime_stub.h"
  18. #endif
  19. #ifdef OS_LINUX
  20. #include <time.h> // clock_gettime()
  21. #endif
  22. typedef struct
  23. {
  24. cmErr_t err;
  25. cmCtx_t ctx;
  26. cmMfpCallback_t cbFunc;
  27. void* userCbPtr;
  28. void* printDataPtr;
  29. unsigned memBlockByteCnt;
  30. cmMidiFileH_t mfH; // midi file handle
  31. bool closeFileFl; // true mfH should be closed when this midi file player is closed
  32. unsigned ticksPerQN; // global for file
  33. unsigned microsPerTick; // set via tempo
  34. unsigned etime; // usecs elapsed since transmitting prev msg
  35. unsigned mtime; // usecs to wait before transmitting next msg
  36. unsigned msgN; // count of pointers in msgV[]
  37. unsigned msgIdx; // index into msgV[] of next msg to transmit
  38. const cmMidiTrackMsg_t** msgV; // array of msg pointers
  39. } cmMfp_t;
  40. cmMfpH_t cmMfpNullHandle = cmSTATIC_NULL_HANDLE;
  41. #define _cmMfpError( mfp, rc ) _cmMfpOnError(mfp, rc, __LINE__,__FILE__,__FUNCTION__ )
  42. // note: mfp may be NULL
  43. cmMfpRC_t _cmMfpOnError( cmMfp_t* mfp, cmMfpRC_t rc, int line, const char* fn, const char* func )
  44. {
  45. return cmErrMsg(&mfp->err,rc,"rc:%i %i %s %s\n",rc,line,func,fn);
  46. }
  47. cmMfp_t* _cmMfpHandleToPtr( cmMfpH_t h )
  48. {
  49. cmMfp_t* p = (cmMfp_t*)h.h;
  50. assert(p != NULL);
  51. return p;
  52. }
  53. void _cmMfpUpdateMicrosPerTick( cmMfp_t* mfp, unsigned microsPerQN )
  54. {
  55. mfp->microsPerTick = microsPerQN / mfp->ticksPerQN;
  56. printf("microsPerTick: %i bpm:%i ticksPerQN:%i\n", mfp->microsPerTick,microsPerQN,mfp->ticksPerQN);
  57. }
  58. cmMfpRC_t cmMfpCreate( cmMfpH_t* hp, cmMfpCallback_t cbFunc, void* userCbPtr, cmCtx_t* ctx )
  59. {
  60. cmMfp_t* p = cmMemAllocZ( cmMfp_t, 1 );
  61. cmErrSetup(&p->err,&ctx->rpt,"MIDI File Player");
  62. p->ctx = *ctx;
  63. p->cbFunc = cbFunc;
  64. p->userCbPtr = userCbPtr;
  65. p->mfH.h = NULL;
  66. p->closeFileFl = false;
  67. p->ticksPerQN = 0;
  68. p->microsPerTick = 0;
  69. p->etime = 0;
  70. p->msgN = 0;
  71. p->msgV = NULL;
  72. p->msgIdx = 0;
  73. hp->h = p;
  74. return kOkMfpRC;
  75. }
  76. cmMfpRC_t cmMfpDestroy( cmMfpH_t* hp )
  77. {
  78. if( hp == NULL )
  79. return kOkMfpRC;
  80. if( cmMfpIsValid(*hp) )
  81. {
  82. cmMfp_t* p = _cmMfpHandleToPtr(*hp);
  83. if( cmMidiFileIsNull(p->mfH)==false && p->closeFileFl==true )
  84. cmMidiFileClose(&p->mfH);
  85. cmMemFree(p);
  86. hp->h = NULL;
  87. }
  88. return kOkMfpRC;
  89. }
  90. bool cmMfpIsValid( cmMfpH_t h )
  91. { return h.h != NULL; }
  92. cmMfpRC_t cmMfpLoadFile( cmMfpH_t h, const char* fn )
  93. {
  94. cmMfpRC_t rc = kOkMfpRC;
  95. cmMfp_t* p = _cmMfpHandleToPtr(h);
  96. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  97. if((rc = cmMidiFileOpen( fn, &mfH, &p->ctx )) != kOkMfRC )
  98. return _cmMfpError(p,kFileOpenFailMfpRC);
  99. if((rc= cmMfpLoadHandle( h, mfH )) == kOkMfpRC )
  100. p->closeFileFl = true;
  101. return rc;
  102. }
  103. cmMfpRC_t cmMfpLoadHandle( cmMfpH_t h, cmMidiFileH_t mfH )
  104. {
  105. cmMfp_t* p = _cmMfpHandleToPtr(h);
  106. // if a file has already been assigned to this player
  107. if( (cmMidiFileIsNull(p->mfH) == false) && p->closeFileFl)
  108. {
  109. // close the existing file
  110. cmMidiFileClose(&p->mfH);
  111. }
  112. // get the count of msg's in the new midi file
  113. if((p->msgN = cmMidiFileMsgCount(mfH)) == cmInvalidCnt )
  114. return _cmMfpError(p,kInvalidFileMfpRC);
  115. // get a pointer to the first mesage
  116. if((p->msgV = cmMidiFileMsgArray(mfH)) == NULL )
  117. return _cmMfpError(p,kInvalidFileMfpRC);
  118. // get the count of ticks per qn
  119. if((p->ticksPerQN = cmMidiFileTicksPerQN( mfH )) == 0 )
  120. return _cmMfpError(p,kSmpteTickNotImplMfpRC);
  121. // set the initial tempo to 120
  122. _cmMfpUpdateMicrosPerTick(p,60000000/120);
  123. p->msgIdx = 0;
  124. p->mfH = mfH;
  125. p->etime = 0;
  126. p->mtime = 0;
  127. p->closeFileFl= false;
  128. //if( p->msgIdx > 0 )
  129. // p->mtime = p->msgV[0]->tick * p->microsPerTick;
  130. return kOkMfpRC;
  131. }
  132. cmMfpRC_t cmMfpSeek( cmMfpH_t h, unsigned offsUsecs )
  133. {
  134. cmMfp_t* p = _cmMfpHandleToPtr(h);
  135. unsigned msgOffsUsecs = 0;
  136. unsigned msgIdx;
  137. unsigned newMicrosPerTick;
  138. // if the requested offset is past the end of the file then return EOF
  139. if((msgIdx = cmMidiFileSeekUsecs( p->mfH, offsUsecs, &msgOffsUsecs, &newMicrosPerTick )) == cmInvalidIdx )
  140. {
  141. p->msgIdx = p->msgN;
  142. return _cmMfpError(p,kEndOfFileMfpRC);
  143. }
  144. if( msgIdx < p->msgIdx )
  145. p->msgIdx = 0;
  146. p->mtime = msgOffsUsecs;
  147. p->etime = 0;
  148. p->microsPerTick = newMicrosPerTick;
  149. p->msgIdx = msgIdx;
  150. assert(p->mtime >= 0);
  151. return kOkMfpRC;
  152. }
  153. // p 0 1 n 2
  154. // v v v v v
  155. // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  156. // 012345678901234567890123456780
  157. // 0 1 2
  158. //
  159. // p = 3 = prev msg sent
  160. // n = 19 = next msg to send
  161. // 0 = 6 = call to cmMfpClock()
  162. // 1 = 12 = call to cmMfpClock()
  163. // 2 = 22 = call to cmMfpClock()
  164. //
  165. // dusecs etime mtime
  166. // 0 n/a 3 13
  167. // 1 6 9 7
  168. // 2 10 19 -3
  169. //
  170. cmMfpRC_t cmMfpClock( cmMfpH_t h, unsigned dusecs )
  171. {
  172. cmMfp_t* p = _cmMfpHandleToPtr(h);
  173. if( p->msgIdx >= p->msgN )
  174. return kEndOfFileMfpRC;
  175. // get a pointer to the next msg to send
  176. const cmMidiTrackMsg_t* mp = p->msgV[p->msgIdx];
  177. // p->etime is the interval of time between when the last msg was
  178. // sent and the end of the time window for this mfpClock() cycle
  179. p->etime += dusecs;
  180. //printf("init e:%i d:%i\n",p->etime, p->mtime);
  181. // if the elapsed time (etime) since the last msg is greater or equal
  182. // to the delta time to the next msg (mtime)
  183. while( p->etime >= p->mtime )
  184. {
  185. //printf("e:%i d:%i\n",p->etime, p->mtime);
  186. if( mp->status == kMetaStId && mp->metaId == kTempoMdId )
  187. _cmMfpUpdateMicrosPerTick(p,mp->u.iVal );
  188. p->cbFunc( p->userCbPtr, p->mtime, mp );
  189. ++(p->msgIdx);
  190. if( p->msgIdx >= p->msgN )
  191. break;
  192. // get the next msg to send
  193. mp = p->msgV[p->msgIdx];
  194. // we probably went past the actual mtime - so update etime
  195. // with the delta usecs from the msg just sent and the current time
  196. p->etime -= p->mtime;
  197. // calc the delta usecs from the message just sent to the next msg to send
  198. //p->mtime = (mp->tick - p->msgV[p->msgIdx-1]->tick) * p->microsPerTick;
  199. p->mtime = mp->dtick * p->microsPerTick;
  200. }
  201. return p->msgIdx >= p->msgN ? kEndOfFileMfpRC : kOkMfpRC;
  202. }
  203. void mfpPrint( void* userDataPtr, const char* fmt, va_list vl )
  204. {
  205. vprintf(fmt,vl);
  206. }
  207. // this assumes that the seconds have been normalized to a recent start time
  208. // so as to avoid overflow
  209. unsigned _cmMfpElapsedMicroSecs( const struct timespec* t0, const struct timespec* t1 )
  210. {
  211. // convert seconds to usecs
  212. long u0 = t0->tv_sec * 1000000;
  213. long u1 = t1->tv_sec * 1000000;
  214. // convert nanoseconds to usec
  215. u0 += t0->tv_nsec / 1000;
  216. u1 += t1->tv_nsec / 1000;
  217. // take diff between t1 and t0
  218. return u1 - u0;
  219. }
  220. void _cmMfpTestTimer()
  221. {
  222. useconds_t suspendUsecs = 15 * 1000;
  223. struct timespec t0,t1,t2;
  224. unsigned accum = 0;
  225. unsigned i;
  226. unsigned n = 4000;
  227. // t0 will be the base time which all other times will be
  228. // set relative to.
  229. clock_gettime(CLOCK_REALTIME,&t0);
  230. t2 = t0;
  231. t2.tv_sec = 0;
  232. for(i=0; i<n; ++i)
  233. {
  234. usleep(suspendUsecs);
  235. clock_gettime(CLOCK_REALTIME,&t1);
  236. t1.tv_sec -= t0.tv_sec;
  237. unsigned d0usec = _cmMfpElapsedMicroSecs(&t0,&t1);
  238. unsigned d1usec = _cmMfpElapsedMicroSecs(&t2,&t1);
  239. accum += d1usec;
  240. if( i == n-1 )
  241. printf("%i %i %i\n",d0usec,d1usec,accum);
  242. t2 = t1;
  243. }
  244. }
  245. // midi file player callback test function
  246. void _cmMfpCallbackTest( void* userCbPtr, unsigned dmicros, const cmMidiTrackMsg_t* msgPtr )
  247. {
  248. if( kNoteOffMdId <= msgPtr->status && msgPtr->status <= kPbendMdId )
  249. cmMpDeviceSend( 0, 0, msgPtr->status+msgPtr->u.chMsgPtr->ch, msgPtr->u.chMsgPtr->d0,msgPtr->u.chMsgPtr->d1);
  250. //printf("%i 0x%x 0x%x %i\n",msgPtr->tick,msgPtr->status,msgPtr->metaId,msgPtr->trkIdx);
  251. }
  252. // midi port callback test function
  253. void _cmMpCallbackTest( const cmMidiPacket_t* pktArray, unsigned pktCnt )
  254. {}
  255. cmMfpRC_t cmMfpTest( const char* fn, cmCtx_t* ctx )
  256. {
  257. cmMfpH_t mfpH = cmMfpNullHandle;
  258. cmMfpRC_t rc;
  259. useconds_t suspendUsecs = 15 * 1000;
  260. struct timespec t0,t1,base;
  261. //unsigned i;
  262. //unsigned n = 4000;
  263. unsigned mdParserBufByteCnt = 1024;
  264. printf("Initializing MIDI Devices...\n");
  265. cmMpInitialize( _cmMpCallbackTest, NULL, mdParserBufByteCnt,"app", &ctx->rpt );
  266. //mdReport();
  267. printf("Creating Player...\n");
  268. if((rc = cmMfpCreate( &mfpH, _cmMfpCallbackTest, NULL, ctx )) != kOkMfpRC )
  269. return rc;
  270. printf("Loading MIDI file...\n");
  271. if((rc = cmMfpLoadFile( mfpH, fn )) != kOkMfpRC )
  272. goto errLabel;
  273. if((rc = cmMfpSeek( mfpH, 60 * 1000000 )) != kOkMfpRC )
  274. goto errLabel;
  275. clock_gettime(CLOCK_REALTIME,&base);
  276. t0 = base;
  277. t0.tv_sec = 0;
  278. //for(i=0; i<n; ++i)
  279. while(rc != kEndOfFileMfpRC)
  280. {
  281. usleep(suspendUsecs);
  282. clock_gettime(CLOCK_REALTIME,&t1);
  283. t1.tv_sec -= base.tv_sec;
  284. unsigned dusecs = _cmMfpElapsedMicroSecs(&t0,&t1);
  285. rc = cmMfpClock( mfpH, dusecs );
  286. //printf("%i %i\n",dusecs,rc);
  287. t0 = t1;
  288. }
  289. errLabel:
  290. cmMfpDestroy(&mfpH);
  291. cmMpFinalize();
  292. return rc;
  293. }
  294. //------------------------------------------------------------------------------------------------------------
  295. #include "cmFloatTypes.h"
  296. #include "cmComplexTypes.h"
  297. #include "cmLinkedHeap.h"
  298. #include "cmSymTbl.h"
  299. #include "cmAudioFile.h"
  300. #include "cmProcObj.h"
  301. #include "cmProcTemplateMain.h"
  302. #include "cmVectOps.h"
  303. #include "cmProc.h"
  304. #include "cmProc2.h"
  305. enum
  306. {
  307. kOkMfptRC = cmOkRC,
  308. kMfpFailMfptRC,
  309. kAudioFileFailMfptRC,
  310. kProcObjFailMfptRC
  311. };
  312. typedef struct
  313. {
  314. cmErr_t* err;
  315. cmMidiSynth* msp;
  316. } _cmMfpTest2CbData_t;
  317. // Called by the MIDI file player to send a msg to the MIDI synth.
  318. void _cmMfpCb( void* userCbPtr, unsigned dmicros, const cmMidiTrackMsg_t* msgPtr )
  319. {
  320. if( kNoteOffMdId <= msgPtr->status && msgPtr->status <= kPbendMdId )
  321. {
  322. cmMidiPacket_t pkt;
  323. cmMidiMsg msg;
  324. _cmMfpTest2CbData_t* d = (_cmMfpTest2CbData_t*)userCbPtr;
  325. msg.deltaUs = dmicros;
  326. msg.status = msgPtr->status + msgPtr->u.chMsgPtr->ch;
  327. msg.d0 = msgPtr->u.chMsgPtr->d0;
  328. msg.d1 = msgPtr->u.chMsgPtr->d1;
  329. pkt.cbDataPtr = NULL;
  330. pkt.devIdx = cmInvalidIdx;
  331. pkt.portIdx = cmInvalidIdx;
  332. pkt.msgArray = &msg;
  333. pkt.sysExMsg = NULL;
  334. pkt.msgCnt = 1;
  335. if( cmMidiSynthOnMidi( d->msp, &pkt, 1 ) != cmOkRC )
  336. cmErrMsg(d->err,kProcObjFailMfptRC,"Synth. MIDI receive failed.");
  337. }
  338. }
  339. // Called by the MIDI synth to send a msg to the voice bank.
  340. int _cmMidiSynthCb( struct cmMidiVoice_str* voicePtr, unsigned sel, cmSample_t* outChArray[], unsigned outChCnt )
  341. {
  342. return cmWtVoiceBankExec( ((cmWtVoiceBank*)voicePtr->pgm.cbDataPtr), voicePtr, sel, outChArray, outChCnt );
  343. }
  344. // BUG BUG BUG: THIS FUNCTION IS NOT TESTED!!!!!
  345. cmRC_t cmMfpTest2( const char* midiFn, const char* audioFn, cmCtx_t* ctx )
  346. {
  347. cmRC_t rc = kOkMfptRC;
  348. cmMfpH_t mfpH = cmMfpNullHandle;
  349. _cmMfpTest2CbData_t cbData;
  350. cmErr_t err;
  351. cmAudioFileH_t afH = cmNullAudioFileH;
  352. cmRC_t afRC = kOkAfRC;
  353. double afSrate = 44100;
  354. unsigned afBits = 16;
  355. unsigned afChCnt = 1;
  356. cmCtx* cctx;
  357. cmMidiSynth* msp;
  358. cmWtVoiceBank* vbp;
  359. unsigned msPgmCnt = 127;
  360. cmMidiSynthPgm msPgmArray[ msPgmCnt ];
  361. unsigned msVoiceCnt = 36;
  362. unsigned procSmpCnt = 64;
  363. unsigned i;
  364. cmErrSetup(&err,&ctx->rpt,"MFP Test 2");
  365. // create the MIDI file player
  366. if( cmMfpCreate(&mfpH, _cmMfpCb, &cbData, ctx ) != kOkMfpRC )
  367. return cmErrMsg(&err,kMfpFailMfptRC,"MIDI file player create failed.");
  368. // create an output audio file
  369. if( cmAudioFileIsValid( afH = cmAudioFileNewCreate(audioFn, afSrate, afBits, afChCnt, &afRC, &ctx->rpt))==false)
  370. {
  371. rc = cmErrMsg(&err,kAudioFileFailMfptRC,"The audio file create failed.");
  372. goto errLabel;
  373. }
  374. // load the midi file into the player
  375. if( cmMfpLoadFile( mfpH, midiFn ) != kOkMfpRC )
  376. {
  377. rc = cmErrMsg(&err,kMfpFailMfptRC,"MIDI file load failed.");
  378. goto errLabel;
  379. }
  380. // create the proc obj context
  381. if((cctx = cmCtxAlloc(NULL, &ctx->rpt, cmLHeapNullHandle, cmSymTblNullHandle )) == NULL)
  382. {
  383. rc = cmErrMsg(&err,kProcObjFailMfptRC,"cmCtx allocate failed.");
  384. goto errLabel;
  385. }
  386. // create the voice bank
  387. if((vbp = cmWtVoiceBankAlloc(cctx, NULL, afSrate, procSmpCnt, msVoiceCnt, afChCnt )) == NULL)
  388. {
  389. rc = cmErrMsg(&err,kProcObjFailMfptRC,"WT voice bank allocate failed.");
  390. goto errLabel;
  391. }
  392. // a MIDI synth
  393. if((msp = cmMidiSynthAlloc(cctx, NULL, msPgmArray, msPgmCnt, msVoiceCnt, procSmpCnt, afChCnt, afSrate )) == NULL )
  394. {
  395. rc = cmErrMsg(&err,kProcObjFailMfptRC,"MIDI synth allocate failed.");
  396. goto errLabel;
  397. }
  398. cbData.msp = msp;
  399. cbData.err = &err;
  400. // load all of the the MIDI pgm recds with the same settings
  401. for(i=0; i<msPgmCnt; ++i)
  402. {
  403. msPgmArray[i].pgm = i;
  404. msPgmArray[i].cbPtr = _cmMidiSynthCb; // Call this function to update voices using this pgm
  405. msPgmArray[i].cbDataPtr = vbp; // Voice bank containing the voice states.
  406. }
  407. unsigned dusecs = floor((double)procSmpCnt * 1000000. / afSrate);
  408. while(rc != kEndOfFileMfpRC)
  409. {
  410. // update the MFP's current time and call _cmMfpCb() for MIDI msgs whose time has elapsed
  411. rc = cmMfpClock( mfpH, dusecs );
  412. // check for MFP errors
  413. if(rc!=kOkMfpRC && rc!=kEndOfFileMfpRC)
  414. {
  415. cmErrMsg(&err,kMfpFailMfptRC,"MIDI file player exec failed.");
  416. goto errLabel;
  417. }
  418. // generate audio based on the current state of the synth voices
  419. if( cmMidiSynthExec(msp, NULL, 0 ) != cmOkRC )
  420. {
  421. cmErrMsg(&err,kProcObjFailMfptRC,"MIDI synth exec. failed.");
  422. goto errLabel;
  423. }
  424. // write the last frame of synth. generated audio to the output file
  425. if( cmAudioFileWriteSample(afH, procSmpCnt, msp->outChCnt, msp->outChArray ) != kOkAfRC )
  426. {
  427. cmErrMsg(&err,kProcObjFailMfptRC,"Audio file write failed.");
  428. goto errLabel;
  429. }
  430. }
  431. errLabel:
  432. if( cmMidiSynthFree(&msp) != cmOkRC )
  433. cmErrMsg(&err,kProcObjFailMfptRC,"MIDI synth. free failed.");
  434. if( cmWtVoiceBankFree(&vbp) != cmOkRC )
  435. cmErrMsg(&err,kProcObjFailMfptRC,"WT voice free failed.");
  436. if( cmCtxFree(&cctx) != cmOkRC )
  437. cmErrMsg(&err,kProcObjFailMfptRC,"cmCtx free failed.");
  438. if( cmAudioFileDelete(&afH) )
  439. cmErrMsg(&err,kAudioFileFailMfptRC,"The audio file close failed.");
  440. if( cmMfpDestroy(&mfpH) != kOkMfpRC )
  441. cmErrMsg(&err,kMfpFailMfptRC,"MIDI file player destroy failed.");
  442. return rc;
  443. }