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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  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 "cmRpt.h"
  7. #include "cmErr.h"
  8. #include "cmCtx.h"
  9. #include "cmMem.h"
  10. #include "cmMallocDebug.h"
  11. #include "cmLinkedHeap.h"
  12. #include "cmTime.h"
  13. #include "cmMidi.h"
  14. #include "cmLex.h"
  15. #include "cmCsv.h"
  16. #include "cmSymTbl.h"
  17. #include "cmMidiFile.h"
  18. #include "cmAudioFile.h"
  19. #include "cmTimeLine.h"
  20. #include "cmText.h"
  21. #include "cmFile.h"
  22. #include "cmScore.h"
  23. #include "cmScoreMatchGraphic.h"
  24. enum
  25. {
  26. kLocSmgFl = 0x0001,
  27. kBarSmgFl = 0x0002, // score bar
  28. kNoteSmgFl = 0x0004, // score note
  29. kPedalSmgFl = 0x0008, // sore damper|sot pedal
  30. kSostSmgFl = 0x0010, // score sost pedal
  31. kMidiSmgFl = 0x0020, // midi msg
  32. kNoMatchSmgFl = 0x0040, // midi or score events that were not matched
  33. kPedalDnSmgFl = 0x0080 // score pedal is down
  34. };
  35. // Graphic box representing a score label or MIDI event
  36. typedef struct cmSmgBox_str
  37. {
  38. unsigned flags;
  39. unsigned id; // csvEventId or midiUid
  40. unsigned left;
  41. unsigned top;
  42. unsigned width;
  43. unsigned height;
  44. cmChar_t* text0;
  45. cmChar_t* text1;
  46. struct cmSmgBox_str* link;
  47. } cmSmgBox_t;
  48. // Graphic line linking secondary MIDI matches to score events
  49. typedef struct cmSmgLine_str
  50. {
  51. cmSmgBox_t* b0;
  52. cmSmgBox_t* b1;
  53. struct cmSmgLine_str* link;
  54. } cmSmgLine_t;
  55. // Score Location
  56. typedef struct cmSmgLoc_str
  57. {
  58. cmSmgBox_t* bV; // List of graphic boxes assigned to this score location
  59. } cmSmgLoc_t;
  60. // Score label
  61. typedef struct
  62. {
  63. unsigned type; // kBarEvtScId | kNonEvtScId | kPedalEvtScId
  64. unsigned barNumb;
  65. unsigned csvEventId;
  66. unsigned locIdx;
  67. cmSmgBox_t* box;
  68. } cmSmgSc_t;
  69. // Link a MIDI event to it's matched score label.
  70. typedef struct cmSmgMatch_str
  71. {
  72. cmSmgSc_t* score;
  73. struct cmSmgMatch_str* link;
  74. } cmSmgMatch_t;
  75. // MIDI file event
  76. typedef struct
  77. {
  78. double secs;
  79. unsigned uid;
  80. unsigned pitch;
  81. unsigned vel;
  82. cmSmgMatch_t* matchV; // list of matches to score events
  83. cmSmgBox_t* box;
  84. } cmSmgMidi_t;
  85. typedef struct
  86. {
  87. cmErr_t err;
  88. cmChar_t* scFn;
  89. cmSmgSc_t* scV; // scV[scN] score bars,notes, pedals
  90. unsigned scN;
  91. cmSmgLoc_t* locV; // locV[locN] score locations (from the score file)
  92. unsigned locN;
  93. cmSmgLine_t* lines; // Graphic lines used to indicate that a midi event matches to multiple score notes.
  94. // (Each match after the first gets a line from the box representing the midi event
  95. // to the matching score event)
  96. cmChar_t* mfFn; // MIDI file name
  97. cmSmgMidi_t* mV; // mV[mN] midi note-on events
  98. unsigned mN;
  99. double mfDurSecs; // midi file duration in seconds
  100. unsigned boxW; // graphic box width and height
  101. unsigned boxH;
  102. } cmSmg_t;
  103. cmSmgH_t cmSmgNullHandle = cmSTATIC_NULL_HANDLE;
  104. cmSmg_t* _cmSmgHandleToPtr( cmSmgH_t h )
  105. {
  106. cmSmg_t* p = (cmSmg_t*)h.h;
  107. assert(p!=NULL);
  108. return p;
  109. }
  110. cmSmgRC_t _cmSmgFree( cmSmg_t* p )
  111. {
  112. unsigned i;
  113. for(i=0; i<p->mN; ++i)
  114. {
  115. cmSmgMatch_t* m0 = p->mV[i].matchV;
  116. cmSmgMatch_t* m1 = NULL;
  117. while(m0!=NULL)
  118. {
  119. m1 = m0->link;
  120. cmMemFree(m0);
  121. m0 = m1;
  122. }
  123. }
  124. for(i=0; i<p->locN; ++i)
  125. {
  126. cmSmgBox_t* b0 = p->locV[i].bV;
  127. cmSmgBox_t* b1 = NULL;
  128. while(b0!=NULL)
  129. {
  130. b1 = b0->link;
  131. cmMemFree(b0->text0);
  132. cmMemFree(b0->text1);
  133. cmMemFree(b0);
  134. b0 = b1;
  135. }
  136. }
  137. cmSmgLine_t* l0 = p->lines;
  138. cmSmgLine_t* l1 = NULL;
  139. while( l0 != NULL )
  140. {
  141. l1 = l0->link;
  142. cmMemFree(l0);
  143. l0 = l1;
  144. }
  145. cmMemFree(p->scFn);
  146. cmMemFree(p->mfFn);
  147. cmMemFree(p->scV);
  148. cmMemFree(p->mV);
  149. cmMemFree(p->locV);
  150. cmMemFree(p);
  151. return kOkSmgRC;
  152. }
  153. cmSmgBox_t* _cmSmgInsertBox( cmSmg_t* p, unsigned locIdx, unsigned flags, unsigned id, cmChar_t* text0, cmChar_t* text1 )
  154. {
  155. assert( locIdx < p->locN );
  156. cmSmgBox_t* b = cmMemAllocZ(cmSmgBox_t,1);
  157. b->flags = flags;
  158. b->id = id;
  159. b->text0 = text0;
  160. b->text1 = text1;
  161. if( p->locV[locIdx].bV == NULL )
  162. {
  163. p->locV[locIdx].bV = b;
  164. }
  165. else
  166. {
  167. cmSmgBox_t* b0 = p->locV[locIdx].bV;
  168. while( b0->link!=NULL )
  169. b0 = b0->link;
  170. b0->link = b;
  171. }
  172. return b;
  173. }
  174. cmSmgRC_t _cmSmgInitFromScore( cmCtx_t* ctx, cmSmg_t* p, const cmChar_t* scoreFn )
  175. {
  176. cmSmgRC_t rc = kOkSmgRC;
  177. cmScH_t scH = cmScNullHandle;
  178. unsigned i,j,k;
  179. if( cmScoreInitialize(ctx,&scH,scoreFn,44100.0, NULL, 0, NULL, NULL, cmSymTblNullHandle ) != kOkScRC )
  180. return cmErrMsg(&p->err,kScoreFailSmgRC,"Score initializatio failed on '%s'.",cmStringNullGuard(scoreFn));
  181. p->scFn = cmMemAllocStr(scoreFn);
  182. p->scN = cmScoreEvtCount(scH);
  183. p->scV = cmMemAllocZ(cmSmgSc_t,p->scN);
  184. p->locN = cmScoreLocCount(scH);
  185. p->locV = cmMemAllocZ(cmSmgLoc_t,p->locN);
  186. // for each score location
  187. for(i=0,k=0; i<cmScoreLocCount(scH); ++i)
  188. {
  189. cmScoreLoc_t* l = cmScoreLoc(scH,i);
  190. // insert the location label box
  191. _cmSmgInsertBox(p, i, kLocSmgFl, cmInvalidId, cmTsPrintfP(NULL,"%i",i), NULL );
  192. // for each event in location i
  193. for(j=0; j<l->evtCnt; ++j)
  194. {
  195. const cmScoreEvt_t* e = l->evtArray[j];
  196. unsigned flags = kNoMatchSmgFl;
  197. cmChar_t* text = NULL;
  198. switch( e->type)
  199. {
  200. case kNonEvtScId:
  201. flags |= kNoteSmgFl;
  202. text = cmMemAllocStr( cmMidiToSciPitch( e->pitch, NULL, 0));
  203. break;
  204. case kBarEvtScId:
  205. flags |= kBarSmgFl;
  206. text = cmTsPrintfP(NULL,"%i",e->barNumb);
  207. break;
  208. case kPedalEvtScId:
  209. flags |= kPedalSmgFl;
  210. text = cmTsPrintfP(NULL,"%s", cmIsFlag(e->flags,kPedalDnScFl)?"v":"^");
  211. if( e->pitch == kSostenutoCtlMdId )
  212. flags |= kSostSmgFl;
  213. flags |= cmIsFlag(e->flags,kPedalDnScFl) ? kPedalDnSmgFl : 0;
  214. break;
  215. }
  216. // if e is a score event of interest then store a reference to it
  217. if( flags != kNoMatchSmgFl )
  218. {
  219. assert( k < p->scN );
  220. p->scV[k].type = e->type;
  221. p->scV[k].csvEventId = e->csvEventId;
  222. p->scV[k].locIdx = i;
  223. p->scV[k].barNumb = e->barNumb;
  224. p->scV[k].box = _cmSmgInsertBox(p, i, flags, e->csvEventId, text, NULL );
  225. k += 1;
  226. }
  227. }
  228. }
  229. p->scN = k;
  230. cmScoreFinalize(&scH);
  231. return rc;
  232. }
  233. cmSmgRC_t _cmSmgInitFromMidi( cmCtx_t* ctx, cmSmg_t* p, const cmChar_t* midiFn )
  234. {
  235. cmSmgRC_t rc = kOkSmgRC;
  236. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  237. unsigned i,j;
  238. if( cmMidiFileOpen(ctx, &mfH, midiFn ) != kOkMfRC )
  239. return cmErrMsg(&p->err,kMidiFileFailSmgRC,"MIDI file open failed on '%s'.",cmStringNullGuard(midiFn));
  240. const cmMidiTrackMsg_t** mV = cmMidiFileMsgArray(mfH);
  241. unsigned mN = cmMidiFileMsgCount(mfH);
  242. p->mV = cmMemAllocZ(cmSmgMidi_t,mN);
  243. p->mN = mN;
  244. p->mfDurSecs = cmMidiFileDurSecs(mfH);
  245. p->mfFn = cmMemAllocStr(midiFn);
  246. for(i=0,j=0; i<mN; ++i)
  247. if( (mV[i]!=NULL) && cmMidiIsChStatus(mV[i]->status) && cmMidiIsNoteOn(mV[i]->status) && (mV[i]->u.chMsgPtr->d1>0) )
  248. {
  249. assert(j < mN);
  250. p->mV[j].secs = mV[i]->amicro / 1000000.0;
  251. p->mV[j].uid = mV[i]->uid;
  252. p->mV[j].pitch = mV[i]->u.chMsgPtr->d0;
  253. p->mV[j].vel = mV[i]->u.chMsgPtr->d1;
  254. ++j;
  255. }
  256. p->mN = j;
  257. cmMidiFileClose(&mfH);
  258. return rc;
  259. }
  260. cmSmgRC_t cmScoreMatchGraphicAlloc( cmCtx_t* ctx, cmSmgH_t* hp, const cmChar_t* scoreFn, const cmChar_t* midiFn )
  261. {
  262. cmSmgRC_t rc;
  263. if((rc = cmScoreMatchGraphicFree(hp)) != kOkSmgRC )
  264. return rc;
  265. cmSmg_t* p = cmMemAllocZ(cmSmg_t,1);
  266. cmErrSetup(&p->err,&ctx->rpt,"ScoreMatchGraphic");
  267. if((rc = _cmSmgInitFromScore(ctx,p,scoreFn)) != kOkSmgRC )
  268. goto errLabel;
  269. if((rc = _cmSmgInitFromMidi(ctx,p,midiFn)) != kOkSmgRC )
  270. goto errLabel;
  271. p->boxW = 30;
  272. p->boxH = 50;
  273. hp->h = p;
  274. errLabel:
  275. if( rc != kOkSmgRC )
  276. _cmSmgFree(p);
  277. return rc;
  278. }
  279. bool cmScoreMatchGraphicIsValid( cmSmgH_t h )
  280. { return h.h != NULL; }
  281. cmSmgRC_t cmScoreMatchGraphicFree( cmSmgH_t* hp )
  282. {
  283. cmSmgRC_t rc = kOkSmgRC;
  284. if(hp==NULL || cmScoreMatchGraphicIsValid(*hp)==false)
  285. return kOkSmgRC;
  286. cmSmg_t* p = _cmSmgHandleToPtr(*hp);
  287. if((rc = _cmSmgFree(p)) != kOkSmgRC )
  288. return rc;
  289. hp->h = NULL;
  290. return rc;
  291. }
  292. bool cmScoreMatchGraphic( cmSmgH_t h )
  293. { return h.h != NULL; }
  294. cmSmgRC_t cmScoreMatchGraphicInsertMidi( cmSmgH_t h, unsigned midiUid, unsigned midiPitch, unsigned midiVel, unsigned csvScoreEventId )
  295. {
  296. cmSmg_t* p = _cmSmgHandleToPtr(h);
  297. unsigned i,j;
  298. // if this midi event did not match any score records
  299. if( csvScoreEventId == cmInvalidId )
  300. return kOkSmgRC;
  301. assert(midiUid != cmInvalidId );
  302. assert(midiPitch<128 && midiVel<128);
  303. // find the midi file record which matches the event
  304. for(i=0; i<p->mN; ++i)
  305. if( p->mV[i].uid == midiUid )
  306. {
  307. // find the score record which matches the score event id
  308. for(j=0; j<p->scN; ++j)
  309. if( p->scV[j].csvEventId == csvScoreEventId )
  310. {
  311. // create a match record
  312. cmSmgMatch_t* m = cmMemAllocZ(cmSmgMatch_t,1);
  313. m->score = p->scV + j;
  314. // mark the box associated with this score record as 'matched' by clearing the kNoMatchSmgFl
  315. p->scV[j].box->flags = cmClrFlag(p->scV[j].box->flags,kNoMatchSmgFl);
  316. // insert the match record in the midi files match list
  317. if( p->mV[i].matchV == NULL )
  318. p->mV[i].matchV = m;
  319. else
  320. {
  321. cmSmgMatch_t* m0 = p->mV[i].matchV;
  322. while( m0->link != NULL )
  323. m0 = m0->link;
  324. m0->link = m;
  325. }
  326. return kOkSmgRC;
  327. }
  328. return cmErrMsg(&p->err,kScoreFailSmgRC,"The score csv event id %i not found,",csvScoreEventId);
  329. }
  330. return cmErrMsg(&p->err,kMidiFileFailSmgRC,"MIDI uid %i not found.",midiUid);
  331. }
  332. // Create a box for each MIDI event and a line for each
  333. // match beyond the first.
  334. void _cmSmgResolveMidi( cmSmg_t* p )
  335. {
  336. unsigned prevLocIdx = 0;
  337. unsigned i;
  338. // for each midi record
  339. for(i=0; i<p->mN; ++i)
  340. {
  341. // get the first match record for this MIDI event
  342. const cmSmgMatch_t* m = p->mV[i].matchV;
  343. // get the score location for this midi event
  344. unsigned locIdx = m==NULL ? prevLocIdx : m->score->locIdx;
  345. unsigned flags = kMidiSmgFl | (m==NULL ? kNoMatchSmgFl : 0);
  346. // set the text label for this event
  347. cmChar_t* text = cmMemAllocStr( cmMidiToSciPitch( p->mV[i].pitch, NULL, 0));
  348. // insert a box to represent this midi event
  349. cmSmgBox_t* box = _cmSmgInsertBox( p, locIdx, flags, p->mV[i].uid, text, cmTsPrintfP(NULL,"%i",p->mV[i].uid) );
  350. prevLocIdx = locIdx;
  351. // if this midi event matched to multiple score positions
  352. if( m != NULL && m->link != NULL )
  353. {
  354. // insert a line for each match after the first
  355. m = m->link;
  356. for(; m!=NULL; m=m->link )
  357. {
  358. cmSmgLine_t* l = cmMemAllocZ(cmSmgLine_t,1);
  359. l->b0 = box;
  360. l->b1 = m->score->box;
  361. l->link = p->lines;
  362. p->lines = l;
  363. }
  364. }
  365. }
  366. }
  367. void _cmSmgLayout( cmSmg_t* p )
  368. {
  369. unsigned i;
  370. unsigned bordX = 5;
  371. unsigned bordY = 5;
  372. unsigned top = p->boxH + 2*bordY;
  373. unsigned left = bordX;
  374. for(i=0; i<p->locN; ++i)
  375. {
  376. cmSmgLoc_t* l = p->locV + i;
  377. cmSmgBox_t* b = l->bV;
  378. // for each box attached to this location
  379. for(; b!=NULL; b=b->link)
  380. {
  381. // bar boxes are always drawn at the top of the column
  382. if( cmIsFlag(b->flags,kBarSmgFl) )
  383. b->top = bordY;
  384. else
  385. {
  386. b->top = top;
  387. top += p->boxH + bordY;
  388. }
  389. b->left = left;
  390. b->width = p->boxW;
  391. b->height = p->boxH;
  392. }
  393. left += p->boxW + bordX;
  394. top = p->boxH + 2*bordY;
  395. }
  396. }
  397. void _cmSmgSvgSize( cmSmg_t* p, unsigned* widthRef, unsigned* heightRef )
  398. {
  399. unsigned i;
  400. unsigned maxWidth = 0;
  401. unsigned maxHeight = 0;
  402. for(i=0; i<p->locN; ++i)
  403. {
  404. cmSmgBox_t* b = p->locV[i].bV;
  405. for(; b != NULL; b=b->link )
  406. {
  407. if( b->left + b->width > maxWidth )
  408. maxWidth = b->left + b->width;
  409. if( b->top + b->height > maxHeight )
  410. maxHeight = b->top + b->height;
  411. }
  412. }
  413. *widthRef = maxWidth;
  414. *heightRef = maxHeight;
  415. }
  416. cmSmgRC_t cmScoreMatchGraphicWrite( cmSmgH_t h, const cmChar_t* fn )
  417. {
  418. cmSmg_t* p = _cmSmgHandleToPtr(h);
  419. cmFileH_t fH = cmFileNullHandle;
  420. unsigned svgHeight = 0;
  421. unsigned svgWidth = 0;
  422. unsigned i;
  423. // BUG BUG BUG : this can only be called once
  424. // create a box for each midi event
  425. _cmSmgResolveMidi( p );
  426. // layout the boxes
  427. _cmSmgLayout( p );
  428. if( cmFileOpen(&fH,fn,kWriteFileFl,p->err.rpt) != kOkFileRC )
  429. return cmErrMsg(&p->err,kFileFailScRC,"Graphic file create failed for '%s'.",cmStringNullGuard(fn));
  430. _cmSmgSvgSize(p,&svgWidth,&svgHeight);
  431. svgHeight += 10; // add a right and lower border
  432. svgWidth += 10;
  433. cmFilePrintf(fH,"<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" type=\"text/css\" href=\"cmScoreMatchGraphic.css\"></head><body>\n<svg width=\"%i\" height=\"%i\">\n",svgWidth,svgHeight);
  434. for(i=0; i<p->locN; ++i)
  435. {
  436. cmSmgBox_t* b = p->locV[i].bV;
  437. for(; b != NULL; b=b->link )
  438. {
  439. const cmChar_t* classStr = "score";
  440. if( cmIsFlag(b->flags,kLocSmgFl) )
  441. classStr = "loc";
  442. if( cmIsFlag(b->flags,kMidiSmgFl) )
  443. classStr = "midi";
  444. if( cmIsFlag(b->flags,kNoMatchSmgFl) )
  445. if( cmIsFlag(b->flags,kMidiSmgFl) )
  446. classStr = "midi_miss";
  447. if( cmIsFlag(b->flags,kNoMatchSmgFl) )
  448. if( cmIsFlag(b->flags,kNoteSmgFl) )
  449. classStr = "score_miss";
  450. if( cmIsFlag(b->flags,kBarSmgFl) )
  451. classStr = "bar";
  452. if( cmIsFlag(b->flags,kPedalSmgFl) )
  453. {
  454. if( cmIsFlag(b->flags,kSostSmgFl) )
  455. classStr = "sost";
  456. else
  457. classStr = "damper";
  458. }
  459. 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 )
  460. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file rect output.");
  461. if( b->text0 != NULL )
  462. {
  463. unsigned tx = b->left + b->width/2;
  464. unsigned ty = b->top + 20;
  465. if( cmFilePrintf(fH,"<text x=\"%i\" y=\"%i\" text-anchor=\"middle\" class=\"stext\">%s</text>\n",tx,ty,b->text0) != kOkFileRC )
  466. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file text output.");
  467. }
  468. if( b->text1 != NULL )
  469. {
  470. unsigned tx = b->left + b->width/2;
  471. unsigned ty = b->top + 20 + 20;
  472. if( cmFilePrintf(fH,"<text x=\"%i\" y=\"%i\" text-anchor=\"middle\" class=\"stext\">%s</text>\n",tx,ty,b->text1) != kOkFileRC )
  473. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file text output.");
  474. }
  475. }
  476. }
  477. cmSmgLine_t* l = p->lines;
  478. for(; l!=NULL; l=l->link)
  479. {
  480. unsigned x0 = l->b0->left + l->b0->width/2;
  481. unsigned y0 = l->b0->top + l->b0->height/2;
  482. unsigned x1 = l->b1->left + l->b1->width/2;
  483. unsigned y1 = l->b1->top + l->b1->height/2;
  484. if( cmFilePrintf(fH,"<line x1=\"%i\" y1=\"%i\" x2=\"%i\" y2=\"%i\" class=\"sline\"/>\n",x0,y0,x1,y1) != kOkFileRC )
  485. return cmErrMsg(&p->err,kFileFailScRC,"File write failed on graphic file line output.");
  486. }
  487. cmFilePrint(fH,"</svg>\n</body>\n</html>\n");
  488. cmFileClose(&fH);
  489. return kOkSmgRC;
  490. }
  491. cmSmgRC_t cmScoreMatchGraphicGenTimeLineBars( cmSmgH_t h, const cmChar_t* fn, unsigned srate )
  492. {
  493. cmSmgRC_t rc = kOkSmgRC;
  494. cmFileH_t f = cmFileNullHandle;
  495. cmSmg_t* p = _cmSmgHandleToPtr(h);
  496. unsigned i = 0;
  497. if( cmFileOpen(&f,fn,kWriteFileFl,p->err.rpt) != kOkFileRC )
  498. return cmErrMsg(&p->err,kFileSmgRC,"The time-line bar file '%s' could not be created.",cmStringNullGuard(fn));
  499. // for each MIDI event
  500. for(i=0; i<p->mN; ++i)
  501. {
  502. // if this MIDI event matched a score event
  503. if( p->mV[i].matchV == NULL || p->mV[i].matchV->score == NULL )
  504. continue;
  505. // backup the score position by one location - because we are looking for bar events
  506. // which are just prior to note events - because for our purposes they will have the same onset.
  507. cmSmgSc_t* s = p->mV[i].matchV->score - 1;
  508. for(; s >= p->scV; s-- )
  509. {
  510. // if this is a bar event just preceding the matched MIDI event - then the bar happens at the same time as the note event
  511. if( s->type == kBarEvtScId )
  512. break;
  513. // if this is a note event - then there is no preceding bar event
  514. if( s->type == kNonEvtScId )
  515. break;
  516. }
  517. // if a bar was found
  518. if(s >= p->scV && s->type == kBarEvtScId)
  519. {
  520. unsigned bar = s->barNumb;
  521. unsigned offset = p->mV[i].secs * srate;
  522. unsigned smpCnt = p->mfDurSecs * srate - offset;
  523. cmFilePrintf(f,"{ label: \"%i\" type: \"mk\" ref: \"mf-0\" offset: %8i smpCnt:%8i trackId: 0 textStr: \"Bar %3i\" bar: %3i sec:\"%3i\" }\n",bar,offset,smpCnt,bar,bar,bar);
  524. }
  525. }
  526. cmFileClose(&f);
  527. return rc;
  528. }
  529. // Find the first MIDI event that matches this score event
  530. const cmSmgMidi_t* _cmScoreMatchGraphicScoreToMatchedMidiEvent( cmSmg_t* p, const cmSmgSc_t* sc )
  531. {
  532. unsigned i;
  533. for(i=0; i<p->mN; ++i)
  534. {
  535. const cmSmgMatch_t* m = p->mV[i].matchV;
  536. for(; m != NULL; m=m->link )
  537. if( sc->csvEventId == m->score->csvEventId )
  538. return p->mV + i;
  539. }
  540. return NULL;
  541. }
  542. cmSmgRC_t _cmScoreMatchGraphicInsertMidiMsg( cmSmg_t* p, cmMidiFileH_t mfH, bool pedalDnFl, const cmSmgSc_t* s )
  543. {
  544. const cmSmgMidi_t* m;
  545. // locate the MIDI event associated with the reference event
  546. if((m =_cmScoreMatchGraphicScoreToMatchedMidiEvent( p, s )) == NULL )
  547. return cmErrWarnMsg(&p->err,kMatchFailSmgRC,"A sostenuto pedal msg could not be aligned to a note event.");
  548. int dtick_offset = pedalDnFl ? 1 : -1;
  549. cmMidiByte_t midi_vel = pedalDnFl ? 64 : 0;
  550. cmMidiByte_t midi_ch = 0;
  551. //printf("pedal:%s\n",pedalDnFl?"down":"up");
  552. // insert a pedal msg relative to the reference event
  553. if( cmMidiFileInsertMsg(mfH, m->uid, dtick_offset, midi_ch, kCtlMdId, kSostenutoCtlMdId, midi_vel ) != kOkMfRC )
  554. return cmErrWarnMsg(&p->err,kMidiFileFailSmgRC,"MIDI msg insert failed.");
  555. return kOkSmgRC;
  556. }
  557. cmSmgRC_t _cmScoreMatchGraphicUpdateSostenuto( cmSmg_t* p, cmMidiFileH_t mfH )
  558. {
  559. cmSmgRC_t rc = kOkSmgRC;
  560. unsigned i, j = cmInvalidIdx;
  561. bool pedalUpFl = false;
  562. // for each score event
  563. for(i=0; i<p->scN; ++i)
  564. {
  565. switch( p->scV[i].type )
  566. {
  567. case kNonEvtScId:
  568. {
  569. // if the pedalUpFl is set then insert a sost-pedal-up msg before this note.
  570. if( pedalUpFl )
  571. {
  572. _cmScoreMatchGraphicInsertMidiMsg(p, mfH, false, p->scV + i );
  573. pedalUpFl = false;
  574. }
  575. j = i; // store the index of this note event (it may be needed if the next event is a sost. pedal evt.)
  576. }
  577. break;
  578. case kPedalEvtScId:
  579. // if this is a sost pedal event
  580. if( cmIsFlag(p->scV[i].box->flags,kSostSmgFl) )
  581. {
  582. if( cmIsFlag(p->scV[i].box->flags,kPedalDnSmgFl) )
  583. {
  584. assert( j != cmInvalidIdx );
  585. _cmScoreMatchGraphicInsertMidiMsg(p, mfH, true, p->scV + j );
  586. }
  587. else
  588. {
  589. pedalUpFl = true; // insert a pedal up message before the next note-on
  590. }
  591. }
  592. default:
  593. break;
  594. }
  595. }
  596. return rc;
  597. }
  598. cmSmgRC_t cmScoreMatchGraphicUpdateMidiFromScore( cmCtx_t* ctx, cmSmgH_t h, const cmChar_t* newMidiFn )
  599. {
  600. cmSmgRC_t rc = kOkSmgRC;
  601. cmSmg_t* p = _cmSmgHandleToPtr(h);
  602. unsigned i = 0;
  603. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  604. cmScH_t scH = cmScNullHandle;
  605. // open the MIDI file
  606. if( cmMidiFileOpen(ctx, &mfH, p->mfFn ) != kOkMfRC )
  607. return cmErrMsg(&p->err,kMidiFileFailSmgRC,"MIDI file open failed on '%s'.",cmStringNullGuard(p->mfFn));
  608. // instantiate the score from the score CSV file
  609. if( cmScoreInitialize(ctx,&scH,p->scFn,44100.0, NULL, 0, NULL, NULL, cmSymTblNullHandle ) != kOkScRC )
  610. {
  611. rc = cmErrMsg(&p->err,kScoreFailSmgRC,"Score initializatio failed on '%s'.",cmStringNullGuard(p->scFn));
  612. goto errLabel;
  613. }
  614. // for each MIDI note-on event
  615. for(i=0; i<p->mN; ++i)
  616. {
  617. cmSmgMidi_t* mr = p->mV + i;
  618. // only update midi events which were matched exactly once
  619. if( mr->matchV==NULL || mr->matchV->link!=NULL )
  620. continue;
  621. // locate the matched score event
  622. const cmScoreEvt_t* s= cmScoreIdToEvt( scH, mr->matchV->score->csvEventId );
  623. assert( s!=NULL );
  624. // assign the score velocity to the MIDI note
  625. if(cmMidiFileSetVelocity( mfH, mr->uid, s->vel ) != kOkMfRC )
  626. {
  627. rc = cmErrMsg(&p->err,kOpFailSmgRC,"Set velocify operation failed.");
  628. goto errLabel;
  629. }
  630. }
  631. // update the sostenuto pedal msg's in the MIDI file.
  632. _cmScoreMatchGraphicUpdateSostenuto(p, mfH );
  633. // write the updated MIDI file
  634. if( cmMidiFileWrite( mfH, newMidiFn ) != kOkMfRC )
  635. {
  636. rc = cmErrMsg(&p->err,kMidiFileFailSmgRC,"MIDI file write failed on '%s'.",cmStringNullGuard(newMidiFn));
  637. goto errLabel;
  638. }
  639. errLabel:
  640. cmMidiFileClose(&mfH);
  641. cmScoreFinalize(&scH);
  642. return rc;
  643. }