libcm is a C development framework with an emphasis on audio signal processing applications.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

cmMidiFilePlay.c 15KB

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