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.

cmScoreMatchGraphic.c 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. #include "cmPrefix.h"
  2. #include "cmGlobal.h"
  3. #include "cmFloatTypes.h"
  4. #include "cmRpt.h"
  5. #include "cmErr.h"
  6. #include "cmCtx.h"
  7. #include "cmMem.h"
  8. #include "cmMallocDebug.h"
  9. #include "cmLinkedHeap.h"
  10. #include "cmTime.h"
  11. #include "cmMidi.h"
  12. #include "cmLex.h"
  13. #include "cmCsv.h"
  14. #include "cmSymTbl.h"
  15. #include "cmMidiFile.h"
  16. #include "cmAudioFile.h"
  17. #include "cmTimeLine.h"
  18. #include "cmText.h"
  19. #include "cmFile.h"
  20. #include "cmScore.h"
  21. #include "cmScoreMatchGraphic.h"
  22. enum
  23. {
  24. kLocSmgFl = 0x0001,
  25. kBarSmgFl = 0x0002,
  26. kNoteSmgFl = 0x0004,
  27. kMidiSmgFl = 0x0008,
  28. kNoMatchSmgFl = 0x0010
  29. };
  30. typedef struct cmSmgBox_str
  31. {
  32. unsigned flags;
  33. unsigned id; // csvEventId or midiUid
  34. unsigned left;
  35. unsigned top;
  36. unsigned width;
  37. unsigned height;
  38. cmChar_t* text;
  39. struct cmSmgBox_str* link;
  40. } cmSmgBox_t;
  41. typedef struct cmSmgLine_str
  42. {
  43. cmSmgBox_t* b0;
  44. cmSmgBox_t* b1;
  45. struct cmSmgLine_str* link;
  46. } cmSmgLine_t;
  47. typedef struct cmSmgLoc_str
  48. {
  49. cmSmgBox_t* bV;
  50. } cmSmgLoc_t;
  51. typedef struct
  52. {
  53. unsigned type;
  54. unsigned csvEventId;
  55. unsigned locIdx;
  56. cmSmgBox_t* box;
  57. } cmSmgSc_t;
  58. typedef struct cmSmgMatch_str
  59. {
  60. cmSmgSc_t* score;
  61. struct cmSmgMatch_str* link;
  62. } cmSmgMatch_t;
  63. typedef struct
  64. {
  65. unsigned uid;
  66. unsigned pitch;
  67. unsigned vel;
  68. cmSmgMatch_t* matchV;
  69. cmSmgBox_t* box;
  70. } cmSmgMidi_t;
  71. typedef struct
  72. {
  73. cmErr_t err;
  74. cmSmgSc_t* scV;
  75. unsigned scN;
  76. cmSmgMidi_t* mV;
  77. unsigned mN;
  78. cmSmgLoc_t* locV;
  79. unsigned locN;
  80. cmSmgLine_t* lines;
  81. } cmSmg_t;
  82. cmSmgH_t cmSmgNullHandle = cmSTATIC_NULL_HANDLE;
  83. cmSmg_t* _cmSmgHandleToPtr( cmSmgH_t h )
  84. {
  85. cmSmg_t* p = (cmSmg_t*)h.h;
  86. assert(p!=NULL);
  87. return p;
  88. }
  89. cmSmgRC_t _cmSmgFree( cmSmg_t* p )
  90. {
  91. unsigned i;
  92. for(i=0; i<p->mN; ++i)
  93. {
  94. cmSmgMatch_t* m0 = p->mV[i].matchV;
  95. cmSmgMatch_t* m1 = NULL;
  96. while(m0!=NULL)
  97. {
  98. m1 = m0->link;
  99. cmMemFree(m0);
  100. m0 = m1;
  101. }
  102. }
  103. for(i=0; i<p->locN; ++i)
  104. {
  105. cmSmgBox_t* b0 = p->locV[i].bV;
  106. cmSmgBox_t* b1 = NULL;
  107. while(b0!=NULL)
  108. {
  109. b1 = b0->link;
  110. cmMemFree(b0->text);
  111. cmMemFree(b0);
  112. b0 = b1;
  113. }
  114. }
  115. cmSmgLine_t* l0 = p->lines;
  116. cmSmgLine_t* l1 = NULL;
  117. while( l0 != NULL )
  118. {
  119. l1 = l0->link;
  120. cmMemFree(l0);
  121. l0 = l1;
  122. }
  123. cmMemFree(p->scV);
  124. cmMemFree(p->mV);
  125. cmMemFree(p->locV);
  126. cmMemFree(p);
  127. return kOkSmgRC;
  128. }
  129. cmSmgBox_t* _cmSmgInsertBox( cmSmg_t* p, unsigned locIdx, unsigned flags, unsigned id, cmChar_t* text )
  130. {
  131. assert( locIdx < p->locN );
  132. cmSmgBox_t* b = cmMemAllocZ(cmSmgBox_t,1);
  133. b->flags = flags;
  134. b->id = id;
  135. b->text = text;
  136. if( p->locV[locIdx].bV == NULL )
  137. {
  138. p->locV[locIdx].bV = b;
  139. }
  140. else
  141. {
  142. cmSmgBox_t* b0 = p->locV[locIdx].bV;
  143. while( b0->link!=NULL )
  144. b0 = b0->link;
  145. b0->link = b;
  146. }
  147. return b;
  148. }
  149. cmSmgRC_t _cmSmgInitFromScore( cmCtx_t* ctx, cmSmg_t* p, const cmChar_t* scoreFn )
  150. {
  151. cmSmgRC_t rc = kOkSmgRC;
  152. cmScH_t scH = cmScNullHandle;
  153. unsigned i,j,k;
  154. if( cmScoreInitialize(ctx,&scH,scoreFn,44100.0, NULL, 0, NULL, NULL, cmSymTblNullHandle ) != kOkScRC )
  155. return cmErrMsg(&p->err,kScoreFailSmgRC,"Score initializatio failed on '%s'.",cmStringNullGuard(scoreFn));
  156. p->scN = cmScoreEvtCount(scH);
  157. p->scV = cmMemAllocZ(cmSmgSc_t,p->scN);
  158. p->locN = cmScoreLocCount(scH);
  159. p->locV = cmMemAllocZ(cmSmgLoc_t,p->locN);
  160. // for each score location
  161. for(i=0,k=0; i<cmScoreLocCount(scH); ++i)
  162. {
  163. cmScoreLoc_t* l = cmScoreLoc(scH,i);
  164. // insert the location label box
  165. _cmSmgInsertBox(p, i, kLocSmgFl, cmInvalidId, cmTsPrintfP(NULL,"%i",i) );
  166. // for each event in location i
  167. for(j=0; j<l->evtCnt; ++j)
  168. {
  169. const cmScoreEvt_t* e = l->evtArray[j];
  170. switch( e->type)
  171. {
  172. case kBarEvtScId:
  173. case kNonEvtScId:
  174. {
  175. // Note: Mark all score boxes as 'no-match' - this will be cleared in cmScoreMatchGraphicInsertMidi().
  176. unsigned flags = kNoMatchSmgFl | (e->type==kNonEvtScId ? kNoteSmgFl : kBarSmgFl);
  177. cmChar_t* text = NULL;
  178. assert( k < p->scN );
  179. p->scV[k].type = e->type;
  180. p->scV[k].csvEventId = e->csvEventId;
  181. p->scV[k].locIdx = i;
  182. if( e->type == kBarEvtScId )
  183. text = cmTsPrintfP(NULL,"%i",e->barNumb);
  184. else
  185. text = cmMemAllocStr( cmMidiToSciPitch( e->pitch, NULL, 0));
  186. p->scV[k].box = _cmSmgInsertBox(p, i, flags, e->csvEventId, text );
  187. k += 1;
  188. }
  189. break;
  190. }
  191. }
  192. }
  193. p->scN = k;
  194. cmScoreFinalize(&scH);
  195. return rc;
  196. }
  197. cmSmgRC_t _cmSmgInitFromMidi( cmCtx_t* ctx, cmSmg_t* p, const cmChar_t* midiFn )
  198. {
  199. cmSmgRC_t rc = kOkSmgRC;
  200. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  201. unsigned i,j;
  202. if( cmMidiFileOpen(ctx, &mfH, midiFn ) != kOkMfRC )
  203. return cmErrMsg(&p->err,kMidiFileFailSmgRC,"MIDI file open failed on '%s'.",cmStringNullGuard(midiFn));
  204. const cmMidiTrackMsg_t** mV = cmMidiFileMsgArray(mfH);
  205. unsigned mN = cmMidiFileMsgCount(mfH);
  206. p->mV = cmMemAllocZ(cmSmgMidi_t,mN);
  207. p->mN = mN;
  208. for(i=0,j=0; i<mN; ++i)
  209. if( (mV[i]!=NULL) && cmMidiIsChStatus(mV[i]->status) && cmMidiIsNoteOn(mV[i]->status) && (mV[i]->u.chMsgPtr->d1>0) )
  210. {
  211. assert(j < mN);
  212. p->mV[j].uid = mV[i]->uid;
  213. p->mV[j].pitch = mV[i]->u.chMsgPtr->d0;
  214. p->mV[j].vel = mV[i]->u.chMsgPtr->d1;
  215. ++j;
  216. }
  217. p->mN = j;
  218. cmMidiFileClose(&mfH);
  219. return rc;
  220. }
  221. cmSmgRC_t cmScoreMatchGraphicAlloc( cmCtx_t* ctx, cmSmgH_t* hp, const cmChar_t* scoreFn, const cmChar_t* midiFn )
  222. {
  223. cmSmgRC_t rc;
  224. if((rc = cmScoreMatchGraphicFree(hp)) != kOkSmgRC )
  225. return rc;
  226. cmSmg_t* p = cmMemAllocZ(cmSmg_t,1);
  227. cmErrSetup(&p->err,&ctx->rpt,"ScoreMatchGraphic");
  228. if((rc = _cmSmgInitFromScore(ctx,p,scoreFn)) != kOkSmgRC )
  229. goto errLabel;
  230. if((rc = _cmSmgInitFromMidi(ctx,p,midiFn)) != kOkSmgRC )
  231. goto errLabel;
  232. hp->h = p;
  233. errLabel:
  234. if( rc != kOkSmgRC )
  235. _cmSmgFree(p);
  236. return rc;
  237. }
  238. bool cmScoreMatchGraphicIsValid( cmSmgH_t h )
  239. { return h.h != NULL; }
  240. cmSmgRC_t cmScoreMatchGraphicFree( cmSmgH_t* hp )
  241. {
  242. cmSmgRC_t rc = kOkSmgRC;
  243. if(hp==NULL || cmScoreMatchGraphicIsValid(*hp)==false)
  244. return kOkSmgRC;
  245. cmSmg_t* p = _cmSmgHandleToPtr(*hp);
  246. if((rc = _cmSmgFree(p)) != kOkSmgRC )
  247. return rc;
  248. hp->h = NULL;
  249. return rc;
  250. }
  251. bool cmScoreMatchGraphic( cmSmgH_t h )
  252. { return h.h != NULL; }
  253. cmSmgRC_t cmScoreMatchGraphicInsertMidi( cmSmgH_t h, unsigned midiUid, unsigned midiPitch, unsigned midiVel, unsigned csvScoreEventId )
  254. {
  255. cmSmg_t* p = _cmSmgHandleToPtr(h);
  256. unsigned i,j;
  257. // if this midi event did not match any score records
  258. if( csvScoreEventId == cmInvalidId )
  259. return kOkSmgRC;
  260. assert(midiUid != cmInvalidId );
  261. assert(midiPitch<128 && midiVel<128);
  262. // find the midi file record which matches the event
  263. for(i=0; i<p->mN; ++i)
  264. if( p->mV[i].uid == midiUid )
  265. {
  266. // find the score record which matches the score event id
  267. for(j=0; j<p->scN; ++j)
  268. if( p->scV[j].csvEventId == csvScoreEventId )
  269. {
  270. // create a match record
  271. cmSmgMatch_t* m = cmMemAllocZ(cmSmgMatch_t,1);
  272. m->score = p->scV + j;
  273. // mark the box associated with this score record as 'matched' by clearing the kNoMatchSmgFl
  274. p->scV[j].box->flags = cmClrFlag(p->scV[j].box->flags,kNoMatchSmgFl);
  275. // insert the match record in the midi files match list
  276. if( p->mV[i].matchV == NULL )
  277. p->mV[i].matchV = m;
  278. else
  279. {
  280. cmSmgMatch_t* m0 = p->mV[i].matchV;
  281. while( m0->link != NULL )
  282. m0 = m0->link;
  283. m0->link = m;
  284. }
  285. return kOkSmgRC;
  286. }
  287. return cmErrMsg(&p->err,kScoreFailSmgRC,"The score csv event id %i not found,",csvScoreEventId);
  288. }
  289. return cmErrMsg(&p->err,kMidiFileFailSmgRC,"MIDI uid %i not found.",midiUid);
  290. }
  291. // Create a box for each MIDI event and a line for each
  292. // match beyond the first.
  293. void _cmSmgResolveMidi( cmSmg_t* p )
  294. {
  295. unsigned prevLocIdx = 0;
  296. unsigned i;
  297. // for each midi record
  298. for(i=0; i<p->mN; ++i)
  299. {
  300. // get the first match record for this MIDI event
  301. const cmSmgMatch_t* m = p->mV[i].matchV;
  302. // get the score location for this midi event
  303. unsigned locIdx = m==NULL ? prevLocIdx : m->score->locIdx;
  304. unsigned flags = kMidiSmgFl | (m==NULL ? kNoMatchSmgFl : 0);
  305. // set the text label for this event
  306. cmChar_t* text = cmMemAllocStr( cmMidiToSciPitch( p->mV[i].pitch, NULL, 0));
  307. // insert a box to represent this midi event
  308. cmSmgBox_t* box = _cmSmgInsertBox( p, locIdx, flags, p->mV[i].uid, text );
  309. prevLocIdx = locIdx;
  310. // if this midi event matched to multiple score positions
  311. if( m != NULL && m->link != NULL )
  312. {
  313. // insert a line for each match after the first
  314. m = m->link;
  315. for(; m!=NULL; m=m->link )
  316. {
  317. cmSmgLine_t* l = cmMemAllocZ(cmSmgLine_t,1);
  318. l->b0 = box;
  319. l->b1 = m->score->box;
  320. l->link = p->lines;
  321. p->lines = l;
  322. }
  323. }
  324. }
  325. }
  326. void _cmSmgLayout( cmSmg_t* p )
  327. {
  328. unsigned i;
  329. unsigned bordX = 5;
  330. unsigned bordY = 5;
  331. unsigned boxH = 30;
  332. unsigned boxW = 30;
  333. unsigned top = boxH + 2*bordY;
  334. unsigned left = bordX;
  335. for(i=0; i<p->locN; ++i)
  336. {
  337. cmSmgLoc_t* l = p->locV + i;
  338. cmSmgBox_t* b = l->bV;
  339. // for each box attached to this location
  340. for(; b!=NULL; b=b->link)
  341. {
  342. // bar boxes are always drawn at the top of the column
  343. if( cmIsFlag(b->flags,kBarSmgFl) )
  344. b->top = bordY;
  345. else
  346. {
  347. b->top = top;
  348. top += boxH + bordY;
  349. }
  350. b->left = left;
  351. b->width = boxW;
  352. b->height = boxH;
  353. }
  354. left += boxW + bordX;
  355. top = boxH + 2*bordY;
  356. }
  357. }
  358. void _cmSmgSvgSize( cmSmg_t* p, unsigned* widthRef, unsigned* heightRef )
  359. {
  360. unsigned i;
  361. unsigned maxWidth = 0;
  362. unsigned maxHeight = 0;
  363. for(i=0; i<p->locN; ++i)
  364. {
  365. cmSmgBox_t* b = p->locV[i].bV;
  366. for(; b != NULL; b=b->link )
  367. {
  368. if( b->left + b->width > maxWidth )
  369. maxWidth = b->left + b->width;
  370. if( b->top + b->height > maxHeight )
  371. maxHeight = b->top + b->height;
  372. }
  373. }
  374. *widthRef = maxWidth;
  375. *heightRef = maxHeight;
  376. }
  377. cmSmgRC_t cmScoreMatchGraphicWrite( cmSmgH_t h, const cmChar_t* fn )
  378. {
  379. cmSmg_t* p = _cmSmgHandleToPtr(h);
  380. cmFileH_t fH = cmFileNullHandle;
  381. unsigned svgHeight = 0;
  382. unsigned svgWidth = 0;
  383. unsigned i;
  384. // BUG BUG BUG : this can only be called once
  385. // create a box for each midi event
  386. _cmSmgResolveMidi( p );
  387. // layout the boxes
  388. _cmSmgLayout( p );
  389. if( cmFileOpen(&fH,fn,kWriteFileFl,p->err.rpt) != kOkFileRC )
  390. return cmErrMsg(&p->err,kFileFailScRC,"Graphic file create failed for '%s'.",cmStringNullGuard(fn));
  391. _cmSmgSvgSize(p,&svgWidth,&svgHeight);
  392. svgHeight += 10; // add a right and lower border
  393. svgWidth += 10;
  394. cmFilePrintf(fH,"<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" type=\"text/css\" href=\"score0.css\"></head><body>\n<svg width=\"%i\" height=\"%i\">\n",svgWidth,svgHeight);
  395. for(i=0; i<p->locN; ++i)
  396. {
  397. cmSmgBox_t* b = p->locV[i].bV;
  398. for(; b != NULL; b=b->link )
  399. {
  400. const cmChar_t* classStr = "score";
  401. if( cmIsFlag(b->flags,kLocSmgFl) )
  402. classStr = "loc";
  403. if( cmIsFlag(b->flags,kMidiSmgFl) )
  404. classStr = "midi";
  405. if( cmIsFlag(b->flags,kNoMatchSmgFl) )
  406. if( cmIsFlag(b->flags,kMidiSmgFl) )
  407. classStr = "midi_miss";
  408. if( cmIsFlag(b->flags,kNoMatchSmgFl) )
  409. if( cmIsFlag(b->flags,kNoteSmgFl) )
  410. classStr = "score_miss";
  411. if( cmIsFlag(b->flags,kBarSmgFl) )
  412. classStr = "bar";
  413. if( cmFilePrintf(fH,"<rect x=\"%i\" y=\"%i\" width=\"%i\" height=\"%i\" class=\"%s\"/>\n",b->left,b->top,b->width,b->height,classStr) != kOkFileRC )
  414. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file rect output.");
  415. if( b->text != NULL )
  416. {
  417. unsigned tx = b->left + b->width/2;
  418. unsigned ty = b->top + 20; //g->height/2;
  419. if( cmFilePrintf(fH,"<text x=\"%i\" y=\"%i\" text-anchor=\"middle\" class=\"stext\">%s</text>\n",tx,ty,b->text) != kOkFileRC )
  420. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file text output.");
  421. }
  422. }
  423. }
  424. cmSmgLine_t* l = p->lines;
  425. for(; l!=NULL; l=l->link)
  426. {
  427. unsigned x0 = l->b0->left + l->b0->width/2;
  428. unsigned y0 = l->b0->top + l->b0->height/2;
  429. unsigned x1 = l->b1->left + l->b1->width/2;
  430. unsigned y1 = l->b1->top + l->b1->height/2;
  431. if( cmFilePrintf(fH,"<line x1=\"%i\" y1=\"%i\" x2=\"%i\" y2=\"%i\" class=\"sline\"/>\n",x0,y0,x1,y1) != kOkFileRC )
  432. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file line output.");
  433. }
  434. cmFilePrint(fH,"</svg>\n</body>\n</html>\n");
  435. cmFileClose(&fH);
  436. return kOkSmgRC;
  437. }