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 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. }