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.

cmSyncRecd.c 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. #include "cmGlobal.h"
  2. #include "cmFloatTypes.h"
  3. #include "cmRpt.h"
  4. #include "cmErr.h"
  5. #include "cmCtx.h"
  6. #include "cmMem.h"
  7. #include "cmMallocDebug.h"
  8. #include "cmTime.h"
  9. #include "cmFile.h"
  10. #include "cmAudioFile.h"
  11. #include "cmSyncRecd.h"
  12. #include "cmVectOpsTemplateMain.h"
  13. #include "cmMidi.h"
  14. typedef enum
  15. {
  16. kInvalidSrId,
  17. kMidiSrId,
  18. kAudioSrId
  19. } cmSrTypeId_t;
  20. typedef struct cmSrMidi_str
  21. {
  22. cmTimeSpec_t timestamp;
  23. unsigned status;
  24. unsigned d0;
  25. unsigned d1;
  26. } cmSrMidi_t;
  27. typedef struct cmSrAudio_str
  28. {
  29. cmTimeSpec_t timestamp;
  30. unsigned smpIdx;
  31. } cmSrAudio_t;
  32. typedef struct cmSrRecd_str
  33. {
  34. cmSrTypeId_t tid;
  35. union
  36. {
  37. cmSrMidi_t m;
  38. cmSrAudio_t a;
  39. } u;
  40. } cmSrRecd_t;
  41. enum
  42. {
  43. kReadSrFl = 0x01, // This is a read (not a write) file
  44. kFileUUSrId = 0xf00d
  45. };
  46. typedef struct cmSr_str
  47. {
  48. cmErr_t err;
  49. cmFileH_t fH;
  50. cmAudioFileH_t afH;
  51. unsigned flags; // See kXXXSrFl
  52. cmSrRecd_t* cache; // cache[cn]
  53. cmSrAudio_t* map; //
  54. unsigned cn; // count of records in cache[].
  55. unsigned ci; // next cache recd index
  56. unsigned fn; // count of recds written to file
  57. long offs;
  58. cmAudioFileInfo_t afInfo;
  59. } cmSr_t;
  60. cmSyncRecdH_t cmSyncRecdNullHandle = cmSTATIC_NULL_HANDLE;
  61. cmSr_t* _cmSrHtoP( cmSyncRecdH_t h )
  62. {
  63. cmSr_t* p = (cmSr_t*)h.h;
  64. assert(p!=NULL);
  65. return p;
  66. }
  67. cmSyRC_t _cmSrWriteCache( cmSr_t* p )
  68. {
  69. if( cmFileWrite(p->fH,p->cache,p->ci * sizeof(cmSrRecd_t)) != kOkFileRC )
  70. return cmErrMsg(&p->err,kFileFailSyRC,"File write failed.");
  71. p->fn += p->ci;
  72. p->ci = 0;
  73. return kOkSyRC;
  74. }
  75. cmSyRC_t _cmSrFinal( cmSr_t* p )
  76. {
  77. cmSyRC_t rc = kOkSyRC;
  78. // write any remaining cache records
  79. if( cmIsFlag(p->flags,kReadSrFl) == false )
  80. {
  81. if((rc = _cmSrWriteCache(p)) == kOkSyRC )
  82. {
  83. if(cmFileSeek(p->fH,kBeginFileFl,p->offs) != kOkFileRC )
  84. rc = cmErrMsg(&p->err,kFileFailSyRC, "File seek fail on file offset positioning.");
  85. else
  86. if(cmFileWriteUInt(p->fH,&p->fn,1) != kOkFileRC )
  87. rc = cmErrMsg(&p->err,kFileFailSyRC, "File write failed on record count.");
  88. }
  89. }
  90. // release the audio file object
  91. if( cmAudioFileIsValid(p->afH) )
  92. if( cmAudioFileDelete(&p->afH) != kOkAfRC )
  93. return cmErrMsg(&p->err,kAudioFileFailSyRC,"Audio file object delete failed.");
  94. // release the sync-recd file object
  95. if( cmFileIsValid(p->fH) )
  96. if( cmFileClose(&p->fH) != kOkFileRC )
  97. return cmErrMsg(&p->err,kFileFailSyRC,"File close failed.");
  98. cmMemFree(p->cache);
  99. cmMemFree(p->map);
  100. cmMemFree(p);
  101. return rc;
  102. }
  103. cmSr_t* _cmSrAlloc( cmCtx_t* ctx, unsigned flags )
  104. {
  105. cmSr_t* p = cmMemAllocZ(cmSr_t,1);
  106. cmErrSetup(&p->err,&ctx->rpt,"SyncRecd");
  107. p->cn = 2048;
  108. p->cache = cmMemAllocZ(cmSrRecd_t,p->cn);
  109. return p;
  110. }
  111. cmSyRC_t cmSyncRecdCreate( cmCtx_t* ctx, cmSyncRecdH_t* hp, const cmChar_t* srFn, const cmChar_t* audioFn, double srate, unsigned chCnt, unsigned bits )
  112. {
  113. cmSyRC_t rc = kOkSyRC;
  114. cmRC_t afRC = kOkAfRC;
  115. assert( audioFn != NULL );
  116. if((rc = cmSyncRecdFinal(hp)) != kOkSyRC )
  117. return rc;
  118. cmSr_t* p = _cmSrAlloc(ctx,0);
  119. if( cmFileOpen(&p->fH,srFn,kWriteFileFl,&ctx->rpt) != kOkFileRC )
  120. {
  121. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to create the sync-recd file '%s'.",cmStringNullGuard(srFn));
  122. goto errLabel;
  123. }
  124. if( cmAudioFileIsValid(p->afH = cmAudioFileNewCreate(audioFn,srate,bits,chCnt,&afRC,&ctx->rpt))==false)
  125. {
  126. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Unable to create the sync-recd audio file '%s'.",cmStringNullGuard(audioFn));
  127. goto errLabel;
  128. }
  129. unsigned fileUUId = kFileUUSrId;
  130. unsigned audioFnCnt = strlen(audioFn)+1;
  131. if( cmFileWriteUInt(p->fH,&fileUUId,1) != kOkFileRC )
  132. {
  133. rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on UUID.");
  134. goto errLabel;
  135. }
  136. if( cmFileWriteUInt(p->fH,&audioFnCnt,1) != kOkFileRC )
  137. {
  138. rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on audio file length write count.");
  139. goto errLabel;
  140. }
  141. if( cmFileWriteChar(p->fH,audioFn,audioFnCnt) != kOkFileRC )
  142. {
  143. rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on audio file string.");
  144. goto errLabel;
  145. }
  146. if( cmFileTell(p->fH,&p->offs) != kOkFileRC )
  147. {
  148. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to determine file offset.");
  149. goto errLabel;
  150. }
  151. if( cmFileWriteUInt(p->fH,&p->fn,1) != kOkFileRC )
  152. {
  153. rc = cmErrMsg(&p->err,kFileFailSyRC,"File write failed on initial record count.");
  154. goto errLabel;
  155. }
  156. hp->h = p;
  157. errLabel:
  158. if( rc != kOkSyRC )
  159. _cmSrFinal(p);
  160. return rc;
  161. }
  162. cmSyRC_t cmSyncRecdOpen( cmCtx_t* ctx, cmSyncRecdH_t* hp, const cmChar_t* srFn )
  163. {
  164. cmSyRC_t rc = kOkSyRC;
  165. cmRC_t afRC = kOkAfRC;
  166. unsigned fileUUId = cmInvalidId;
  167. unsigned audioFnCnt = 0;
  168. cmChar_t* audioFn = NULL;
  169. unsigned i;
  170. unsigned acnt = 0;
  171. unsigned mcnt = 0;
  172. unsigned* tiV = NULL;
  173. if((rc = cmSyncRecdFinal(hp)) != kOkSyRC )
  174. return rc;
  175. cmSr_t* p = _cmSrAlloc(ctx,kReadSrFl);
  176. if( cmFileOpen(&p->fH,srFn,kReadFileFl,&ctx->rpt) != kOkFileRC )
  177. {
  178. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to open the sync-recd file '%s'.",cmStringNullGuard(srFn));
  179. goto errLabel;
  180. }
  181. if( cmFileReadUInt(p->fH,&fileUUId,1) != kOkFileRC )
  182. {
  183. rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on UUId.");
  184. goto errLabel;
  185. }
  186. if( cmFileReadUInt(p->fH,&audioFnCnt,1) != kOkFileRC )
  187. {
  188. rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on audio file name count.");
  189. goto errLabel;
  190. }
  191. audioFn = cmMemAllocZ(cmChar_t,audioFnCnt);
  192. if( cmFileReadChar(p->fH,audioFn,audioFnCnt) != kOkFileRC )
  193. {
  194. rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on audio file string.");
  195. goto errLabel;
  196. }
  197. if( cmFileReadUInt(p->fH,&p->fn,1) != kOkFileRC )
  198. {
  199. rc = cmErrMsg(&p->err,kFileFailSyRC,"File read failed on record count.");
  200. goto errLabel;
  201. }
  202. // store the file offset to the first recd
  203. if( cmFileTell(p->fH,&p->offs) != kOkFileRC )
  204. {
  205. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to determine the current file offset.");
  206. goto errLabel;
  207. }
  208. // read each file - and count the types
  209. for(i=0; i<p->fn; ++i)
  210. {
  211. cmSrRecd_t r;
  212. if( cmFileRead(p->fH,&r,sizeof(r)) != kOkFileRC )
  213. {
  214. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to read the record at index %i.");
  215. goto errLabel;
  216. }
  217. switch(r.tid)
  218. {
  219. case kMidiSrId:
  220. mcnt += 1;
  221. break;
  222. case kAudioSrId:
  223. acnt += 1;
  224. break;
  225. default:
  226. { assert(0); }
  227. }
  228. }
  229. printf("%i %i = %i\n",mcnt,acnt,p->fn);
  230. // rewind to the begining of the records
  231. if( cmFileSeek(p->fH,kBeginFileFl,p->offs) != kOkFileRC )
  232. {
  233. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to seek to first recd offset.");
  234. goto errLabel;
  235. }
  236. // allocate space to hold the MIDI records
  237. p->cn = mcnt;
  238. p->ci = 0;
  239. p->cache = cmMemResizeZ(cmSrRecd_t,p->cache,p->cn);
  240. p->map = cmMemAllocZ(cmSrAudio_t,p->cn);
  241. tiV = cmMemAllocZ(unsigned,p->cn);
  242. for(i=0; p->ci<p->cn && i<p->fn; ++i)
  243. {
  244. if( cmFileRead(p->fH,p->cache + p->ci,sizeof(cmSrRecd_t)) != kOkFileRC )
  245. {
  246. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to read the record at index %i.");
  247. goto errLabel;
  248. }
  249. if( p->cache[p->ci].tid == kMidiSrId )
  250. p->ci += 1;
  251. }
  252. // assert that all the MIDI records were read
  253. assert( p->ci == p->cn);
  254. // rewind to the first recd
  255. if( cmFileSeek(p->fH,kBeginFileFl,p->offs) != kOkFileRC )
  256. {
  257. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to seek to first recd offset.");
  258. goto errLabel;
  259. }
  260. // for each recd in the file
  261. for(i=0; i<p->fn; ++i)
  262. {
  263. cmSrRecd_t r;
  264. if( cmFileRead(p->fH,&r,sizeof(r)) != kOkFileRC )
  265. {
  266. rc = cmErrMsg(&p->err,kFileFailSyRC,"Unable to read the record at index %i.");
  267. goto errLabel;
  268. }
  269. // if this is an audio record
  270. if( r.tid == kAudioSrId )
  271. {
  272. unsigned j;
  273. // for each midi record
  274. for(j=0; j<p->cn; ++j)
  275. {
  276. // measure the time interval between this midi and this audio recd
  277. unsigned time_interval_micros = cmTimeAbsElapsedMicros(&r.u.a.timestamp,&p->cache[j].u.m.timestamp);
  278. // if the audio recd is closer to this midi recd than prior audio records ...
  279. if( time_interval_micros < tiV[j] || i==0 )
  280. {
  281. // ... then store the audio time stamp in the map
  282. tiV[j] = time_interval_micros;
  283. p->map[j].timestamp = r.u.a.timestamp;
  284. p->map[j].smpIdx = r.u.a.smpIdx;
  285. }
  286. }
  287. }
  288. }
  289. // open the audio file
  290. if( cmAudioFileIsValid(p->afH = cmAudioFileNewOpen(audioFn,&p->afInfo,&afRC,&ctx->rpt ))==false)
  291. {
  292. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Unable to open the sync-recd audio file '%s'.",cmStringNullGuard(audioFn));
  293. goto errLabel;
  294. }
  295. p->flags = cmSetFlag(p->flags,kReadSrFl);
  296. hp->h = p;
  297. errLabel:
  298. cmMemFree(tiV);
  299. cmMemFree(audioFn);
  300. if( rc != kOkSyRC )
  301. _cmSrFinal(p);
  302. return rc;
  303. }
  304. cmSyRC_t cmSyncRecdFinal( cmSyncRecdH_t* hp )
  305. {
  306. cmSyRC_t rc = kOkSyRC;
  307. if( hp==NULL || cmSyncRecdIsValid(*hp)==false)
  308. return rc;
  309. cmSr_t* p = _cmSrHtoP(*hp);
  310. if((rc = _cmSrFinal(p)) != kOkSyRC )
  311. return rc;
  312. hp->h = NULL;
  313. return rc;
  314. }
  315. bool cmSyncRecdIsValid( cmSyncRecdH_t h )
  316. { return h.h != NULL; }
  317. cmSyRC_t cmSyncRecdMidiWrite( cmSyncRecdH_t h, const cmTimeSpec_t* timestamp, unsigned status, unsigned d0, unsigned d1 )
  318. {
  319. cmSyRC_t rc = kOkSyRC;
  320. cmSr_t* p = _cmSrHtoP(h);
  321. cmSrRecd_t* rp = p->cache + p->ci;
  322. rp->tid = kMidiSrId;
  323. rp->u.m.timestamp = *timestamp;
  324. rp->u.m.status = status;
  325. rp->u.m.d0 = d0;
  326. rp->u.m.d1 = d1;
  327. p->ci += 1;
  328. if( p->ci == p->cn )
  329. rc = _cmSrWriteCache(p);
  330. return rc;
  331. }
  332. cmSyRC_t cmSyncRecdAudioWrite( cmSyncRecdH_t h, const cmTimeSpec_t* timestamp, unsigned smpIdx, const cmSample_t* ch[], unsigned chCnt, unsigned frmCnt )
  333. {
  334. cmSyRC_t rc = kOkSyRC;
  335. cmSr_t* p = _cmSrHtoP(h);
  336. cmSrRecd_t* rp = p->cache + p->ci;
  337. rp->tid = kAudioSrId;
  338. rp->u.a.timestamp = *timestamp;
  339. rp->u.a.smpIdx = smpIdx;
  340. p->ci += 1;
  341. if( p->ci == p->cn )
  342. if((rc = _cmSrWriteCache(p)) != kOkSyRC )
  343. goto errLabel;
  344. if( cmAudioFileWriteSample(p->afH,frmCnt,chCnt,(cmSample_t**)ch) != kOkAfRC )
  345. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Audio file write failed.");
  346. errLabel:
  347. return rc;
  348. }
  349. cmSyRC_t cmSyncRecdPrint( cmSyncRecdH_t h )
  350. {
  351. cmSyRC_t rc = kOkSyRC;
  352. cmSr_t* p = _cmSrHtoP(h);
  353. unsigned i;
  354. if( cmIsFlag(p->flags,kReadSrFl)==false)
  355. return cmErrMsg(&p->err,kInvalidOpSyRC,"The 'print' operation is only valid on sync-recd files opened for reading.");
  356. for(i=0; i<p->cn; ++i)
  357. {
  358. cmSrRecd_t* r = p->cache + i;
  359. cmRptPrintf(p->err.rpt,"0x%x %3i %3i %ld %5.3f : %ld %5.3f %i",r->u.m.status,r->u.m.d0,r->u.m.d1,r->u.m.timestamp.tv_sec,r->u.m.timestamp.tv_nsec/1000000000.0,p->map[i].timestamp.tv_sec,p->map[i].timestamp.tv_nsec/1000000000.0,p->map[i].smpIdx);
  360. }
  361. return rc;
  362. }
  363. cmSyRC_t cmSyncRecdAudioFile( cmSyncRecdH_t h, const cmChar_t* fn )
  364. {
  365. cmSyRC_t rc = kOkSyRC;
  366. cmSr_t* p = _cmSrHtoP(h);
  367. cmAudioFileH_t afH = cmNullAudioFileH;
  368. unsigned chCnt = 2;
  369. unsigned frmCnt = 1024;
  370. unsigned chIdx = 0;
  371. cmRC_t afRC = kOkAfRC;
  372. unsigned actFrmCnt = 0;
  373. unsigned smpIdx = 0;
  374. cmSample_t* chs[chCnt];
  375. cmSample_t buf[frmCnt*2];
  376. chs[0] = buf;
  377. chs[1] = buf+frmCnt;
  378. if( cmIsFlag(p->flags,kReadSrFl)==false)
  379. return cmErrMsg(&p->err,kInvalidOpSyRC,"The 'audio-file-output' operation is only valid on sync-recd files opened for reading.");
  380. /// Open an audio file for writing
  381. if(cmAudioFileIsValid(afH = cmAudioFileNewCreate(fn, p->afInfo.srate, p->afInfo.bits, chCnt, &afRC, p->err.rpt))==false)
  382. {
  383. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Unable to create the synchronized audio file '%s'.",cmStringNullGuard(fn));
  384. goto errLabel;
  385. }
  386. // rewind the input audio file
  387. if( cmAudioFileSeek(p->afH,0) != kOkAfRC )
  388. {
  389. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Seek failed during synchronized audio file output.");
  390. goto errLabel;
  391. }
  392. actFrmCnt = frmCnt;
  393. // for each buffer of audio
  394. for(smpIdx=0; actFrmCnt==frmCnt; smpIdx+=actFrmCnt)
  395. {
  396. unsigned i;
  397. // read frmCnt samples from the first channel of the input audio file
  398. if( cmAudioFileReadSample(p->afH, frmCnt, chIdx, 1, chs, &actFrmCnt ) != kOkAfRC )
  399. {
  400. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Audio file read failed.");
  401. break;
  402. }
  403. // zero the output buffer for the second audio channel
  404. cmVOS_Zero(chs[1],frmCnt);
  405. // insert impulses at the location of the MIDI messages.
  406. for(i=0; i<p->cn; ++i)
  407. if( p->cache[i].u.m.status==kNoteOnMdId && smpIdx <= p->map[i].smpIdx && p->map[i].smpIdx < smpIdx+frmCnt )
  408. chs[1][ p->map[i].smpIdx - smpIdx ] = 1.0;
  409. // write the audio output samples
  410. if( cmAudioFileWriteSample(afH, frmCnt, chCnt, chs ) != kOkAfRC )
  411. {
  412. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Synchronized audio file write failed.");
  413. break;
  414. }
  415. }
  416. errLabel:
  417. if( cmAudioFileDelete(&afH) != kOkAfRC )
  418. rc = cmErrMsg(&p->err,kAudioFileFailSyRC,"Synchronized audio file close failed.");
  419. return rc;
  420. }
  421. cmSyRC_t cmSyncRecdTest( cmCtx_t* ctx )
  422. {
  423. enum
  424. {
  425. kOkTestRC,
  426. kTestFailRC,
  427. };
  428. cmSyRC_t rc = kOkSyRC;
  429. const cmChar_t* srFn = "/home/kevin/temp/kr/sr/sr0.sr";
  430. const cmChar_t* aFn = "/home/kevin/temp/kr/sr/sync_af.aiff";
  431. cmErr_t err;
  432. cmSyncRecdH_t srH = cmSyncRecdNullHandle;
  433. cmErrSetup(&err,&ctx->rpt,"SyncRecdTest");
  434. if((rc = cmSyncRecdOpen(ctx, &srH, srFn )) != kOkSyRC )
  435. {
  436. cmErrMsg(&err,kTestFailRC,"Sync-recd open failed.");
  437. goto errLabel;
  438. }
  439. cmSyncRecdPrint(srH);
  440. cmSyncRecdAudioFile(srH,aFn);
  441. errLabel:
  442. if((rc = cmSyncRecdFinal(&srH)) != kOkSyRC )
  443. cmErrMsg(&err,kTestFailRC,"Sync-recd close failed.");
  444. return rc;
  445. }