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

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