libcm is a C development framework with an emphasis on audio signal processing applications.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. }