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.

cmAudioFileMgr.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  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 "cmAudioFile.h"
  9. #include "cmVectOpsTemplateMain.h"
  10. #include "cmAudioFileMgr.h"
  11. struct cmAfm_str;
  12. typedef struct
  13. {
  14. cmSample_t* minV; // minV[summN]
  15. cmSample_t* maxV; // maxV[summN]
  16. unsigned summN; // lenght of minV[] and maxV[]
  17. } cmAfmSummary_t;
  18. typedef struct cmAfmFile_str
  19. {
  20. unsigned id;
  21. cmAudioFileH_t afH;
  22. cmAudioFileInfo_t afInfo;
  23. unsigned smpPerSummPt;
  24. cmAfmSummary_t* summArray; // summArray[ afInfo.chCnt ]
  25. cmSample_t* summMem; // memory used by summArray[] vectors
  26. struct cmAfm_str* p;
  27. struct cmAfmFile_str* next;
  28. struct cmAfmFile_str* prev;
  29. } cmAfmFile_t;
  30. typedef struct cmAfm_str
  31. {
  32. cmErr_t err;
  33. cmAfmFile_t* list;
  34. } cmAfm_t;
  35. cmAfmH_t cmAfmNullHandle = cmSTATIC_NULL_HANDLE;
  36. cmAfmFileH_t cmAfmFileNullHandle = cmSTATIC_NULL_HANDLE;
  37. cmAfm_t* _cmAfmHandleToPtr( cmAfmH_t h )
  38. {
  39. cmAfm_t* p = (cmAfm_t*)h.h;
  40. assert(p!=NULL);
  41. return p;
  42. }
  43. cmAfmFile_t* _cmAfmFileHandleToPtr( cmAfmFileH_t fh )
  44. {
  45. cmAfmFile_t* fp = (cmAfmFile_t*)fh.h;
  46. assert(fp!=NULL);
  47. return fp;
  48. }
  49. cmAfmRC_t _cmAfmFileClose( cmAfmFile_t* fp )
  50. {
  51. cmAfmRC_t rc = kOkAfmRC;
  52. if( cmAudioFileIsValid( fp->afH ) )
  53. if( cmAudioFileDelete( &fp->afH) != kOkAfRC )
  54. return cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file close failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH)));
  55. if( fp->next != NULL )
  56. fp->next->prev = fp->prev;
  57. if( fp->prev != NULL )
  58. fp->prev->next = fp->next;
  59. if( fp->p->list == fp )
  60. fp->p->list = fp->next;
  61. cmMemFree(fp->summArray);
  62. cmMemFree(fp->summMem);
  63. cmMemFree(fp);
  64. return rc;
  65. }
  66. cmAfmRC_t cmAfmFileOpen( cmAfmH_t h, cmAfmFileH_t* fhp, const cmChar_t* audioFn, unsigned id, cmAudioFileInfo_t* afInfo )
  67. {
  68. cmAfmRC_t rc;
  69. cmRC_t afRC;
  70. if((rc = cmAfmFileClose(fhp)) != kOkAfmRC )
  71. return rc;
  72. cmAfmFile_t* fp = cmMemAllocZ(cmAfmFile_t,1);
  73. fp->p = _cmAfmHandleToPtr(h);
  74. // open the audio file
  75. if( cmAudioFileIsValid(fp->afH = cmAudioFileNewOpen(audioFn, &fp->afInfo, &afRC, fp->p->err.rpt )) == false )
  76. {
  77. rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"The audio file '%s' could not be opened.",cmStringNullGuard(audioFn));
  78. goto errLabel;
  79. }
  80. // prepend the new file to the mgr's file list
  81. if( fp->p->list != NULL )
  82. fp->p->list->prev = fp;
  83. fp->next = fp->p->list;
  84. fp->p->list = fp;
  85. fp->id = id;
  86. fhp->h = fp;
  87. if( afInfo != NULL )
  88. *afInfo = fp->afInfo;
  89. errLabel:
  90. if( rc != kOkAfmRC )
  91. _cmAfmFileClose(fp);
  92. return rc;
  93. }
  94. cmAfmRC_t cmAfmFileClose( cmAfmFileH_t* fhp )
  95. {
  96. cmAfmRC_t rc = kOkAfmRC;
  97. if( fhp==NULL || cmAfmFileIsValid(*fhp)==false)
  98. return rc;
  99. cmAfmFile_t* fp = _cmAfmFileHandleToPtr( *fhp );
  100. if((rc = _cmAfmFileClose(fp)) != kOkAfmRC )
  101. return rc;
  102. fhp->h = NULL;
  103. return rc;
  104. }
  105. bool cmAfmFileIsValid( cmAfmFileH_t fh )
  106. { return fh.h != NULL; }
  107. unsigned cmAfmFileId( cmAfmFileH_t fh )
  108. {
  109. cmAfmFile_t* fp = _cmAfmFileHandleToPtr( fh );
  110. return fp->id;
  111. }
  112. cmAudioFileH_t cmAfmFileHandle( cmAfmFileH_t fh )
  113. {
  114. cmAfmFile_t* fp = _cmAfmFileHandleToPtr( fh );
  115. return fp->afH;
  116. }
  117. const cmAudioFileInfo_t* cmAfmFileInfo( cmAfmFileH_t fh )
  118. {
  119. cmAfmFile_t* fp = _cmAfmFileHandleToPtr( fh );
  120. return &fp->afInfo;
  121. }
  122. cmAfmRC_t cmAfmFileSummarize( cmAfmFileH_t fh, unsigned smpPerSummPt )
  123. {
  124. cmAfmFile_t* fp = _cmAfmFileHandleToPtr(fh);
  125. cmAfmRC_t rc = kOkAfmRC;
  126. unsigned chCnt = fp->afInfo.chCnt;
  127. // summary points per channel per vector
  128. unsigned summN = (unsigned)ceil((double)fp->afInfo.frameCnt / smpPerSummPt );
  129. // total summary points in all channels and vectors
  130. unsigned n = chCnt*2*summN;
  131. // Calc the number of summary points per audio file read
  132. unsigned ptsPerRd = cmMax(1,cmMax(smpPerSummPt,8192) / smpPerSummPt);
  133. // Calc the number samples per audio file read as an integer multiple of ptsPerRd.
  134. unsigned frmCnt = ptsPerRd * smpPerSummPt;
  135. unsigned actualFrmCnt = 0;
  136. cmSample_t* chBuf[ chCnt ];
  137. cmSample_t buf[ frmCnt * chCnt ];
  138. unsigned i;
  139. // allocate the summary record array
  140. if( fp->summArray == NULL )
  141. fp->summArray = cmMemAllocZ( cmAfmSummary_t, chCnt );
  142. // allocate the summary vector memory for all channels
  143. fp->summMem = cmMemResizeZ( cmSample_t, fp->summMem, n);
  144. fp->smpPerSummPt = smpPerSummPt;
  145. // setup the summary record array and audio file read buffer
  146. for(i=0; i<chCnt; ++i)
  147. {
  148. // assign memory to the summary vectors
  149. fp->summArray[i].minV = fp->summMem + i * summN * 2;
  150. fp->summArray[i].maxV = fp->summArray[i].minV + summN;
  151. fp->summArray[i].summN = summN;
  152. // setup the audio file reading channel buffer
  153. chBuf[i] = buf + (i*frmCnt);
  154. }
  155. // read the entire file and calculate the summary vectors
  156. i = 0;
  157. do
  158. {
  159. unsigned chIdx = 0;
  160. unsigned j,k;
  161. // read the next frmCnt samples from the
  162. if( cmAudioFileReadSample(fp->afH, frmCnt, chIdx, chCnt, chBuf, &actualFrmCnt ) != kOkAfRC )
  163. {
  164. rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file read failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH)));
  165. goto errLabel;
  166. }
  167. // for each summary point
  168. for(k=0; k<actualFrmCnt && i<summN; k+=smpPerSummPt,++i)
  169. {
  170. // cnt of samples in this summary report
  171. unsigned m = cmMin(smpPerSummPt,actualFrmCnt-k);
  172. // for each channel
  173. for(j=0; j<chCnt; ++j)
  174. {
  175. fp->summArray[j].minV[i] = cmVOS_Min(chBuf[j]+k,m,1);
  176. fp->summArray[j].maxV[i] = cmVOS_Max(chBuf[j]+k,m,1);
  177. }
  178. }
  179. }while( i<summN && actualFrmCnt==frmCnt );
  180. errLabel:
  181. return rc;
  182. }
  183. // Downsample the summary data to produce the output.
  184. // There must be 1 or more summary points per output point.
  185. cmAfmRC_t _cmAfmFileGetDownSummary(
  186. cmAfmFile_t* fp,
  187. unsigned chIdx,
  188. unsigned begSmpIdx,
  189. unsigned smpCnt,
  190. cmSample_t* minV,
  191. cmSample_t* maxV,
  192. unsigned outCnt )
  193. {
  194. assert( smpCnt >= outCnt );
  195. double smpPerOut = (double)smpCnt/outCnt;
  196. double summPerOut = smpPerOut/fp->smpPerSummPt;
  197. unsigned i;
  198. for(i=0; i<outCnt; ++i)
  199. {
  200. double fsbi = (begSmpIdx + (i*smpPerOut)) / fp->smpPerSummPt; // starting summary pt index
  201. double fsei = fsbi + summPerOut; // endiing summary pt index
  202. unsigned si = (unsigned)floor(fsbi);
  203. unsigned sn = (unsigned)floor(fsei - fsbi + 1);
  204. if( si > fp->summArray[chIdx].summN )
  205. {
  206. minV[i] = 0;
  207. maxV[i] = 0;
  208. }
  209. else
  210. {
  211. if( si + sn > fp->summArray[chIdx].summN )
  212. sn = fp->summArray[chIdx].summN - si;
  213. if( sn == 0 )
  214. {
  215. minV[i] = 0;
  216. maxV[i] = 0;
  217. }
  218. else
  219. {
  220. minV[i] = cmVOS_Min(fp->summArray[chIdx].minV+si,sn,1);
  221. maxV[i] = cmVOS_Max(fp->summArray[chIdx].maxV+si,sn,1);
  222. }
  223. }
  224. }
  225. return kOkAfmRC;
  226. }
  227. // Downsample the audio data to produce the output.
  228. cmAfmRC_t _cmAfmFileGetDownAudio(
  229. cmAfmFile_t* fp,
  230. unsigned chIdx,
  231. unsigned begSmpIdx,
  232. unsigned smpCnt,
  233. cmSample_t* minV,
  234. cmSample_t* maxV,
  235. unsigned outCnt )
  236. {
  237. assert( smpCnt >= outCnt );
  238. cmAfmRC_t rc = kOkAfmRC;
  239. unsigned actualFrmCnt = 0;
  240. unsigned chCnt = 1;
  241. unsigned i;
  242. cmSample_t buf[ smpCnt ];
  243. cmSample_t* chBuf[] = { buf };
  244. // seek to the read location
  245. if( cmAudioFileSeek( fp->afH, begSmpIdx ) != kOkAfRC )
  246. {
  247. rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file seek failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH)));
  248. goto errLabel;
  249. }
  250. // read 'smpCnt' samples into chBuf[][]
  251. if( cmAudioFileReadSample(fp->afH, smpCnt, chIdx, chCnt, chBuf, &actualFrmCnt ) != kOkAfRC )
  252. {
  253. rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file read failed on '%s' durnig upsample.",cmStringNullGuard(cmAudioFileName(fp->afH)));
  254. goto errLabel;
  255. }
  256. double smpPerOut = (double)smpCnt/outCnt;
  257. for(i=0; i<outCnt; ++i)
  258. {
  259. double fsbi = i*smpPerOut;
  260. double fsei = fsbi + smpPerOut;
  261. unsigned si = (unsigned)floor(fsbi);
  262. unsigned sn = (unsigned)floor(fsei - fsbi + 1);
  263. if( si > smpCnt )
  264. {
  265. minV[i] = 0;
  266. maxV[i] = 0;
  267. }
  268. if( si + sn > smpCnt )
  269. sn = smpCnt - si;
  270. minV[i] = cmVOS_Min(chBuf[chIdx]+si,sn,1);
  271. maxV[i] = cmVOS_Max(chBuf[chIdx]+si,sn,1);
  272. }
  273. errLabel:
  274. return rc;
  275. }
  276. // If there is one or less summary points per output
  277. cmAfmRC_t _cmAfmFileGetUpSummary(
  278. cmAfmFile_t* fp,
  279. unsigned chIdx,
  280. unsigned begSmpIdx,
  281. unsigned smpCnt,
  282. cmSample_t* minV,
  283. cmSample_t* maxV,
  284. unsigned outCnt )
  285. {
  286. assert( outCnt >= smpCnt );
  287. cmAfmRC_t rc = kOkAfmRC;
  288. unsigned actualFrmCnt = 0;
  289. unsigned chCnt = 1;
  290. unsigned i;
  291. cmSample_t buf[ smpCnt ];
  292. cmSample_t* chBuf[] = { buf };
  293. if( cmAudioFileSeek( fp->afH, begSmpIdx ) != kOkAfRC )
  294. {
  295. rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file seek failed on '%s'.",cmStringNullGuard(cmAudioFileName(fp->afH)));
  296. goto errLabel;
  297. }
  298. if( cmAudioFileReadSample(fp->afH, smpCnt, chIdx, chCnt, chBuf, &actualFrmCnt ) != kOkAfRC )
  299. {
  300. rc = cmErrMsg(&fp->p->err,kAudioFileFailAfmRC,"Audio file read failed on '%s' durnig upsample.",cmStringNullGuard(cmAudioFileName(fp->afH)));
  301. goto errLabel;
  302. }
  303. for(i=0; i<outCnt; ++i)
  304. {
  305. unsigned si = cmMin(smpCnt-1, (unsigned)floor(i * smpCnt / outCnt));
  306. cmSample_t v = buf[si];
  307. minV[i] = v;
  308. maxV[i] = v;
  309. }
  310. errLabel:
  311. return rc;
  312. }
  313. cmAfmRC_t cmAfmFileGetSummary( cmAfmFileH_t fh, unsigned chIdx, unsigned begSmpIdx, unsigned smpCnt, cmSample_t* minV, cmSample_t* maxV, unsigned outCnt )
  314. {
  315. cmAfmRC_t rc = kOkAfmRC;
  316. cmAfmFile_t* fp = _cmAfmFileHandleToPtr(fh);
  317. double maxHiResDurSecs = 20.0;
  318. if( smpCnt <= outCnt )
  319. rc = _cmAfmFileGetUpSummary( fp, chIdx, begSmpIdx, smpCnt, minV, maxV, outCnt );
  320. else
  321. {
  322. if( smpCnt/fp->afInfo.srate < maxHiResDurSecs )
  323. rc = _cmAfmFileGetDownAudio( fp, chIdx, begSmpIdx, smpCnt, minV, maxV, outCnt );
  324. else
  325. rc = _cmAfmFileGetDownSummary( fp, chIdx, begSmpIdx, smpCnt, minV, maxV, outCnt );
  326. }
  327. return rc;
  328. }
  329. //----------------------------------------------------------------------------
  330. // Audio File Manager
  331. //----------------------------------------------------------------------------
  332. cmAfmRC_t _cmAfmDestroy( cmAfm_t* p )
  333. {
  334. cmAfmRC_t rc = kOkAfmRC;
  335. while( p->list != NULL )
  336. {
  337. if((rc = _cmAfmFileClose(p->list)) != kOkAfmRC )
  338. goto errLabel;
  339. }
  340. cmMemFree(p);
  341. errLabel:
  342. return rc;
  343. }
  344. cmAfmRC_t cmAfmCreate( cmCtx_t* ctx, cmAfmH_t* hp )
  345. {
  346. cmAfmRC_t rc;
  347. if((rc = cmAfmDestroy(hp)) != kOkAfmRC )
  348. return rc;
  349. cmAfm_t* p = cmMemAllocZ(cmAfm_t,1);
  350. cmErrSetup(&p->err,&ctx->rpt,"Audio File Mgr");
  351. hp->h = p;
  352. return rc;
  353. }
  354. cmAfmRC_t cmAfmDestroy( cmAfmH_t* hp )
  355. {
  356. cmAfmRC_t rc = kOkAfmRC;
  357. if( hp==NULL || cmAfmIsValid(*hp)==false)
  358. return rc;
  359. cmAfm_t* p = _cmAfmHandleToPtr(*hp);
  360. if((rc = _cmAfmDestroy(p)) != kOkAfmRC )
  361. return rc;
  362. hp->h = NULL;
  363. return rc;
  364. }
  365. bool cmAfmIsValid( cmAfmH_t h )
  366. { return h.h != NULL; }
  367. cmAfmFileH_t cmAfmIdToHandle( cmAfmH_t h, unsigned fileId )
  368. {
  369. cmAfm_t* p = _cmAfmHandleToPtr(h);
  370. cmAfmFile_t* fp = p->list;
  371. cmAfmFileH_t fh = cmAfmFileNullHandle;
  372. for(; fp!=NULL; fp=fp->next)
  373. if( fp->id == fileId )
  374. {
  375. fh.h = fp;
  376. break;
  377. }
  378. return fh;
  379. }