libcm is a C development framework with an emphasis on audio signal processing applications.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

cmScoreMatchGraphic.c 14KB

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