cmtools is a collection of utilities for generating and processing machine readable musical scores based on MusicXML, MIDI and other data files.
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.

mas.c 71KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234
  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 "cmPrefix.h"
  4. #include "cmGlobal.h"
  5. #include "cmFloatTypes.h"
  6. #include "cmComplexTypes.h"
  7. #include "cmRpt.h"
  8. #include "cmErr.h"
  9. #include "cmCtx.h"
  10. #include "cmMem.h"
  11. #include "cmMallocDebug.h"
  12. #include "cmLinkedHeap.h"
  13. #include "cmSymTbl.h"
  14. #include "cmFile.h"
  15. #include "cmFileSys.h"
  16. #include "cmTime.h"
  17. #include "cmMidi.h"
  18. #include "cmAudioFile.h"
  19. #include "cmMidiFile.h"
  20. #include "cmJson.h"
  21. #include "cmText.h"
  22. #include "cmProcObj.h"
  23. #include "cmProcTemplateMain.h"
  24. #include "cmVectOpsTemplateMain.h"
  25. #include "cmProc.h"
  26. #include "cmProc2.h"
  27. #include "cmTimeLine.h"
  28. #include "cmOnset.h"
  29. #include "cmPgmOpts.h"
  30. #include "cmScore.h"
  31. typedef cmRC_t masRC_t;
  32. enum
  33. {
  34. kOkMasRC = cmOkRC,
  35. kFailMasRC,
  36. kJsonFailMasRC,
  37. kParamErrMasRC,
  38. kTimeLineFailMasRC
  39. };
  40. enum
  41. {
  42. kMidiToAudioSelId,
  43. kAudioOnsetSelId,
  44. kConvolveSelId,
  45. kSyncSelId,
  46. kGenTimeLineSelId,
  47. kLoadMarkersSelId,
  48. kTestStubSelId
  49. };
  50. typedef struct
  51. {
  52. const cmChar_t* input;
  53. const cmChar_t* output;
  54. unsigned selId;
  55. double wndMs;
  56. double srate;
  57. cmOnsetCfg_t onsetCfg;
  58. const cmChar_t* refDir;
  59. const cmChar_t* keyDir;
  60. const cmChar_t* refExt;
  61. const cmChar_t* keyExt;
  62. const cmChar_t* markFn;
  63. const cmChar_t* prefixPath;
  64. } masPgmArgs_t;
  65. typedef struct
  66. {
  67. const char* refFn;
  68. double refWndBegSecs; // location of the ref window in the midi file
  69. double refWndSecs; // length of the ref window
  70. const char* keyFn;
  71. double keyBegSecs; // offset into audio file of first sliding window
  72. double keyEndSecs; // offset into audio file of the last sliding window
  73. unsigned keySyncIdx; // index into audio file of best matched sliding window
  74. double syncDist; // distance (matching score) to the ref window of the best matched sliding window
  75. unsigned refSmpCnt; // count of samples in the midi file
  76. unsigned keySmpCnt; // count of samples in the audio file
  77. double srate; // sample rate of audio and midi file
  78. } syncRecd_t;
  79. // Notes:
  80. // audioBegSecs
  81. // != 0 - audio file is locked to midi file
  82. // == 0 - midi file is locked to audio file
  83. typedef struct
  84. {
  85. cmJsonH_t jsH;
  86. syncRecd_t* syncArray;
  87. unsigned syncArrayCnt;
  88. const cmChar_t* refDir;
  89. const cmChar_t* keyDir;
  90. double hopMs;
  91. } syncCtx_t;
  92. enum
  93. {
  94. kMidiFl = 0x01,
  95. kAudioFl= 0x02,
  96. kLabelCharCnt = 31
  97. };
  98. // Notes:
  99. // 1) Master files will have refPtr==NULL.
  100. // 2) refSmpIdx and keySmpIdx are only valid for slave files.
  101. // 3) A common group id is given to sets of files which are
  102. // locked in time relative to one another. For example
  103. // if file B and C are synced to master file A and
  104. // file D is synced to file E which is synced to master
  105. // file F. Then files A,B,C will be given one group
  106. // id and files D,E and F will be given another group id.
  107. //
  108. typedef struct file_str
  109. {
  110. unsigned flags; // see kXXXFl
  111. const char* fn; // file name of this file
  112. const char* fullFn; // path and name
  113. unsigned refIdx; // index into file array of recd pointed to by refPtr
  114. struct file_str* refPtr; // ptr to the file that this file is positioned relative to (set to NULL for master files)
  115. int refSmpIdx; // index into the reference file that is synced to keySmpIdx
  116. int keySmpIdx; // index into this file which is synced to refSmpIdx
  117. int absSmpIdx; // abs smp idx of sync location
  118. int absBegSmpIdx; // file beg smp idx - the earliest file in the group is set to 0.
  119. int smpCnt; // file duration
  120. double srate; // file sample rate
  121. unsigned groupId; // every file belongs to a group - a group is a set of files referencing a common master
  122. char label[ kLabelCharCnt+1 ];
  123. } fileRecd_t;
  124. void print( void* p, const cmChar_t* text)
  125. {
  126. if( text != NULL )
  127. {
  128. printf("%s",text);
  129. fflush(stdout);
  130. }
  131. }
  132. masRC_t midiStringSearch( cmCtx_t* ctx, const cmChar_t* srcDir, cmMidiByte_t* x, unsigned xn )
  133. {
  134. cmFileSysDirEntry_t* dep = NULL;
  135. unsigned dirEntryCnt = 0;
  136. unsigned i,j,k;
  137. masRC_t rc = kOkMasRC;
  138. unsigned totalNoteCnt = 0;
  139. typedef struct
  140. {
  141. cmMidiByte_t pitch;
  142. unsigned micros;
  143. } note_t;
  144. assert( xn > 0 );
  145. note_t wnd[ xn ];
  146. // iterate the source directory
  147. if( (dep = cmFsDirEntries( srcDir, kFileFsFl | kFullPathFsFl, &dirEntryCnt )) == NULL )
  148. return cmErrMsg(&ctx->err,kFailMasRC,"Unable to iterate the source directory '%s'.",srcDir);
  149. // for each file in the source directory
  150. for(i=0; i<dirEntryCnt; ++i)
  151. {
  152. cmMidiFileH_t mfH;
  153. // open the MIDI file
  154. if( cmMidiFileOpen(ctx, &mfH, dep[i].name) != kOkMfRC )
  155. {
  156. rc = cmErrMsg(&ctx->err,kFailMasRC,"The MIDI file '%s' could not be opened.",dep[i].name);
  157. goto errLabel;
  158. }
  159. cmRptPrintf(ctx->err.rpt,"%3i of %3i %s ",i,dirEntryCnt,dep[i].name);
  160. unsigned msgCnt = cmMidiFileMsgCount(mfH); // get the count of messages in the MIDI file
  161. const cmMidiTrackMsg_t** msgPtrPtr = cmMidiFileMsgArray(mfH); // get a ptr to the base of the the MIDI msg array
  162. //cmMidiFileTickToMicros(mfH); // convert the MIDI msg time base from ticks to microseconds
  163. // empty the window
  164. for(j=0; j<xn; ++j)
  165. wnd[j].pitch = kInvalidMidiPitch;
  166. unsigned micros = 0;
  167. unsigned noteCnt = 0;
  168. for(k=0; k<msgCnt; ++k)
  169. {
  170. const cmMidiTrackMsg_t* mp = msgPtrPtr[k];
  171. micros += mp->dtick;
  172. if( mp->status == kNoteOnMdId )
  173. {
  174. ++noteCnt;
  175. // shift the window to the left
  176. for(j=0; j<xn-1; ++j)
  177. wnd[j] = wnd[j+1];
  178. // insert the new note on the right
  179. wnd[ xn-1 ].pitch = mp->u.chMsgPtr->d0;
  180. wnd[ xn-1 ].micros = micros;
  181. // compare the window to the search string
  182. for(j=0; j<xn; ++j)
  183. if( wnd[j].pitch != x[j] )
  184. break;
  185. // if a match was found
  186. if( j == xn )
  187. cmRptPrintf(ctx->err.rpt,"\n %5i %5.1f ", i, /* minuites */ (double)micros/60000000.0 );
  188. }
  189. }
  190. totalNoteCnt += noteCnt;
  191. cmRptPrintf(ctx->err.rpt,"%i %i \n",noteCnt,totalNoteCnt);
  192. // close the midi file
  193. cmMidiFileClose(&mfH);
  194. }
  195. errLabel:
  196. cmFsDirFreeEntries(dep);
  197. return rc;
  198. }
  199. // Generate an audio file containing impulses at the location of each note-on message.
  200. masRC_t midiToAudio( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* audioFn, double srate )
  201. {
  202. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  203. unsigned sampleBits = 16;
  204. unsigned chCnt = 1;
  205. unsigned msgCnt;
  206. const cmMidiTrackMsg_t** msgPtrPtr;
  207. masRC_t rc = kFailMasRC;
  208. cmRC_t afRC = kOkAfRC;
  209. cmAudioFileH_t afH = cmNullAudioFileH;
  210. unsigned bufSmpCnt = 1024;
  211. cmSample_t buf[ bufSmpCnt ];
  212. cmSample_t *bufPtr = buf;
  213. unsigned noteOnCnt = 0;
  214. // open the MIDI file
  215. if( cmMidiFileOpen(ctx,&mfH,midiFn) != kOkMfRC )
  216. return kFailMasRC;
  217. // force the first event to occur one quarter note into the file
  218. cmMidiFileSetDelay(mfH, cmMidiFileTicksPerQN(mfH) );
  219. double mfDurSecs = cmMidiFileDurSecs(mfH);
  220. cmRptPrintf(&ctx->rpt,"Secs:%f \n",mfDurSecs);
  221. msgCnt = cmMidiFileMsgCount(mfH); // get the count of messages in the MIDI file
  222. msgPtrPtr = cmMidiFileMsgArray(mfH); // get a ptr to the base of the the MIDI msg array
  223. //cmMidiFileTickToMicros(mfH); // convert the MIDI msg time base from ticks to microseconds
  224. if( msgCnt == 0 )
  225. {
  226. rc = kOkMasRC;
  227. goto errLabel;
  228. }
  229. // create the output audio file
  230. if( cmAudioFileIsValid( afH = cmAudioFileNewCreate(audioFn, srate, sampleBits, chCnt, &afRC, &ctx->rpt))==false )
  231. {
  232. cmErrMsg(&ctx->err,kFailMasRC,"The attempt to create the audio file '%s' failed.",audioFn);
  233. goto errLabel;
  234. }
  235. unsigned msgIdx = 0;
  236. unsigned msgSmpIdx = floor( msgPtrPtr[msgIdx]->dtick * srate / 1000000.0);
  237. unsigned begSmpIdx = 0;
  238. do
  239. {
  240. // zero the audio buffer
  241. cmVOS_Zero(buf,bufSmpCnt);
  242. // for each msg which falls inside the current buffer
  243. while( begSmpIdx <= msgSmpIdx && msgSmpIdx < begSmpIdx + bufSmpCnt )
  244. {
  245. // put an impulse representing this note-on msg in the buffer
  246. if( msgPtrPtr[msgIdx]->status == kNoteOnMdId )
  247. {
  248. buf[ msgSmpIdx - begSmpIdx ] = (cmSample_t)msgPtrPtr[msgIdx]->u.chMsgPtr->d1 / 127;
  249. ++noteOnCnt;
  250. }
  251. // advance to the next msg
  252. ++msgIdx;
  253. if( msgIdx == msgCnt )
  254. break;
  255. // update the current msg time
  256. msgSmpIdx += floor( msgPtrPtr[msgIdx]->dtick * srate / 1000000.0);
  257. }
  258. // write the audio buffer
  259. if( cmAudioFileWriteFloat(afH, bufSmpCnt, chCnt, &bufPtr ) != kOkAfRC )
  260. {
  261. cmErrMsg(&ctx->err,kFailMasRC,"Audio file write failed on '%s'.",audioFn);
  262. goto errLabel;
  263. }
  264. // advance the buffer position
  265. begSmpIdx += bufSmpCnt;
  266. }while(msgIdx < msgCnt);
  267. /*
  268. // for each MIDI msg
  269. for(i=0; i<msgCnt; ++i)
  270. {
  271. const cmMidiTrackMsg_t* mp = msgPtrPtr[i];
  272. // calculate the absolute time of this msg in microseconds
  273. absUSecs += mp->dtick;
  274. // if this is a note on msg
  275. if( mp->status == kNoteOnMdId && absUSecs > shiftUSecs )
  276. {
  277. // convert the msg time to samples
  278. unsigned smpIdx = floor((absUSecs - shiftUSecs) * srate / 1000000.0);
  279. assert(smpIdx<smpCnt);
  280. // put a velocity scaled impulse in the audio file
  281. sV[smpIdx] = (cmSample_t)mp->u.chMsgPtr->d1 / 127;
  282. }
  283. }
  284. cmSample_t** bufPtrPtr = &sV;
  285. if( cmAudioFileWriteFileSample(audioFn,srate,sampleBits,smpCnt,chCnt,bufPtrPtr,&ctx->rpt) != kOkAfRC )
  286. goto errLabel;
  287. */
  288. rc = kOkMasRC;
  289. cmRptPrintf(&ctx->rpt,"Note-on count:%i\n",noteOnCnt);
  290. errLabel:
  291. //cmMemFree(sV);
  292. if( cmAudioFileIsValid(afH) )
  293. cmAudioFileDelete(&afH);
  294. // close the midi file
  295. cmMidiFileClose(&mfH);
  296. return rc;
  297. }
  298. masRC_t filter( cmCtx_t* ctx, const cmChar_t* inAudioFn, const cmChar_t* outAudioFn, double wndMs, double feedbackCoeff )
  299. {
  300. cmAudioFileH_t iafH = cmNullAudioFileH;
  301. cmAudioFileH_t oafH = cmNullAudioFileH;
  302. masRC_t rc = kFailMasRC;
  303. cmAudioFileInfo_t afInfo;
  304. cmRC_t afRC;
  305. double prog = 0.1;
  306. unsigned progIdx = 0;
  307. // open the input audio file
  308. if( cmAudioFileIsValid( iafH = cmAudioFileNewOpen(inAudioFn,&afInfo,&afRC, &ctx->rpt ))==false)
  309. return kFailMasRC;
  310. // create the output audio file
  311. if( cmAudioFileIsValid( oafH = cmAudioFileNewCreate(outAudioFn,afInfo.srate,afInfo.bits,1,&afRC,&ctx->rpt)) == false )
  312. goto errLabel;
  313. else
  314. {
  315. unsigned wndSmpCnt = floor(afInfo.srate * wndMs / 1000);
  316. unsigned procSmpCnt = wndSmpCnt;
  317. cmSample_t procBuf[procSmpCnt];
  318. unsigned actFrmCnt;
  319. cmReal_t a[] = {-feedbackCoeff };
  320. cmReal_t b0 = 1.0;
  321. cmReal_t b[] = {0,};
  322. cmReal_t d[] = {0,0};
  323. do
  324. {
  325. unsigned chIdx = 0;
  326. unsigned chCnt = 1;
  327. cmSample_t* procBufPtr = procBuf;
  328. actFrmCnt = 0;
  329. // read the next procSmpCnt samples from the input file into procBuf[]
  330. cmAudioFileReadSample(iafH, procSmpCnt, chIdx, chCnt, &procBufPtr, &actFrmCnt );
  331. if( actFrmCnt > 0 )
  332. {
  333. cmSample_t y[actFrmCnt];
  334. cmSample_t* yp = y;
  335. cmVOS_Filter( y, actFrmCnt, procBuf, actFrmCnt, b0, b, a, d, 1 );
  336. // write the output audio file
  337. if( cmAudioFileWriteSample(oafH, actFrmCnt, chCnt, &yp ) != kOkAfRC )
  338. goto errLabel;
  339. }
  340. progIdx += actFrmCnt;
  341. if( progIdx > prog * afInfo.frameCnt )
  342. {
  343. cmRptPrintf(&ctx->rpt,"%i ",(int)round(prog*10));
  344. prog += 0.1;
  345. }
  346. }while(actFrmCnt==procSmpCnt);
  347. cmRptPrint(&ctx->rpt,"\n");
  348. }
  349. rc = kOkMasRC;
  350. errLabel:
  351. if( cmAudioFileIsValid(iafH) )
  352. cmAudioFileDelete(&iafH);
  353. if( cmAudioFileIsValid(oafH) )
  354. cmAudioFileDelete(&oafH);
  355. return rc;
  356. }
  357. masRC_t convolve( cmCtx_t* ctx, const cmChar_t* inAudioFn, const cmChar_t* outAudioFn, double wndMs )
  358. {
  359. cmAudioFileH_t iafH = cmNullAudioFileH;
  360. cmAudioFileH_t oafH = cmNullAudioFileH;
  361. cmCtx* ctxp = NULL;
  362. cmConvolve* cnvp = NULL;
  363. masRC_t rc = kFailMasRC;
  364. cmAudioFileInfo_t afInfo;
  365. cmRC_t afRC;
  366. double prog = 0.1;
  367. unsigned progIdx = 0;
  368. // open the input audio file
  369. if( cmAudioFileIsValid( iafH = cmAudioFileNewOpen(inAudioFn,&afInfo,&afRC, &ctx->rpt ))==false)
  370. return kFailMasRC;
  371. // create the output audio file
  372. if( cmAudioFileIsValid( oafH = cmAudioFileNewCreate(outAudioFn,afInfo.srate,afInfo.bits,1,&afRC,&ctx->rpt)) == false )
  373. goto errLabel;
  374. else
  375. {
  376. unsigned wndSmpCnt = floor(afInfo.srate * wndMs / 1000);
  377. unsigned procSmpCnt = wndSmpCnt;
  378. cmSample_t wnd[wndSmpCnt];
  379. cmSample_t procBuf[procSmpCnt];
  380. unsigned actFrmCnt;
  381. cmVOS_Hann(wnd,wndSmpCnt);
  382. //cmVOS_DivVS(wnd,wndSmpCnt, fl ? 384 : 2);
  383. cmVOS_DivVS(wnd,wndSmpCnt, 4);
  384. ctxp = cmCtxAlloc(NULL,&ctx->rpt,cmLHeapNullHandle,cmSymTblNullHandle); // alloc a cmCtx object
  385. cnvp = cmConvolveAlloc(ctxp,NULL,wnd,wndSmpCnt,procSmpCnt); // alloc a convolver object
  386. do
  387. {
  388. unsigned chIdx = 0;
  389. unsigned chCnt = 1;
  390. cmSample_t* procBufPtr = procBuf;
  391. actFrmCnt = 0;
  392. // read the next procSmpCnt samples from the input file into procBuf[]
  393. cmAudioFileReadSample(iafH, procSmpCnt, chIdx, chCnt, &procBufPtr, &actFrmCnt );
  394. if( actFrmCnt > 0 )
  395. {
  396. // convolve the audio signal with the Gaussian window
  397. cmConvolveExec(cnvp,procBuf,actFrmCnt);
  398. //cmVOS_AddVV( cnvp->outV, cnvp->outN, procBufPtr );
  399. // write the output audio file
  400. if( cmAudioFileWriteSample(oafH, cnvp->outN, chCnt, &cnvp->outV ) != kOkAfRC )
  401. goto errLabel;
  402. }
  403. progIdx += actFrmCnt;
  404. if( progIdx > prog * afInfo.frameCnt )
  405. {
  406. cmRptPrintf(&ctx->rpt,"%i ",(int)round(prog*10));
  407. prog += 0.1;
  408. }
  409. }while(actFrmCnt==procSmpCnt);
  410. cmRptPrint(&ctx->rpt,"\n");
  411. }
  412. rc = kOkMasRC;
  413. errLabel:
  414. cmCtxFree(&ctxp);
  415. cmConvolveFree(&cnvp);
  416. if( cmAudioFileIsValid(iafH) )
  417. cmAudioFileDelete(&iafH);
  418. if( cmAudioFileIsValid(oafH) )
  419. cmAudioFileDelete(&oafH);
  420. return rc;
  421. }
  422. masRC_t audioToOnset( cmCtx_t* ctx, const cmChar_t* ifn, const cmChar_t* ofn, const cmOnsetCfg_t* cfg )
  423. {
  424. masRC_t rc = kOkMasRC;
  425. cmOnH_t onH = cmOnsetNullHandle;
  426. cmFileSysPathPart_t* ofsp = NULL;
  427. const cmChar_t* tfn = NULL;
  428. // parse the output file name
  429. if((ofsp = cmFsPathParts(ofn)) == NULL )
  430. {
  431. rc = cmErrMsg(&ctx->err,kFailMasRC,"Onset detector output file name '%s' could not be parsed.",cmStringNullGuard(ofn));
  432. goto errLabel;
  433. }
  434. // verify the output audio file does not use the 'txt' extension
  435. if(strcmp(ofsp->extStr,"txt") == 0 )
  436. {
  437. rc = cmErrMsg(&ctx->err,kFailMasRC,"The onset detector output audio file name cannot use the file name extension 'txt' because it will class with the output text file name.");
  438. goto errLabel;
  439. }
  440. // generate the output text file name by setting the output audio file name to '.txt'.
  441. if((tfn = cmFsMakeFn(ofsp->dirStr,ofsp->fnStr,"txt",NULL)) == NULL )
  442. {
  443. rc = cmErrMsg(&ctx->err,kFailMasRC,"Onset detector output file name generation failed on %s.",cmStringNullGuard(ifn));
  444. goto errLabel;
  445. }
  446. // initialize the onset detection API
  447. if( cmOnsetInitialize(ctx,&onH) != kOkOnRC )
  448. {
  449. rc = cmErrMsg(&ctx->err,kFailMasRC,"The onset detector initialization failed on %s.",cmStringNullGuard(ifn));
  450. goto errLabel;
  451. }
  452. // run the onset detector
  453. if( cmOnsetProc( onH, cfg, ifn ) != kOkOnRC )
  454. {
  455. rc = cmErrMsg(&ctx->err,kFailMasRC,"The onset detector execution failed on %s.",cmStringNullGuard(ifn));
  456. goto errLabel;
  457. }
  458. // store the results of the onset detection
  459. if( cmOnsetWrite( onH, ofn, tfn) != kOkOnRC )
  460. {
  461. rc = cmErrMsg(&ctx->err,kFailMasRC,"The onset detector write result failed on %s.",cmStringNullGuard(ifn));
  462. goto errLabel;
  463. }
  464. errLabel:
  465. // finalize the onset detector API
  466. if( cmOnsetFinalize(&onH) != kOkOnRC )
  467. rc = cmErrMsg(&ctx->err,kFailMasRC,"The onset detector finalization failed on %s.",cmStringNullGuard(ifn));
  468. cmFsFreeFn(tfn);
  469. cmFsFreePathParts(ofsp);
  470. return rc;
  471. }
  472. typedef struct
  473. {
  474. const char* fn;
  475. unsigned startSmpIdx;
  476. unsigned durSmpCnt;
  477. } audioFileRecd_t;
  478. int compareAudioFileRecds( const void* p0, const void* p1 )
  479. { return strcmp(((const audioFileRecd_t*)p0)->fn,((const audioFileRecd_t*)p1)->fn); }
  480. // Print out information on all audio files in a directory.
  481. masRC_t audioFileStartTimes( cmCtx_t* ctx, const char* dirStr )
  482. {
  483. cmFileSysDirEntry_t* dep = NULL;
  484. unsigned dirEntryCnt = 0;
  485. unsigned i,n;
  486. masRC_t rc = kOkMasRC;
  487. if( (dep = cmFsDirEntries( dirStr, kFileFsFl | kFullPathFsFl, &dirEntryCnt )) == NULL )
  488. return kFailMasRC;
  489. else
  490. {
  491. audioFileRecd_t afArray[ dirEntryCnt ];
  492. memset(afArray,0,sizeof(afArray));
  493. for(i=0,n=0; i<dirEntryCnt; ++i)
  494. {
  495. cmAudioFileInfo_t afInfo;
  496. if( cmAudioFileGetInfo( dep[i].name, &afInfo, &ctx->rpt ) == kOkAfRC )
  497. {
  498. afArray[n].fn = dep[i].name;
  499. afArray[n].durSmpCnt = afInfo.frameCnt;
  500. afArray[n].startSmpIdx = afInfo.bextRecd.timeRefLow;
  501. ++n;
  502. }
  503. }
  504. qsort(afArray,n,sizeof(audioFileRecd_t),compareAudioFileRecds);
  505. for(i=0; i<n; ++i)
  506. {
  507. printf("%10i %10i %10i %s\n", afArray[i].startSmpIdx, afArray[i].durSmpCnt, afArray[i].startSmpIdx + afArray[i].durSmpCnt, afArray[i].fn );
  508. }
  509. cmFsDirFreeEntries(dep);
  510. }
  511. return rc;
  512. }
  513. // srate - only used when sel == kMidiToAudioSelId
  514. // wndMs - only used when sel == kConvolveSelId
  515. // onsetCfgPtr - only used when sel == kAudioOnsetSelId
  516. masRC_t fileDriver( cmCtx_t* ctx, unsigned sel, const cmChar_t* srcDir, const cmChar_t* dstDir, double srate, double wndMs, const cmOnsetCfg_t* onsetCfgPtr )
  517. {
  518. cmFileSysDirEntry_t* dep = NULL;
  519. unsigned dirEntryCnt = 0;
  520. unsigned i;
  521. masRC_t rc = kOkMasRC;
  522. // verify / create the destination directory
  523. if( !cmFsIsDir(dstDir) )
  524. if( cmFsMkDir(dstDir) != kOkFsRC )
  525. return cmErrMsg(&ctx->err,kFailMasRC,"Attempt to make directory the directory '%s' failed.",dstDir);
  526. // iterate the source directory
  527. if( (dep = cmFsDirEntries( srcDir, kFileFsFl | kFullPathFsFl, &dirEntryCnt )) == NULL )
  528. return cmErrMsg(&ctx->err,kFailMasRC,"Unable to iterate the source directory '%s'.",srcDir);
  529. else
  530. {
  531. // for each file in the source directory
  532. for(i=0; i<dirEntryCnt; ++i)
  533. {
  534. // parse the file name
  535. cmFileSysPathPart_t* pp = cmFsPathParts( dep[i].name );
  536. // combine the dstDir and source file name to form the dest. file name
  537. const cmChar_t* dstFn = cmFsMakeFn( dstDir, pp->fnStr, "aif", NULL );
  538. cmRptPrintf(&ctx->rpt,"Source File:%s\n", dep[i].name);
  539. switch( sel )
  540. {
  541. case kMidiToAudioSelId:
  542. // convert the MIDI to an audio impulse file
  543. if( midiToAudio(ctx, dep[i].name, dstFn, srate ) != kOkMasRC )
  544. cmErrMsg(&ctx->err,kFailMasRC,"MIDI to audio failed.");
  545. break;
  546. case kConvolveSelId:
  547. // convolve impulse audio file with Hann window
  548. if( convolve(ctx,dep[i].name, dstFn, wndMs ) != kOkMasRC )
  549. cmErrMsg(&ctx->err,kFailMasRC,"Convolution failed.");
  550. break;
  551. case kAudioOnsetSelId:
  552. if( audioToOnset(ctx,dep[i].name, dstFn, onsetCfgPtr ) )
  553. cmErrMsg(&ctx->err,kFailMasRC,"Audio to onset failed.");
  554. break;
  555. }
  556. cmFsFreeFn(dstFn);
  557. cmFsFreePathParts(pp);
  558. }
  559. cmFsDirFreeEntries(dep);
  560. }
  561. return rc;
  562. }
  563. // b0 = base of window to compare.
  564. // b0[i] = location of sample in b0[] to compare to b1[0].
  565. // b1[n] = reference window
  566. double distance( const cmSample_t* b0, const cmSample_t* b1, unsigned n, double maxDist )
  567. {
  568. double sum = 0;
  569. const cmSample_t* ep = b1 + n;
  570. while(b1 < ep && sum < maxDist )
  571. {
  572. sum += ((*b0)-(*b1)) * ((*b0)-(*b1));
  573. ++b0;
  574. ++b1;
  575. }
  576. return sum;
  577. }
  578. // write a syncCtx_t record as a JSON file
  579. masRC_t write_sync_json( cmCtx_t* ctx, const syncCtx_t* scp, const cmChar_t* outJsFn )
  580. {
  581. masRC_t rc = kOkMasRC;
  582. unsigned i;
  583. cmJsonH_t jsH = cmJsonNullHandle;
  584. cmJsonNode_t* jnp;
  585. // create a JSON tree
  586. if( cmJsonInitialize(&jsH,ctx) != kOkJsRC )
  587. {
  588. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON output handle initialization failed on '%s'.",cmStringNullGuard(outJsFn));
  589. goto errLabel;
  590. }
  591. // create an outer container object
  592. if((jnp = cmJsonCreateObject(jsH,NULL)) == NULL )
  593. goto errLabel;
  594. // create the 'sync' object
  595. if((jnp = cmJsonInsertPairObject(jsH,jnp,"sync")) == NULL )
  596. goto errLabel;
  597. if( cmJsonInsertPairs(jsH,jnp,
  598. "refDir",kStringTId,scp->refDir,
  599. "keyDir",kStringTId,scp->keyDir,
  600. "hopMs", kRealTId, scp->hopMs,
  601. NULL) != kOkJsRC )
  602. {
  603. goto errLabel;
  604. }
  605. if((jnp = cmJsonInsertPairArray(jsH,jnp,"array")) == NULL )
  606. goto errLabel;
  607. for(i=0; i<scp->syncArrayCnt; ++i)
  608. {
  609. const syncRecd_t* s = scp->syncArray + i;
  610. if( cmJsonCreateFilledObject(jsH,jnp,
  611. "refFn", kStringTId, s->refFn,
  612. "refWndBegSecs",kRealTId, s->refWndBegSecs,
  613. "refWndSecs", kRealTId, s->refWndSecs,
  614. "keyFn", kStringTId, s->keyFn,
  615. "keyBegSecs", kRealTId, s->keyBegSecs,
  616. "keyEndSecs", kRealTId, s->keyEndSecs,
  617. "keySyncIdx", kIntTId, s->keySyncIdx,
  618. "syncDist", kRealTId, s->syncDist,
  619. "refSmpCnt", kIntTId, s->refSmpCnt,
  620. "keySmpCnt", kIntTId, s->keySmpCnt,
  621. "srate", kRealTId, s->srate,
  622. NULL) == NULL )
  623. {
  624. goto errLabel;
  625. }
  626. }
  627. errLabel:
  628. if( cmJsonErrorCode(jsH) != kOkJsRC )
  629. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON tree construction failed on '%s'.",cmStringNullGuard(outJsFn));
  630. else
  631. {
  632. if( cmJsonWrite(jsH,cmJsonRoot(jsH),outJsFn) != kOkJsRC )
  633. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON write failed on '%s.",cmStringNullGuard(outJsFn));
  634. }
  635. if( cmJsonFinalize(&jsH) != kOkJsRC )
  636. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON output finalization failed on '%s'.",cmStringNullGuard(outJsFn));
  637. return rc;
  638. }
  639. masRC_t _masJsonFieldNotFoundError( cmCtx_t* c, const char* msg, const char* errLabelPtr, const char* cfgFn )
  640. {
  641. masRC_t rc;
  642. if( errLabelPtr != NULL )
  643. rc = cmErrMsg( &c->err, kJsonFailMasRC, "Cfg. %s field not found:'%s' in file:'%s'.",msg,cmStringNullGuard(errLabelPtr),cmStringNullGuard(cfgFn));
  644. else
  645. rc = cmErrMsg( &c->err, kJsonFailMasRC, "Cfg. %s parse failed '%s'.",msg,cmStringNullGuard(cfgFn) );
  646. return rc;
  647. }
  648. // Initialize a syncCtx_t record from a JSON file.
  649. masRC_t read_sync_json( cmCtx_t* ctx, syncCtx_t* scp, const cmChar_t* jsFn )
  650. {
  651. masRC_t rc = kOkMasRC;
  652. cmJsonNode_t* jnp;
  653. const cmChar_t* errLabelPtr = NULL;
  654. unsigned i;
  655. // if the JSON tree already exists then finalize it
  656. if( cmJsonFinalize(&scp->jsH) != kOkJsRC )
  657. return cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON object finalization failed.");
  658. // initialize a JSON tree from a file
  659. if( cmJsonInitializeFromFile(&scp->jsH, jsFn, ctx ) != kOkJsRC )
  660. {
  661. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"Initializatoin from JSON file failed on '%s'.",cmStringNullGuard(jsFn));
  662. goto errLabel;
  663. }
  664. // find the 'sync' object
  665. if((jnp = cmJsonFindValue(scp->jsH,"sync",cmJsonRoot(scp->jsH),kObjectTId)) == NULL )
  666. {
  667. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"This JSON file does not have a 'sync' object.");
  668. goto errLabel;
  669. }
  670. // read the 'sync' object header
  671. if( cmJsonMemberValues( jnp, &errLabelPtr,
  672. "refDir", kStringTId, &scp->refDir,
  673. "keyDir", kStringTId, &scp->keyDir,
  674. "hopMs", kRealTId, &scp->hopMs,
  675. "array", kArrayTId, &jnp,
  676. NULL ) != kOkJsRC )
  677. {
  678. rc = _masJsonFieldNotFoundError(ctx, "sync", errLabelPtr, jsFn );
  679. goto errLabel;
  680. }
  681. // allocate the array to hold the sync array records
  682. if((scp->syncArrayCnt = cmJsonChildCount(jnp)) > 0 )
  683. scp->syncArray = cmMemResizeZ(syncRecd_t,scp->syncArray,scp->syncArrayCnt);
  684. // read each sync recd
  685. for(i=0; i<scp->syncArrayCnt; ++i)
  686. {
  687. const cmJsonNode_t* cnp = cmJsonArrayElementC(jnp,i);
  688. syncRecd_t* s = scp->syncArray + i;
  689. if( cmJsonMemberValues(cnp, &errLabelPtr,
  690. "refFn", kStringTId, &s->refFn,
  691. "refWndBegSecs",kRealTId, &s->refWndBegSecs,
  692. "refWndSecs", kRealTId, &s->refWndSecs,
  693. "keyFn", kStringTId, &s->keyFn,
  694. "keyBegSecs", kRealTId, &s->keyBegSecs,
  695. "keyEndSecs", kRealTId, &s->keyEndSecs,
  696. "keySyncIdx", kIntTId, &s->keySyncIdx,
  697. "syncDist", kRealTId, &s->syncDist,
  698. "refSmpCnt", kIntTId, &s->refSmpCnt,
  699. "keySmpCnt", kIntTId, &s->keySmpCnt,
  700. "srate", kRealTId, &s->srate,
  701. NULL) != kOkJsRC )
  702. {
  703. rc = _masJsonFieldNotFoundError(ctx, "sync record", errLabelPtr, jsFn );
  704. goto errLabel;
  705. }
  706. }
  707. errLabel:
  708. if( rc != kOkMasRC )
  709. {
  710. if( cmJsonFinalize(&scp->jsH) != kOkJsRC )
  711. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON finalization failed.");
  712. cmMemPtrFree(&scp->syncArray);
  713. }
  714. return rc;
  715. }
  716. // Form a reference window from file 0 at refBegMs:refBegMs + wndMs.
  717. // Compare each wndMs window in file 1 to this window and
  718. // record the closest match.
  719. // Notes:
  720. // fn0 = midi file
  721. // fn1 = audio file
  722. masRC_t slide_match( cmCtx_t* ctx, const cmChar_t* fn0, const cmChar_t* fn1, syncRecd_t* s, unsigned hopMs, unsigned keyEndMs )
  723. {
  724. masRC_t rc = kOkMasRC;
  725. cmAudioFileInfo_t afInfo0;
  726. cmAudioFileInfo_t afInfo1;
  727. cmRC_t afRC;
  728. unsigned wndMs = s->refWndSecs * 1000;
  729. unsigned refBegMs = s->refWndBegSecs * 1000;
  730. unsigned keyBegMs = s->keyBegSecs * 1000;
  731. cmAudioFileH_t af0H = cmNullAudioFileH;
  732. cmAudioFileH_t af1H = cmNullAudioFileH;
  733. cmSample_t *buf0 = NULL;
  734. cmSample_t *buf1 = NULL;
  735. unsigned minSmpIdx = cmInvalidIdx;
  736. double minDist = DBL_MAX;
  737. if( cmAudioFileIsValid( af0H = cmAudioFileNewOpen(fn0,&afInfo0,&afRC, &ctx->rpt ))==false)
  738. return cmErrMsg(&ctx->err,kFailMasRC,"The ref. audio file could not be opened.",cmStringNullGuard(fn0));
  739. if( cmAudioFileIsValid( af1H = cmAudioFileNewOpen(fn1,&afInfo1,&afRC, &ctx->rpt ))==false)
  740. {
  741. rc = cmErrMsg(&ctx->err,kFailMasRC,"The key audio file could not be opened.",cmStringNullGuard(fn1));
  742. goto errLabel;
  743. }
  744. assert( afInfo0.srate == afInfo1.srate );
  745. unsigned chCnt = 1;
  746. unsigned chIdx = 0;
  747. unsigned actFrmCnt = 0;
  748. unsigned wndSmpCnt = floor(wndMs * afInfo0.srate / 1000);
  749. unsigned hopSmpCnt = floor(hopMs * afInfo0.srate / 1000);
  750. unsigned smpIdx = 0;
  751. double progIdx = 0.01;
  752. unsigned keyBegSmpIdx = floor(keyBegMs * afInfo1.srate / 1000);
  753. unsigned keyEndSmpIdx = floor(keyEndMs * afInfo1.srate / 1000);
  754. unsigned hopCnt = keyEndSmpIdx==0 ? afInfo1.frameCnt / hopSmpCnt : (keyEndSmpIdx-keyBegSmpIdx) / hopSmpCnt;
  755. // make wndSmpCnt an even multiple of hopSmpCnt
  756. wndSmpCnt = (wndSmpCnt/hopSmpCnt) * hopSmpCnt;
  757. if( refBegMs != 0 )
  758. smpIdx = floor(refBegMs * afInfo0.srate / 1000);
  759. else
  760. {
  761. if( afInfo0.frameCnt >= wndSmpCnt )
  762. smpIdx = floor(afInfo0.frameCnt / 2 - wndSmpCnt/2);
  763. else
  764. {
  765. wndSmpCnt = afInfo0.frameCnt;
  766. smpIdx = 0;
  767. }
  768. }
  769. printf("wnd:%i hop:%i cnt:%i ref:%i\n",wndSmpCnt,hopSmpCnt,hopCnt,smpIdx);
  770. // seek to the location of the reference window
  771. if( cmAudioFileSeek( af0H, smpIdx ) != kOkAfRC )
  772. {
  773. rc = cmErrMsg(&ctx->err,kFailMasRC,"File seek failed while moving to ref. window in '%s'.",cmStringNullGuard(fn0));
  774. goto errLabel;
  775. }
  776. // take the center of file 1 as the key window
  777. if( cmAudioFileSeek( af1H, keyBegSmpIdx ) != kOkAfRC )
  778. {
  779. rc = cmErrMsg(&ctx->err,kFailMasRC,"File seek failed while moving to search begin location in '%s'.",cmStringNullGuard(fn1));
  780. goto errLabel;
  781. }
  782. // allocate the window buffers
  783. buf0 = cmMemAllocZ(cmSample_t,wndSmpCnt); // reference window
  784. buf1 = cmMemAllocZ(cmSample_t,wndSmpCnt); // sliding window
  785. cmSample_t* bp0 = buf0;
  786. cmSample_t* bp1 = buf1;
  787. // fill the reference window - the other buffer will be compared to this widow
  788. if( cmAudioFileReadSample(af0H, wndSmpCnt, chIdx, chCnt, &bp0, &actFrmCnt ) != kOkAfRC )
  789. {
  790. rc = cmErrMsg(&ctx->err,kFailMasRC,"Audio file read failed while reading the ref. window in '%s'.",cmStringNullGuard(fn0));
  791. goto errLabel;
  792. }
  793. // fill all except the last hopSmpCnt samples in the sliding window
  794. if( cmAudioFileReadSample(af1H, wndSmpCnt-hopSmpCnt, chIdx, chCnt, &bp1, &actFrmCnt ) != kOkAfRC )
  795. {
  796. rc = cmErrMsg(&ctx->err,kFailMasRC,"Audio file read failed while making the first search area read in '%s'.",cmStringNullGuard(fn1));
  797. goto errLabel;
  798. }
  799. smpIdx = keyBegSmpIdx;
  800. bp1 = buf1 + (wndSmpCnt - hopSmpCnt);
  801. minSmpIdx = smpIdx;
  802. unsigned i = 0;
  803. do
  804. {
  805. // read the new samples into the last hopSmpCnt ele's of the sliding buffer
  806. if( cmAudioFileReadSample(af1H, hopSmpCnt, chIdx, chCnt, &bp1, &actFrmCnt ) != kOkAfRC )
  807. break;
  808. // compare the sliding window to the ref. window
  809. double dist = distance(buf1,buf0,wndSmpCnt,minDist+1);
  810. // record the min dist
  811. if( dist < minDist )
  812. {
  813. //printf("%i %f %f %f\n",minSmpIdx,minDist,dist,minDist-dist);
  814. minSmpIdx = smpIdx;
  815. minDist = dist;
  816. }
  817. smpIdx += hopSmpCnt;
  818. // shift off the expired samples
  819. memmove(buf1, buf1 + hopSmpCnt, (wndSmpCnt-hopSmpCnt)*sizeof(cmSample_t));
  820. ++i;
  821. if( i > progIdx*hopCnt )
  822. {
  823. printf("%i ",(int)(round(progIdx*100)));
  824. fflush(stdout);
  825. progIdx += 0.01;
  826. }
  827. }while(i<hopCnt && actFrmCnt == hopSmpCnt && (keyEndSmpIdx==0 || smpIdx < keyEndSmpIdx) );
  828. errLabel:
  829. if( 0 )
  830. {
  831. cmCtx* ctxp = cmCtxAlloc(NULL,&ctx->rpt,cmLHeapNullHandle,cmSymTblNullHandle); // alloc a cmCtx object
  832. cmBinMtxFile_t* bf0p = cmBinMtxFileAlloc(ctxp,NULL,"/home/kevin/temp/bf0.bin");
  833. cmBinMtxFile_t* bf1p = cmBinMtxFileAlloc(ctxp,NULL,"/home/kevin/temp/bf1.bin");
  834. if( cmAudioFileSeek( af1H, minSmpIdx ) != kOkAfRC )
  835. goto errLabel;
  836. bp1 = buf1;
  837. if( cmAudioFileReadSample(af1H, wndSmpCnt, chIdx, chCnt, &bp1, &actFrmCnt ) != kOkAfRC )
  838. goto errLabel;
  839. cmBinMtxFileExecS(bf1p,buf1,wndSmpCnt);
  840. cmBinMtxFileExecS(bf0p,buf0,wndSmpCnt);
  841. cmBinMtxFileFree(&bf0p);
  842. cmBinMtxFileFree(&bf1p);
  843. cmCtxFree(&ctxp);
  844. }
  845. cmMemPtrFree(&buf0);
  846. cmMemPtrFree(&buf1);
  847. cmAudioFileDelete(&af0H);
  848. cmAudioFileDelete(&af1H);
  849. s->syncDist = minDist;
  850. s->keySyncIdx = minSmpIdx;
  851. s->refSmpCnt = afInfo0.frameCnt;
  852. s->keySmpCnt = afInfo1.frameCnt;
  853. s->srate = afInfo1.srate;
  854. return rc;
  855. }
  856. //
  857. // {
  858. // sync_array:
  859. // {
  860. // { <ref_fn> <wnd_beg_secs> <wnd_dur_secs> <key_fn> <key_beg_secs> }
  861. // }
  862. // }
  863. masRC_t parse_sync_cfg_file( cmCtx_t* c, const cmChar_t* fn, syncCtx_t* scp )
  864. {
  865. masRC_t rc = kOkMasRC;
  866. cmJsonNode_t* arr = NULL;
  867. const char* errLabelPtr = NULL;
  868. unsigned i,j;
  869. if( cmJsonInitializeFromFile( &scp->jsH, fn, c ) != kOkJsRC )
  870. {
  871. rc = cmErrMsg(&c->err,kJsonFailMasRC,"JSON file open failed on '%s'.",cmStringNullGuard(fn));
  872. goto errLabel;
  873. }
  874. if( cmJsonMemberValues( cmJsonRoot(scp->jsH), &errLabelPtr,
  875. "ref_dir", kStringTId, &scp->refDir,
  876. "key_dir", kStringTId, &scp->keyDir,
  877. "hop_ms", kRealTId, &scp->hopMs,
  878. "sync_array", kArrayTId, &arr,
  879. NULL ) != kOkJsRC )
  880. {
  881. rc = _masJsonFieldNotFoundError(c, "header", errLabelPtr, fn );
  882. goto errLabel;
  883. }
  884. if((scp->syncArrayCnt = cmJsonChildCount(arr)) == 0 )
  885. goto errLabel;
  886. scp->syncArray = cmMemAllocZ(syncRecd_t,scp->syncArrayCnt);
  887. for(i=0; i<cmJsonChildCount(arr); ++i)
  888. {
  889. cmJsonNode_t* ele = cmJsonArrayElement(arr,i);
  890. const cmChar_t* refFn = NULL;
  891. const cmChar_t* keyFn = NULL;
  892. double wndBegSecs = 0;
  893. double wndDurSecs = 0;
  894. double keyBegSecs = 0;
  895. double keyEndSecs = 0;
  896. cmJsRC_t jsRC = kOkJsRC;
  897. const int kSix = 6;
  898. if( cmJsonIsArray(ele) == false || cmJsonChildCount(ele) != kSix )
  899. {
  900. rc = cmErrMsg(&c->err,kJsonFailMasRC,"A 'sync_array' element record at index %i is not a 6 element array in '%s'.",i,fn);
  901. goto errLabel;
  902. }
  903. for(j=0; j<kSix; ++j)
  904. {
  905. switch(j)
  906. {
  907. case 0: jsRC = cmJsonStringValue( cmJsonArrayElement(ele,j), &refFn ); break;
  908. case 1: jsRC = cmJsonRealValue( cmJsonArrayElement(ele,j), &wndBegSecs); break;
  909. case 2: jsRC = cmJsonRealValue( cmJsonArrayElement(ele,j), &wndDurSecs); break;
  910. case 3: jsRC = cmJsonStringValue( cmJsonArrayElement(ele,j), &keyFn ); break;
  911. case 4: jsRC = cmJsonRealValue( cmJsonArrayElement(ele,j), &keyBegSecs); break;
  912. case 5: jsRC = cmJsonRealValue( cmJsonArrayElement(ele,j), &keyEndSecs); break;
  913. default:
  914. {
  915. rc = cmErrMsg(&c->err,kJsonFailMasRC,"The 'sync_array' element record contains too many fields on record index %i in '%s'.",i,fn);
  916. goto errLabel;
  917. }
  918. }
  919. if( jsRC != kOkJsRC )
  920. {
  921. rc = cmErrMsg(&c->err,kJsonFailMasRC,"The 'sync_array' element record at index %i at field index %i in '%s'.",i,j,fn);
  922. goto errLabel;
  923. }
  924. }
  925. scp->syncArray[i].refFn = refFn;
  926. scp->syncArray[i].refWndBegSecs = wndBegSecs;
  927. scp->syncArray[i].refWndSecs = wndDurSecs;
  928. scp->syncArray[i].keyFn = keyFn;
  929. scp->syncArray[i].keyBegSecs = keyBegSecs;
  930. scp->syncArray[i].keyEndSecs = keyEndSecs;
  931. //printf("beg:%f dur:%f ref:%s key:%s key beg:%f\n",wndBegSecs,wndDurSecs,refFn,keyFn,keyBegSecs);
  932. }
  933. errLabel:
  934. if( rc != kOkMasRC )
  935. {
  936. cmJsonFinalize(&scp->jsH);
  937. cmMemPtrFree(&scp->syncArray);
  938. }
  939. return rc;
  940. }
  941. unsigned findFile( const char* fn, unsigned flags, fileRecd_t* array, unsigned fcnt )
  942. {
  943. unsigned j;
  944. for(j=0; j<fcnt; ++j)
  945. {
  946. if( cmIsFlag(array[j].flags,flags) && (strcmp(array[j].fn,fn)==0) )
  947. return j;
  948. }
  949. return -1;
  950. }
  951. unsigned insertFile( const char* fn, const char* fullFn, unsigned flags, unsigned smpCnt, double srate, fileRecd_t* array, unsigned fcnt )
  952. {
  953. if( findFile(fn,flags,array,fcnt)==-1 )
  954. {
  955. array[fcnt].fn = fn;
  956. array[fcnt].fullFn = fullFn;
  957. array[fcnt].flags = flags;
  958. array[fcnt].refIdx = -1;
  959. array[fcnt].refPtr = NULL;
  960. array[fcnt].refSmpIdx = -1;
  961. array[fcnt].keySmpIdx = -1;
  962. array[fcnt].absSmpIdx = -1;
  963. array[fcnt].absBegSmpIdx = -1;
  964. array[fcnt].smpCnt = smpCnt;
  965. array[fcnt].srate = srate;
  966. ++fcnt;
  967. }
  968. return fcnt;
  969. }
  970. // calculate the absolute sample index (relative to the master file) of keySmpIdx
  971. int calcAbsSmpIdx( const fileRecd_t* f )
  972. {
  973. // if this file has no master then the absSmpIdx is 0
  974. if( f->refPtr == NULL )
  975. return 0;
  976. // if the reference is a master then f->refSmpIdx is also f->absSmpIdx
  977. if( f->refPtr->refPtr == NULL )
  978. return f->refSmpIdx;
  979. // this file has a master - recurse
  980. int v = calcAbsSmpIdx( f->refPtr );
  981. // absSmpIdx is the absSmpIdx of the reference plus the difference to this sync point
  982. // Note that both f->refSmpIdx and f->refPtr->keySmpIdx are both relative to the file pointed to by f->refPtr
  983. return v + (f->refSmpIdx - f->refPtr->keySmpIdx);
  984. }
  985. // Write an array of fileRecd_t[] (which was created from the output of sync_files()) to
  986. // a JSON file which can be read by cmTimeLineReadJson().
  987. masRC_t masWriteJsonTimeLine(
  988. cmCtx_t* ctx,
  989. double srate,
  990. fileRecd_t* fileArray,
  991. unsigned fcnt,
  992. const char* outFn )
  993. {
  994. masRC_t rc = kJsonFailMasRC;
  995. unsigned i;
  996. cmJsonH_t jsH = cmJsonNullHandle;
  997. cmJsonNode_t* jnp;
  998. // create JSON tree
  999. if( cmJsonInitialize(&jsH, ctx ) != kOkJsRC )
  1000. {
  1001. cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON time_line output tree initialization failed.");
  1002. goto errLabel;
  1003. }
  1004. // create JSON root object
  1005. if((jnp = cmJsonCreateObject(jsH,NULL )) == NULL )
  1006. {
  1007. cmErrMsg(&ctx->err,kJsonFailMasRC,"JSON time_line output tree root object create failed.");
  1008. goto errLabel;
  1009. }
  1010. // create the 'time_line' object
  1011. if((jnp = cmJsonInsertPairObject(jsH,jnp,"time_line")) == NULL )
  1012. goto errLabel;
  1013. if( cmJsonInsertPairs(jsH,jnp,
  1014. "srate",kRealTId,srate,
  1015. NULL) != kOkJsRC )
  1016. {
  1017. goto errLabel;
  1018. }
  1019. if((jnp = cmJsonInsertPairArray(jsH,jnp,"objArray")) == NULL )
  1020. goto errLabel;
  1021. for(i=0; i<fcnt; ++i)
  1022. {
  1023. const fileRecd_t* f = fileArray + i;
  1024. const cmChar_t* typeLabel = cmIsFlag(f->flags,kAudioFl) ? "af" : "mf";
  1025. const cmChar_t* refLabel = f->refPtr == NULL ? "" : f->refPtr->label;
  1026. //int childOffset = f->refPtr == NULL ? 0 : f->absBegSmpIdx - f->refPtr->absBegSmpIdx;
  1027. if( cmJsonCreateFilledObject(jsH,jnp,
  1028. "label",kStringTId,f->label,
  1029. "type", kStringTId,typeLabel,
  1030. "ref", kStringTId,refLabel,
  1031. "offset",kIntTId,f->absBegSmpIdx,
  1032. "smpCnt",kIntTId,f->smpCnt,
  1033. "trackId",kIntTId,f->groupId,
  1034. "textStr",kStringTId,f->fullFn,
  1035. NULL) == NULL )
  1036. {
  1037. goto errLabel;
  1038. }
  1039. }
  1040. if( cmJsonWrite(jsH,cmJsonRoot(jsH),outFn) != kOkJsRC )
  1041. goto errLabel;
  1042. rc = kOkMasRC;
  1043. errLabel:
  1044. if( cmJsonFinalize(&jsH) != kOkJsRC || rc == kJsonFailMasRC )
  1045. {
  1046. rc = cmErrMsg(&ctx->err,rc,"JSON fail while creating time_line file.");
  1047. }
  1048. return rc;
  1049. }
  1050. const cmChar_t* _masGenTlFileName( const cmChar_t* dir, const cmChar_t* fn, const cmChar_t* ext )
  1051. {
  1052. cmFileSysPathPart_t* pp = cmFsPathParts(fn);
  1053. if( pp == NULL )
  1054. return cmFsMakeFn(dir,fn,NULL,NULL);
  1055. fn = cmFsMakeFn(dir,pp->fnStr,ext==NULL?pp->extStr:ext,NULL);
  1056. cmFsFreePathParts(pp);
  1057. return fn;
  1058. }
  1059. enum
  1060. {
  1061. kSequenceGroupsMasFl = 0x01,
  1062. kMakeOneGroupMasFl = 0x02
  1063. };
  1064. //
  1065. // Make adjustments to fileArray[].
  1066. //
  1067. // If kSequenceGroupsMasFl is set then adjust the groups to be sequential in time by
  1068. // separating them with 'secsBetweenGroups'.
  1069. //
  1070. // If kMakeOneGroupMasFl is set then the time line object track id is set to 0 for all objects.
  1071. //
  1072. void masProcFileArray(
  1073. fileRecd_t* fileArray,
  1074. unsigned fcnt,
  1075. unsigned smpsBetweenGroups,
  1076. unsigned flags
  1077. )
  1078. {
  1079. unsigned groupCnt = 0;
  1080. unsigned groupId = cmInvalidId;
  1081. unsigned i,j;
  1082. // determine the count of groups
  1083. for(i=0; i<fcnt; ++i)
  1084. if( fileArray[i].groupId != groupId )
  1085. {
  1086. ++groupCnt;
  1087. groupId = fileArray[i].groupId;
  1088. }
  1089. /*
  1090. // Set all groups to begin at time zero.
  1091. if( cmIsFlag(flags,kZeroBaseTimeMasFl) )
  1092. {
  1093. for(i=0; i<groupCnt; ++i)
  1094. {
  1095. int minBegSmpIdx = cmInvalidIdx;
  1096. // locate the file in this group with the earliest start time
  1097. for(j=0; j<fcnt; ++j)
  1098. if( fileArray[j].groupId == i )
  1099. {
  1100. if( minBegSmpIdx == cmInvalidIdx || fileArray[j].absBegSmpIdx < minBegSmpIdx )
  1101. minBegSmpIdx = fileArray[j].absBegSmpIdx;
  1102. }
  1103. // offset all files in this group so that the earliest file starts at 0
  1104. for(j=0; j<fcnt; ++j)
  1105. if( fileArray[j].groupId == i )
  1106. {
  1107. printf("%i %i ",fileArray[j].groupId,fileArray[j].absBegSmpIdx);
  1108. fileArray[j].absBegSmpIdx -= minBegSmpIdx;
  1109. printf("%i\n", fileArray[j].absBegSmpIdx);
  1110. }
  1111. }
  1112. }
  1113. */
  1114. // Shift all groups to be seperated by secsBetweenGroups.
  1115. if( cmIsFlag(flags,kSequenceGroupsMasFl) )
  1116. {
  1117. int offsetSmpCnt = 0;
  1118. for(i=0; i<groupCnt; ++i)
  1119. {
  1120. int maxEndSmpIdx = 0;
  1121. for(j=0; j<fcnt; ++j)
  1122. if( fileArray[j].groupId == i )
  1123. {
  1124. if( fileArray[j].absBegSmpIdx + fileArray[j].smpCnt > maxEndSmpIdx )
  1125. maxEndSmpIdx = fileArray[j].absBegSmpIdx + fileArray[j].smpCnt;
  1126. if( fileArray[j].refPtr == NULL )
  1127. fileArray[j].absBegSmpIdx = offsetSmpCnt;
  1128. }
  1129. offsetSmpCnt += maxEndSmpIdx + smpsBetweenGroups;
  1130. }
  1131. }
  1132. // merge all groups into one group
  1133. if( cmIsFlag(flags,kMakeOneGroupMasFl ) )
  1134. {
  1135. for(j=0; j<fcnt; ++j)
  1136. fileArray[j].groupId = 0;
  1137. }
  1138. }
  1139. masRC_t masCreateTimeLine(
  1140. cmCtx_t* ctx,
  1141. const syncCtx_t* scp,
  1142. const cmChar_t* outFn,
  1143. const cmChar_t* refDir,
  1144. const cmChar_t* keyDir,
  1145. const cmChar_t* refExt,
  1146. const cmChar_t* keyExt,
  1147. double secsBetweenGroups,
  1148. unsigned procFlags)
  1149. {
  1150. if( scp->syncArrayCnt == 0 )
  1151. return kOkMasRC;
  1152. masRC_t rc = kOkMasRC;
  1153. unsigned i;
  1154. unsigned gcnt = 0;
  1155. unsigned fcnt = 0;
  1156. fileRecd_t fileArray[2*scp->syncArrayCnt];
  1157. // fill in the file array
  1158. for(i=0; i<scp->syncArrayCnt; ++i)
  1159. {
  1160. const syncRecd_t* s = scp->syncArray + i;
  1161. //printf("beg:%f sync:%i dist:%f ref:%s key:%s \n",s->keyBegSecs,s->keySyncIdx,s->syncDist,s->refFn,s->keyFn);
  1162. // insert the reference (master) file prior to the dependent (slave) file
  1163. const char* fn0 = s->keyBegSecs == 0 ? s->refFn : s->keyFn;
  1164. const char* fn1 = s->keyBegSecs == 0 ? s->keyFn : s->refFn;
  1165. unsigned fl0 = s->keyBegSecs == 0 ? kMidiFl : kAudioFl;
  1166. unsigned fl1 = s->keyBegSecs == 0 ? kAudioFl : kMidiFl;
  1167. unsigned sn0 = s->keyBegSecs == 0 ? s->refSmpCnt : s->keySmpCnt;
  1168. unsigned sn1 = s->keyBegSecs == 0 ? s->keySmpCnt : s->refSmpCnt;
  1169. const char* dr0 = s->keyBegSecs == 0 ? refDir : keyDir;
  1170. const char* dr1 = s->keyBegSecs == 0 ? keyDir : refDir;
  1171. const char* ex0 = s->keyBegSecs == 0 ? refExt : keyExt;
  1172. const char* ex1 = s->keyBegSecs == 0 ? keyExt : refExt;
  1173. const char* ffn0 = _masGenTlFileName( dr0, fn0, ex0 );
  1174. const char* ffn1 = _masGenTlFileName( dr1, fn1, ex1 );
  1175. fcnt = insertFile( fn0, ffn0, fl0, sn0, s->srate, fileArray, fcnt);
  1176. fcnt = insertFile( fn1, ffn1, fl1, sn1, s->srate, fileArray, fcnt);
  1177. }
  1178. // locate the reference file in each sync recd
  1179. for(i=0; i<scp->syncArrayCnt; ++i)
  1180. {
  1181. const syncRecd_t* s = scp->syncArray + i;
  1182. unsigned mfi = findFile( s->refFn, kMidiFl, fileArray, fcnt );
  1183. unsigned afi = findFile( s->keyFn, kAudioFl, fileArray, fcnt );
  1184. assert( mfi != -1 && afi != -1 );
  1185. fileRecd_t* mfp = fileArray + mfi;
  1186. fileRecd_t* afp = fileArray + afi;
  1187. if( s->keyBegSecs == 0 )
  1188. {
  1189. // lock audio to midi
  1190. afp->refIdx = mfi;
  1191. afp->refPtr = mfp;
  1192. afp->refSmpIdx = floor( s->refWndBegSecs * s->srate );
  1193. afp->keySmpIdx = s->keySyncIdx;
  1194. }
  1195. else
  1196. {
  1197. // lock midi to audio
  1198. mfp->refIdx = afi;
  1199. mfp->refPtr = afp;
  1200. mfp->refSmpIdx = s->keySyncIdx;
  1201. mfp->keySmpIdx = floor( s->refWndBegSecs * s->srate );
  1202. }
  1203. }
  1204. // Calculate the absolute sample indexes and set groupId's.
  1205. // Note that this process depends on reference files being processed before their dependents
  1206. for(i=0; i<fcnt; ++i)
  1207. {
  1208. fileRecd_t* f = fileArray + i;
  1209. // if this is a master file
  1210. if( f->refPtr == NULL )
  1211. {
  1212. f->groupId = gcnt++;// form a new group
  1213. f->absSmpIdx = 0; // absSmpIdx is meaningless for master files becuase they do not have a sync point
  1214. f->absBegSmpIdx = 0; // the master file location is always 0
  1215. }
  1216. else // this is a slave file
  1217. {
  1218. f->absSmpIdx = calcAbsSmpIdx(f); // calc the absolute time of the sync location
  1219. //f->absBegSmpIdx = f->absSmpIdx - f->keySmpIdx; // calc the absolute begin time of the file
  1220. f->absBegSmpIdx = f->refSmpIdx - f->keySmpIdx;
  1221. f->groupId = f->refPtr->groupId; // set the group id
  1222. }
  1223. }
  1224. // At this point the absBegSmpIdx of the master file in each group is set to 0
  1225. // and the absBegSmpIdx of slave files is then set relative to 0. This means that
  1226. // some slave files may have negative offsets if they start prior to the master.
  1227. //
  1228. // Set the earliest file in the group to have an absBegSmpIdx == 0 and shift all
  1229. // other files relative to this. After this process all absBegSmpIdx values will
  1230. // be positive.
  1231. //
  1232. if(0)
  1233. {
  1234. for(i=0; i<gcnt; ++i)
  1235. {
  1236. int begSmpIdx = -1;
  1237. int j;
  1238. // find the file in groupId==i with the earliest absolute start time
  1239. for(j=0; j<fcnt; ++j)
  1240. {
  1241. fileRecd_t* f = fileArray + j;
  1242. if( f->groupId==i && (begSmpIdx == -1 || f->absBegSmpIdx < begSmpIdx) )
  1243. begSmpIdx = f->absBegSmpIdx;
  1244. }
  1245. // subtract the earliest absolute start time from all files in groupId==i
  1246. for(j=0; j<fcnt; ++j)
  1247. {
  1248. fileRecd_t* f = fileArray + j;
  1249. if( f->groupId == i )
  1250. f->absBegSmpIdx -= begSmpIdx;
  1251. }
  1252. }
  1253. }
  1254. // fill in the text label assoc'd with each file
  1255. unsigned acnt = 0;
  1256. unsigned mcnt = 0;
  1257. for(i=0; i<fcnt; ++i)
  1258. {
  1259. fileRecd_t* f = fileArray + i;
  1260. if( cmIsFlag(f->flags,kAudioFl) )
  1261. snprintf(f->label,kLabelCharCnt,"af-%i",acnt++);
  1262. else
  1263. {
  1264. if( cmIsFlag(f->flags,kMidiFl) )
  1265. snprintf(f->label,kLabelCharCnt,"mf-%i",mcnt++);
  1266. else
  1267. { assert(0); }
  1268. }
  1269. }
  1270. if( fcnt > 0 )
  1271. {
  1272. cmReal_t srate = fileArray[0].srate;
  1273. unsigned smpsBetweenGroups = floor(secsBetweenGroups * srate );
  1274. masProcFileArray(fileArray,fcnt,smpsBetweenGroups,procFlags);
  1275. rc = masWriteJsonTimeLine(ctx,fileArray[0].srate,fileArray,fcnt,outFn);
  1276. for(i=0; i<fcnt; ++i)
  1277. cmFsFreeFn(fileArray[i].fullFn);
  1278. }
  1279. return rc;
  1280. }
  1281. masRC_t sync_files( cmCtx_t* ctx, syncCtx_t* scp )
  1282. {
  1283. masRC_t rc = kOkMasRC;
  1284. unsigned i;
  1285. // for each syncRecd
  1286. for(i=0; i<scp->syncArrayCnt; ++i)
  1287. {
  1288. syncRecd_t* s = scp->syncArray + i;
  1289. // form the ref (midi) and key (audio) file names
  1290. const cmChar_t* refFn = cmFsMakeFn(scp->refDir, s->refFn, NULL, NULL);
  1291. const cmChar_t* keyFn = cmFsMakeFn(scp->keyDir, s->keyFn, NULL, NULL);
  1292. double keyEndSecs = s->keyEndSecs;
  1293. // if the cur key fn is the same as the next key file. Use the search start
  1294. // location (keyBegSecs) of the next sync recd as the search end
  1295. // location for this file.
  1296. if( i < scp->syncArrayCnt-1 && strcmp(s->keyFn, scp->syncArray[i+1].keyFn) == 0 )
  1297. {
  1298. keyEndSecs = scp->syncArray[i+1].keyBegSecs;
  1299. if( keyEndSecs < s->keyBegSecs )
  1300. {
  1301. rc = cmErrMsg(&ctx->err,kParamErrMasRC,"The key file search area start times for for multiple sync records referencing the the same key file should increment in time.");
  1302. }
  1303. }
  1304. masRC_t rc0;
  1305. if((rc0 = slide_match(ctx,refFn,keyFn,s,scp->hopMs,floor(keyEndSecs*1000))) != kOkMasRC)
  1306. {
  1307. cmErrMsg(&ctx->err,rc0,"Slide match failed on Ref:%s Key:%s.",cmStringNullGuard(refFn),cmStringNullGuard(keyFn));
  1308. rc = rc0;
  1309. }
  1310. printf("\nbeg:%f end:%f sync:%i dist:%f ref:%s key:%s \n",s->keyBegSecs,keyEndSecs,s->keySyncIdx,s->syncDist,refFn,keyFn);
  1311. cmFsFreeFn(keyFn);
  1312. cmFsFreeFn(refFn);
  1313. }
  1314. return rc;
  1315. }
  1316. void masSyncCtxInit(syncCtx_t* scp)
  1317. {
  1318. memset(scp,0,sizeof(syncCtx_t));
  1319. scp->jsH = cmJsonNullHandle;
  1320. }
  1321. masRC_t masSyncCtxFinalize(cmCtx_t* ctx, syncCtx_t* scp)
  1322. {
  1323. masRC_t rc = kOkMasRC;
  1324. if( cmJsonFinalize(&scp->jsH) != kOkJsRC )
  1325. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"syncCtx JSON finalization failed.");
  1326. cmMemFree(scp->syncArray);
  1327. scp->syncArrayCnt = 0;
  1328. return rc;
  1329. }
  1330. masRC_t masMidiToImpulse( cmCtx_t* ctx, const masPgmArgs_t* p )
  1331. {
  1332. assert(p->input!=NULL && p->output!=NULL);
  1333. if( cmFsIsDir(p->input) )
  1334. return fileDriver(ctx, kMidiToAudioSelId, p->input, p->output, p->srate, 0, NULL );
  1335. return midiToAudio(ctx, p->input, p->output, p->srate );
  1336. }
  1337. masRC_t masAudioToOnset( cmCtx_t* ctx, const masPgmArgs_t* p )
  1338. {
  1339. assert(p->input!=NULL && p->output!=NULL);
  1340. if( cmFsIsDir(p->input) )
  1341. return fileDriver(ctx, kAudioOnsetSelId, p->input, p->output, 0, 0, &p->onsetCfg );
  1342. return audioToOnset(ctx, p->input, p->output, &p->onsetCfg );
  1343. }
  1344. masRC_t masConvolve( cmCtx_t* ctx, const masPgmArgs_t* p )
  1345. {
  1346. assert(p->input!=NULL && p->output!=NULL);
  1347. if( cmFsIsDir(p->input) )
  1348. return fileDriver(ctx, kConvolveSelId, p->input, p->output, 0, p->wndMs, NULL );
  1349. return convolve(ctx, p->input, p->output, p->wndMs );
  1350. }
  1351. masRC_t masSync( cmCtx_t* ctx, const masPgmArgs_t* p )
  1352. {
  1353. masRC_t rc = kOkMasRC,rc0;
  1354. syncCtx_t sc;
  1355. assert(p->input!=NULL && p->output!=NULL);
  1356. masSyncCtxInit(&sc);
  1357. if( (rc = parse_sync_cfg_file(ctx, p->input, &sc )) == kOkMasRC )
  1358. if((rc = sync_files(ctx, &sc )) == kOkMasRC )
  1359. rc = write_sync_json(ctx,&sc,p->output);
  1360. rc0 = masSyncCtxFinalize(ctx,&sc);
  1361. return rc!=kOkMasRC ? rc : rc0;
  1362. }
  1363. masRC_t masGenTimeLine( cmCtx_t* ctx, const masPgmArgs_t* p )
  1364. {
  1365. masRC_t rc,rc0;
  1366. syncCtx_t sc;
  1367. if( p->refDir == NULL )
  1368. return cmErrMsg(&ctx->err,kParamErrMasRC,"A directory must be provided to locate the audio and MIDI files. See the program parameter 'ref-dir'.");
  1369. if( p->keyDir == NULL )
  1370. return cmErrMsg(&ctx->err,kParamErrMasRC,"A directory must be provided to locate the audio and MIDI files. See the program parameter 'key-dir'.");
  1371. assert(p->input!=NULL && p->output!=NULL);
  1372. masSyncCtxInit(&sc);
  1373. if((rc = read_sync_json(ctx,&sc,p->input)) != kOkMasRC )
  1374. goto errLabel;
  1375. // TODO: Add these as program options, also add a --dry-run option
  1376. //
  1377. unsigned procFlags = 0; //kZeroBaseTimeMasFl | kSequenceGroupsMasFl | kMakeOneGroupMasFl;
  1378. double secsBetweenGroups = 60.0;
  1379. if((rc = masCreateTimeLine(ctx, &sc, p->output, p->refDir, p->keyDir, p->refExt, p->keyExt, secsBetweenGroups, procFlags)) != kOkMasRC )
  1380. goto errLabel;
  1381. errLabel:
  1382. rc0 = masSyncCtxFinalize(ctx,&sc);
  1383. return rc!=kOkMasRC ? rc : rc0;
  1384. }
  1385. // Given a time line file and a marker file, insert the markers in the time line and
  1386. // then write the time line to an output file. The marker file must have the following format:
  1387. //{
  1388. // markerArray : [
  1389. // { sect:1 beg:630.0 end:680.0 label:"Sec 3 m10"},
  1390. // { sect:3 beg:505.1 end:512.15 label:"Sec 4 m12"},
  1391. // { sect:4 beg:143.724490 end:158.624322 label:"Sec 6, 6a m14-16, #2 (A) slower tempo"},
  1392. // ]
  1393. // }
  1394. //
  1395. // NOTES:
  1396. // 1) beg/end are in seconds,
  1397. // 2) 'sect' refers to the audio file number (e.g. "Piano_01.wav,Piano_03.wav,Piano_04.wav")
  1398. //
  1399. masRC_t masLoadMarkers( cmCtx_t* ctx, const masPgmArgs_t* p )
  1400. {
  1401. assert(p->input!=NULL);
  1402. assert(p->markFn!=NULL);
  1403. assert(p->output!=NULL);
  1404. masRC_t rc = kOkMasRC;
  1405. const cmChar_t* tlFn = p->input;
  1406. const cmChar_t* mkFn = p->markFn;
  1407. const cmChar_t* outFn = p->output;
  1408. const cmChar_t* afFmtStr = "/home/kevin/media/audio/20110723-Kriesberg/Audio Files/Piano 3_%02.2i.wav";
  1409. cmTlH_t tlH = cmTimeLineNullHandle;
  1410. cmJsonH_t jsH = cmJsonNullHandle;
  1411. cmJsonNode_t* anp = NULL;
  1412. // create the time line
  1413. if( cmTimeLineInitializeFromFile(ctx, &tlH, NULL, NULL, tlFn, p->prefixPath ) != kOkTlRC )
  1414. return cmErrMsg(&ctx->err,kTimeLineFailMasRC,"Time line created failed on '%s'.", cmStringNullGuard(tlFn));
  1415. // open the marker file
  1416. if( cmJsonInitializeFromFile(&jsH, mkFn, ctx ) != kOkJsRC )
  1417. {
  1418. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"Marker file open failed on '%s'.",cmStringNullGuard(mkFn));
  1419. goto errLabel;
  1420. }
  1421. // locate the marker array in the marker file
  1422. if((anp = cmJsonFindValue(jsH,"markerArray",NULL,kArrayTId)) == NULL )
  1423. {
  1424. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"The marker file is missing a 'markerArray' node in '%s'.",cmStringNullGuard(mkFn));
  1425. goto errLabel;
  1426. }
  1427. unsigned i;
  1428. unsigned markerCnt = cmJsonChildCount(anp);
  1429. for(i=0; i<markerCnt; ++i)
  1430. {
  1431. int sectId;
  1432. double begSecs;
  1433. double endSecs;
  1434. char* markText = NULL;
  1435. const char* errLabel = NULL;
  1436. cmJsRC_t jsRC;
  1437. // read the ith marker record
  1438. if((jsRC = cmJsonMemberValues(cmJsonArrayElementC(anp,i), &errLabel,
  1439. "sect", kIntTId, &sectId,
  1440. "beg", kRealTId, &begSecs,
  1441. "end", kRealTId, &endSecs,
  1442. "label", kStringTId, &markText,
  1443. NULL)) != kOkJsRC )
  1444. {
  1445. if( errLabel != NULL )
  1446. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"The field '%s' was missing on the marker record at index %i.",errLabel,i);
  1447. else
  1448. rc = cmErrMsg(&ctx->err,kJsonFailMasRC,"An error occurred while reading the marker record at index %i.",i);
  1449. goto errLabel;
  1450. }
  1451. cmChar_t* afFn = cmTsPrintfS(afFmtStr,sectId);
  1452. cmTlAudioFile_t* tlop;
  1453. // find the audio file this marker refers to in the time line
  1454. if(( tlop = cmTimeLineFindAudioFile(tlH,afFn)) == NULL )
  1455. cmErrWarnMsg(&ctx->err,kParamErrMasRC,"The audio file '%s' associated with the marker record at index %i could not be found in the time line.",cmStringNullGuard(afFn),i);
  1456. else
  1457. {
  1458. // convert the marker seconds to samples
  1459. unsigned begSmpIdx = floor(cmTimeLineSampleRate(tlH) * begSecs);
  1460. unsigned durSmpCnt = floor(cmTimeLineSampleRate(tlH) * endSecs) - begSmpIdx;
  1461. // insert the marker into the time line
  1462. if( cmTimeLineInsert(tlH,cmTsPrintfS("Mark %i",i),kMarkerTlId,markText,begSmpIdx,durSmpCnt,tlop->obj.name, tlop->obj.seqId) != kOkTlRC )
  1463. {
  1464. rc = cmErrMsg(&ctx->err,kTimeLineFailMasRC,"Marker record insertion failed for marker at record index %i.",i);
  1465. goto errLabel;
  1466. }
  1467. }
  1468. }
  1469. // write the time line as a JSON file
  1470. if( cmTimeLineWrite(tlH,outFn) != kOkTlRC )
  1471. {
  1472. rc = cmErrMsg(&ctx->err,kTimeLineFailMasRC,"Time line write to '%s'. failed.",cmStringNullGuard(outFn));
  1473. goto errLabel;
  1474. }
  1475. errLabel:
  1476. cmJsonFinalize(&jsH);
  1477. cmTimeLineFinalize(&tlH);
  1478. return rc;
  1479. }
  1480. masRC_t masTestStub( cmCtx_t* ctx, const masPgmArgs_t* p )
  1481. {
  1482. //return masSync(ctx,p);
  1483. masRC_t rc = kOkMasRC;
  1484. const char* scFn = "/home/kevin/src/mas/src/data/mod0.txt";
  1485. const char* tlFn = "/home/kevin/src/mas/src/data/tl3.js";
  1486. const char* mdDir= "/home/kevin/media/audio/20110723-Kriesberg/midi";
  1487. if(0)
  1488. {
  1489. cmMidiByte_t x[] = { 37, 65, 87 };
  1490. midiStringSearch(ctx, mdDir, x, sizeof(x)/sizeof(x[0]) );
  1491. return rc;
  1492. }
  1493. if(1)
  1494. {
  1495. const cmChar_t* aFn = "/Users/kevin/temp/mas/sine_96k_24bits.aif";
  1496. double srate = 96000;
  1497. unsigned bits = 24;
  1498. double hz = 1;
  1499. double gain = 1;
  1500. double secs = 1;
  1501. cmAudioFileSine( ctx, aFn, srate, bits, hz, gain, secs );
  1502. return rc;
  1503. }
  1504. cmTimeLinePrintFn(ctx, tlFn, p->prefixPath, &ctx->rpt );
  1505. return rc;
  1506. //cmScoreSyncTimeLineTest(ctx, tlFn, scFn );
  1507. //return rc;
  1508. cmScoreTest(ctx,scFn);
  1509. return rc;
  1510. cmTimeLineTest(ctx,tlFn,p->prefixPath);
  1511. return rc;
  1512. //const char* inFn = "/home/kevin/temp/mas/out0.bin";
  1513. //const char* faFn = "/home/kevin/temp/mas/file0.bin";
  1514. //const char* outFn = "/home/kevin/src/mas/src/data/file0.js";
  1515. //const char* mdir = "/home/kevin/media/audio/20110723-Kriesberg/midi";
  1516. //const char* adir = "/home/kevin/media/audio/20110723-Kriesberg/Audio Files";
  1517. //createFileArray(ctx, inFn, outFn );
  1518. //printFileArray( ctx, faFn, outFn, adir, mdir);
  1519. return rc;
  1520. }
  1521. int main( int argc, char* argv[] )
  1522. {
  1523. // initialize the heap check library
  1524. bool memDebugFl = cmDEBUG_FL;
  1525. unsigned memPadByteCnt = memDebugFl ? 8 : 0;
  1526. unsigned memAlignByteCnt = 16;
  1527. unsigned memFlags = memDebugFl ? kTrackMmFl | kDeferFreeMmFl | kFillUninitMmFl : 0;
  1528. masRC_t rc = kOkMasRC;
  1529. cmPgmOptH_t poH = cmPgmOptNullHandle;
  1530. cmCtx_t ctx;
  1531. masPgmArgs_t args;
  1532. enum
  1533. {
  1534. kInputFileSelId = kBasePoId,
  1535. kOutputFileSelId,
  1536. kExecSelId,
  1537. kWndMsSelId,
  1538. kHopFactSelId,
  1539. kAudioChIdxSelId,
  1540. kWndFrmCntSelId,
  1541. kPreWndMultSelId,
  1542. kThresholdSelId,
  1543. kMaxFreqHzSelId,
  1544. kFiltCoeffSelId,
  1545. kPreDlyMsSelId,
  1546. kMedFltWndMsSelId,
  1547. kFilterSelId,
  1548. kSmthFiltSelId,
  1549. kMedianFiltSelId,
  1550. kSrateSelId,
  1551. kRefDirSelId,
  1552. kKeyDirSelId,
  1553. kRefExtSelId,
  1554. kKeyExtSelId,
  1555. kMarkFnSelId,
  1556. kPrefixPathSelId,
  1557. };
  1558. const cmChar_t helpStr0[] =
  1559. {
  1560. // 1 2 3 4 5 6 7 8
  1561. "Usage: mas -{m|a|c} -i 'input' -o 'output' <options>\n\n"
  1562. };
  1563. const cmChar_t helpStr1[] =
  1564. {
  1565. // 1 2 3 4 5 6 7 8
  1566. "If --input option specifies a directory then all files in the directory are\n"
  1567. "taken as input files. In this case the names of the output files are generated\n"
  1568. "automatically and the --ouptut option must specify a directory to receive all\n"
  1569. "the output files.\n\nIf the --input option specifies a file then the --output\n"
  1570. " option should specifiy the complete name of the output file.\n"
  1571. };
  1572. memset(&args,0,sizeof(args));
  1573. cmCtxSetup(&ctx,"Project",print,print,NULL,memPadByteCnt,memAlignByteCnt,memFlags);
  1574. cmMdInitialize( memPadByteCnt, memAlignByteCnt, memFlags, &ctx.rpt );
  1575. cmFsInitialize( &ctx, "mas" );
  1576. cmTsInitialize( &ctx );
  1577. cmPgmOptInitialize(&ctx,&poH,helpStr0,helpStr1);
  1578. // poH numId charId wordId flags enumId default return ptr cnt help string
  1579. cmPgmOptInstallStr( poH, kInputFileSelId, 'i', "input", kReqPoFl, NULL, &args.input, 1, "Input file or directory." );
  1580. cmPgmOptInstallStr( poH, kOutputFileSelId, 'o', "output", kReqPoFl, NULL, &args.output, 1, "Output file or directory." );
  1581. cmPgmOptInstallEnum(poH, kExecSelId, 'm', "midi_to_impulse", kReqPoFl, kMidiToAudioSelId,cmInvalidId, &args.selId, 1, "Create an audio impulse file from a MIDI file.","Command Code" );
  1582. cmPgmOptInstallEnum(poH, kExecSelId, 'a', "onsets", kReqPoFl, kAudioOnsetSelId, cmInvalidId, &args.selId, 1, "Create an audio impulse file from the audio event onset detector.",NULL );
  1583. cmPgmOptInstallEnum(poH, kExecSelId, 'c', "convolve", kReqPoFl, kConvolveSelId, cmInvalidId, &args.selId, 1, "Convolve a Hann window with an audio file.",NULL );
  1584. cmPgmOptInstallEnum(poH, kExecSelId, 'y', "sync", kReqPoFl, kSyncSelId, cmInvalidId, &args.selId, 1, "Run a synchronization process based on a JSON sync control file and generate a sync. output JSON file..",NULL);
  1585. cmPgmOptInstallEnum(poH, kExecSelId, 'g', "gen_time_line", kReqPoFl, kGenTimeLineSelId,cmInvalidId, &args.selId, 1, "Generate a time-line JSON file from a sync. output JSON file.",NULL);
  1586. cmPgmOptInstallEnum(poH, kExecSelId, 'k', "markers", kReqPoFl, kLoadMarkersSelId,cmInvalidId, &args.selId, 1, "Read markers into the time line.",NULL);
  1587. cmPgmOptInstallEnum(poH, kExecSelId, 'T', "test", kReqPoFl, kTestStubSelId, cmInvalidId, &args.selId, 1, "Run the test stub.",NULL ),
  1588. cmPgmOptInstallDbl( poH, kWndMsSelId, 'w', "wnd_ms", 0, 42.0, &args.wndMs, 1, "Analysis window look in milliseconds." );
  1589. cmPgmOptInstallUInt(poH, kHopFactSelId, 'f', "hop_factor", 0, 4, &args.onsetCfg.hopFact, 1, "Sliding window hop factor 1=1:1 2=1:2 4=1:4 ...");
  1590. cmPgmOptInstallUInt(poH, kAudioChIdxSelId, 'u', "ch_idx", 0, 0, &args.onsetCfg.audioChIdx, 1, "Audio channel index.");
  1591. cmPgmOptInstallUInt(poH, kWndFrmCntSelId, 'r', "wnd_frm_cnt", 0, 3, &args.onsetCfg.wndFrmCnt, 1, "Audio onset window frame count.");
  1592. cmPgmOptInstallDbl( poH, kPreWndMultSelId, 'x', "wnd_pre_mult", 0, 3, &args.onsetCfg.preWndMult, 1, "Audio onset pre-window multiplier.");
  1593. cmPgmOptInstallDbl( poH, kThresholdSelId, 't', "threshold", 0, 0.6, &args.onsetCfg.threshold, 1, "Audio onset threshold value.");
  1594. cmPgmOptInstallDbl( poH, kMaxFreqHzSelId, 'z', "max_frq_hz", 0, 20000, &args.onsetCfg.maxFrqHz, 1, "Audio onset maximum analysis frequency.");
  1595. cmPgmOptInstallDbl( poH, kFiltCoeffSelId, 'e', "filt_coeff", 0, 0.7, &args.onsetCfg.filtCoeff, 1, "Audio onset smoothing filter coefficient.");
  1596. cmPgmOptInstallDbl( poH, kPreDlyMsSelId, 'd', "pre_delay_ms", 0, 0, &args.onsetCfg.preDelayMs, 1, "Move each detected audio onset backwards in time by this amount.");
  1597. cmPgmOptInstallDbl( poH, kMedFltWndMsSelId, 'l',"med_flt_wnd_ms", 0, 50, &args.onsetCfg.medFiltWndMs, 1, "Length of the onset detection median filter. Ignored if the median filter is not used.");
  1598. cmPgmOptInstallEnum(poH, kFilterSelId, 'b', "smooth_filter", 0, kSmthFiltSelId, cmInvalidId, &args.onsetCfg.filterId, 1, "Apply a smoothing filter to the onset detection function.","Audio onset filter");
  1599. cmPgmOptInstallEnum(poH, kFilterSelId, 'n', "median_filter", 0, kMedianFiltSelId, cmInvalidId, &args.onsetCfg.filterId, 1, "Apply a median filter to the onset detections function.", NULL );
  1600. cmPgmOptInstallDbl( poH, kSrateSelId, 's', "sample_rate", 0, 44100, &args.srate, 1, "MIDI to impulse output sample rate.");
  1601. cmPgmOptInstallStr( poH, kRefDirSelId, 'R', "ref_dir", 0, NULL, &args.refDir, 1, "Location of the reference files. Only used with 'gen_time_line'.");
  1602. cmPgmOptInstallStr( poH, kKeyDirSelId, 'K', "key_dir", 0, NULL, &args.keyDir, 1, "Location of the key files. Only used with 'gen_time_line'.");
  1603. cmPgmOptInstallStr( poH, kRefExtSelId, 'M', "ref_ext", 0, NULL, &args.refExt, 1, "Reference file extension. Only used with 'gen_time_line'.");
  1604. cmPgmOptInstallStr( poH, kKeyExtSelId, 'A', "key_ext", 0, NULL, &args.keyExt, 1, "Key file extension. Only used with 'gen_time_line'.");
  1605. cmPgmOptInstallStr( poH, kMarkFnSelId, 'E', "mark_fn", 0, NULL, &args.markFn, 1, "Marker file name");
  1606. cmPgmOptInstallStr( poH, kPrefixPathSelId, 'P', "prefix_path", 0, NULL, &args.prefixPath, 1, "Time Line data file prefix path");
  1607. if((rc = cmPgmOptRC(poH,kOkPoRC)) != kOkPoRC )
  1608. goto errLabel;
  1609. if( cmPgmOptParse(poH, argc, argv ) != kOkPoRC )
  1610. goto errLabel;
  1611. if( cmPgmOptHandleBuiltInActions(poH,&ctx.rpt) )
  1612. {
  1613. switch( args.selId )
  1614. {
  1615. case kMidiToAudioSelId:
  1616. masMidiToImpulse(&ctx,&args);
  1617. break;
  1618. case kAudioOnsetSelId:
  1619. args.onsetCfg.wndMs = args.wndMs;
  1620. switch( args.onsetCfg.filterId )
  1621. {
  1622. case kSmthFiltSelId: args.onsetCfg.filterId = kSmoothFiltId; break;
  1623. case kMedianFiltSelId: args.onsetCfg.filterId = kMedianFiltId; break;
  1624. default:
  1625. args.onsetCfg.filterId = 0;
  1626. }
  1627. masAudioToOnset(&ctx,&args);
  1628. break;
  1629. case kConvolveSelId:
  1630. masConvolve(&ctx,&args);
  1631. break;
  1632. case kSyncSelId:
  1633. masSync(&ctx,&args);
  1634. break;
  1635. case kGenTimeLineSelId:
  1636. masGenTimeLine(&ctx,&args);
  1637. break;
  1638. case kLoadMarkersSelId:
  1639. masLoadMarkers(&ctx,&args);
  1640. break;
  1641. case kTestStubSelId:
  1642. masTestStub(&ctx,&args);
  1643. break;
  1644. default:
  1645. { assert(0); }
  1646. }
  1647. }
  1648. errLabel:
  1649. cmPgmOptFinalize(&poH);
  1650. cmTsFinalize();
  1651. cmFsFinalize();
  1652. cmMdReport( kIgnoreNormalMmFl );
  1653. cmMdFinalize();
  1654. return rc;
  1655. }
  1656. /*
  1657. Use Cases:
  1658. 1) Synchronize Audio to MIDI based on onset patterns:
  1659. a) Convert MIDI to audio impulse files:
  1660. mas -m -i <midi_dir | midi_fn > -o <out_dir> -s <srate>
  1661. Notes:
  1662. 1) If <midi_dir> is given then use all files
  1663. in the directory as input otherwise convert a
  1664. single file.
  1665. 2) The files written to <out_dir> are audio files with
  1666. impulses written at the location of note on msg's.
  1667. The amplitude of the the impulse is velocity/127.
  1668. b) Convert the onsets in audio file(s) to audio impulse
  1669. file(s).
  1670. mas -a -i <audio_dir | audio_fn > -o <out_dir>
  1671. -w <wndMs> -f <hopFactor> -u <chIdx> -r <wnd_frm_cnt>
  1672. -x <preWndMult> -t <threshold> -z <maxFrqHz> -e <filtCoeff>
  1673. 1) If <audio_dir> is given then use all files
  1674. in the directory as input otherwise convert a
  1675. single file.
  1676. 2) The onset detector uses a spectral flux based
  1677. algorithm.
  1678. See cmOnset.h/.c for an explanation of the
  1679. onset detection parameters.
  1680. c) Convolve impulse files created in a) and b) with a
  1681. Hann window to widen the impulse width.
  1682. mas -c -i <audio_dir | audio_fn > -o <out_dir> -w <wndMs>
  1683. 1) If <audio_dir> is given then use all files
  1684. in the directory as input otherwise convert a
  1685. single file.
  1686. 2) <wndMs> gives the width of the Hann window.
  1687. d) Synchronize MIDI and Audio based convolved impulse
  1688. files based on their onset patterns.
  1689. mas -y -i <sync_cfg_fn.js> -o <sync_out_fn.js>
  1690. 1) The <sync_cfg_fn.js> file has the following format:
  1691. {
  1692. ref_dir : "/home/kevin/temp/mas/midi_conv" // location of ref files
  1693. key_dir : "/home/kevin/temp/mas/onset_conv" // location of key files
  1694. hop_ms : 25 // sliding window increment
  1695. sync_array :
  1696. [
  1697. // ref_fn wnd_beg_secs wnd_dur_secs key_fn key_beg_secs, key_end_secs
  1698. [ "1.aif", 678, 113, "Piano 3_01.aif", 239.0, 417.0],
  1699. [ "3.aif", 524, 61, "Piano 3_06.aif", 556.0, 619.0],
  1700. ]
  1701. }
  1702. Notes:
  1703. a. The 'window' is the section of the reference file which is compared
  1704. to the key file search area <key_beg_secs> to <key_end_secs> by sliding it
  1705. in increments of 'hop_ms' samples.
  1706. b. Set 'key_end_secs' to 0 to search to the end of the file.
  1707. c. When one key file matches to multiple reference files the
  1708. key files sync recd should be listed consecutively. This way
  1709. the earlier searches can stop when they reach the beginning
  1710. of the next sync records search region. See sync_files().
  1711. Note that by setting <key_beg_secs> to a non-zero value
  1712. as occurs in the multi-key-file case has a subtle effect of
  1713. changing the master-slave relationship between the reference
  1714. an key file.
  1715. In general the reference file is the master and the key file
  1716. is the slave. When a non-zero <key_beg_secs> is given however
  1717. this relationship reverses. See masCreateTimeLine() for
  1718. how this is used to assign file group id's during the
  1719. time line creation.
  1720. 3) The <sync_out_fn.js> has the following form.
  1721. {
  1722. "sync" :
  1723. {
  1724. "refDir" : "/home/kevin/temp/mas/midi_conv"
  1725. "keyDir" : "/home/kevin/temp/mas/onset_conv"
  1726. "hopMs" : 25.000000
  1727. "array" :
  1728. [
  1729. //
  1730. // sync results for "1.aif" to "Piano 3_01.aif"
  1731. //
  1732. {
  1733. // The following block of fields were copied from <sync_cfg_fn.js>.
  1734. "refFn" : "1.aif"
  1735. "refWndBegSecs" : 678.000000
  1736. "refWndSecs" : 113.000000
  1737. "keyFn" : "Piano 3_01.aif"
  1738. "keyBegSecs" : 239.000000
  1739. "keyEndSecs" : 417.000000
  1740. // Sync. location of the 'window' in the key file.
  1741. // Sample index into the key file which matches to the first sample
  1742. // in the reference window.
  1743. "keySyncIdx" : 25768800 // Offset into the key file of the best match.
  1744. "syncDist" : 4184.826108 // Match distance score for the sync location.
  1745. "refSmpCnt" : 200112000 // Count of samples in the reference file.
  1746. "keySmpCnt" : 161884800 // Count of samples in the key file.
  1747. "srate" : 96000.000000 // Sample rate of the reference and key file.
  1748. },
  1749. ]
  1750. }
  1751. }
  1752. 2) Create a time line from the results of a synchronization. A time line is a data structure
  1753. (See cmTimeLine.h/.c) which maintains a time based ordering of Audio files, MIDI files,
  1754. and arbitrary markers.
  1755. mas -g -i <sync_out_fn.js> -o <time_line_out_fn.js> -R <ref_dir> -K <key_dir> -M <ref_ext> -A <key_ext>
  1756. <sync_out_fn.js> The output file produced as a result of a previous MIDI <-> Audio synchronization.
  1757. <ref_dir> Location of the reference files (MIDI) used for the synchronization.
  1758. <ref_ext> File extension used by the reference files.
  1759. <key_dir> Locate of the key files (Audio) used for the synchronization.
  1760. <key_ext> File extension used by the key files.
  1761. 1) The time line 'trackId' assigned to each time line object is based on the files
  1762. 'groupId'. A common group id is given to sets of files which are
  1763. locked in time relative to one another. For example
  1764. if file B and C are synced to master file A and
  1765. file D is synced to file E which is synced to master
  1766. file F. Then files A,B,C will be given one group
  1767. id and files D,E and F will be given another group id.
  1768. (See masCreateTimeLine()).
  1769. 2) The time line object 'offset' values gives the offset in samples where the object
  1770. begins relative to other objects in the group. Note that the master object in the
  1771. group may not begin at offset 0 if there are slave objects which start before it.
  1772. */
  1773. /* MIDI File Durations (rounded to next minute)
  1774. 1 35 678 113 01 0
  1775. 2 30 53 114 03 0
  1776. 655 116 04 0
  1777. 1216 102 05 0
  1778. 3 19 524 61 06 0
  1779. 958 40 07 0
  1780. 4 15 206 54 08 0
  1781. 797 40 09 0
  1782. 5 40 491 104 11 0
  1783. 1712 109 12 0
  1784. 2291 84 13 0
  1785. 6 44 786 105 13 299
  1786. 1723 112 14 0
  1787. 7 3 99 41 15 0
  1788. 8 38 521 96 17 0
  1789. 1703 71 18 0
  1790. 9 31 425 104 19 0
  1791. 10 2 16 19 21 0
  1792. 12 10 140 87 21 222
  1793. 13 14 377 58 21 942
  1794. 15 18 86 71 21 1975
  1795. 593 79 22 0
  1796. 16-2 16 211 75 23 0
  1797. 17-1 8 129 38 24 0
  1798. 17-2 16 381 54 26 0
  1799. 18 22 181 98 27 0
  1800. 19 22 134 57 28 0
  1801. 20 7 68 44 29 0
  1802. */