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

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