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 12KB

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