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.

cmMidiScoreFollow.c 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #include "cmPrefix.h"
  2. #include "cmGlobal.h"
  3. #include "cmRpt.h"
  4. #include "cmErr.h"
  5. #include "cmCtx.h"
  6. #include "cmMem.h"
  7. #include "cmMallocDebug.h"
  8. #include "cmLinkedHeap.h"
  9. #include "cmFloatTypes.h"
  10. #include "cmComplexTypes.h"
  11. #include "cmFile.h"
  12. #include "cmFileSys.h"
  13. #include "cmJson.h"
  14. #include "cmSymTbl.h"
  15. #include "cmAudioFile.h"
  16. #include "cmText.h"
  17. #include "cmProcObj.h"
  18. #include "cmProcTemplate.h"
  19. #include "cmMath.h"
  20. #include "cmTime.h"
  21. #include "cmMidi.h"
  22. #include "cmMidiFile.h"
  23. #include "cmProc.h"
  24. #include "cmProc2.h"
  25. #include "cmVectOps.h"
  26. #include "cmTimeLine.h"
  27. #include "cmScore.h"
  28. #include "cmProc4.h"
  29. #include "cmMidiScoreFollow.h"
  30. #include "cmScoreMatchGraphic.h"
  31. typedef struct
  32. {
  33. cmScMatcherResult_t* rV; // rV[rN] - array of stored cmScMatcher callback records.
  34. unsigned rAllocN; //
  35. unsigned rN; //
  36. } _cmMsf_ScoreFollow_t;
  37. void _cmMsf_ReportScoreErrors( const _cmMsf_ScoreFollow_t* f, cmScH_t scH )
  38. {
  39. unsigned scoreEvtN = cmScoreEvtCount(scH);
  40. unsigned i,j;
  41. for(i=0; i<scoreEvtN; ++i)
  42. {
  43. const cmScoreEvt_t* e = cmScoreEvt(scH,i);
  44. assert(e != NULL);
  45. if( e->type == kNonEvtScId && cmIsNotFlag(e->flags,kSkipScFl) )
  46. {
  47. unsigned matchN = 0;
  48. for(j=0; j<f->rN; ++j)
  49. if( f->rV[j].scEvtIdx == i )
  50. matchN += 1;
  51. if( matchN != 1 )
  52. {
  53. const cmScoreLoc_t* l = cmScoreEvtLoc(scH,e);
  54. assert(l != NULL);
  55. printf("bar:%3i evtIdx:%5i pitch:%4s match:%i ",l->barNumb,e->index,cmMidiToSciPitch(e->pitch,NULL,0),matchN);
  56. // print the midi event associated with multiple matches.
  57. if( matchN > 1 )
  58. for(j=0; j<f->rN; ++j)
  59. if( f->rV[j].scEvtIdx == i )
  60. printf("(%i %s) ",f->rV[j].muid, cmMidiToSciPitch(f->rV[j].pitch,NULL,0) );
  61. printf("\n");
  62. }
  63. }
  64. }
  65. }
  66. void _cmMsf_ReportMidiErrors( const _cmMsf_ScoreFollow_t* f, cmScH_t scH, const cmMidiTrackMsg_t** m, unsigned mN)
  67. {
  68. unsigned i,j;
  69. unsigned lastBar = 0;
  70. // for each midi note-on msg
  71. for(i=0; i<mN; ++i)
  72. {
  73. if( (m[i]!=NULL) && cmMidiIsChStatus(m[i]->status) && cmMidiIsNoteOn(m[i]->status) && (m[i]->u.chMsgPtr->d1>0) )
  74. {
  75. unsigned matchN = 0;
  76. // find the note-on msg in the score-match result array
  77. for(j=0; j<f->rN; ++j)
  78. if( f->rV[j].muid == m[i]->uid )
  79. {
  80. if( f->rV[j].scEvtIdx != -1 )
  81. {
  82. const cmScoreEvt_t* e = cmScoreEvt(scH,f->rV[j].scEvtIdx);
  83. if( e != NULL )
  84. {
  85. const cmScoreLoc_t* l = cmScoreEvtLoc(scH,e);
  86. assert(l != NULL );
  87. lastBar = l->barNumb;
  88. }
  89. }
  90. matchN += 1;
  91. break;
  92. }
  93. if( matchN==0 )
  94. {
  95. printf("bar:%3i muid:%4i %s\n", lastBar, m[i]->uid, cmMidiToSciPitch(m[i]->u.chMsgPtr->d0,NULL,0));
  96. }
  97. }
  98. }
  99. }
  100. void _cmMsf_WriteMatchFileHeader( cmFileH_t fH )
  101. {
  102. cmFilePrintf(fH," Score Score Score MIDI MIDI MIDI\n");
  103. cmFilePrintf(fH," Bar UUID Pitch UUID Ptch Vel.\n");
  104. cmFilePrintf(fH,"- ----- ----- ----- ----- ---- ----\n");
  105. }
  106. // Write one scScoreMatcherResult_t record to the file fH.
  107. unsigned _cmMsf_WriteMatchFileLine( cmFileH_t fH, cmScH_t scH, const cmScMatcherResult_t* r )
  108. {
  109. unsigned scUid = -1;
  110. cmChar_t buf[6];
  111. buf[0] = 0;
  112. buf[5] = 0;
  113. cmScoreLoc_t* loc = NULL;
  114. if( r->scEvtIdx > 0 && r->scEvtIdx < cmScoreEvtCount(scH))
  115. {
  116. cmScoreEvt_t* e = cmScoreEvt(scH,r->scEvtIdx);
  117. loc = cmScoreEvtLoc(scH,e);
  118. scUid = e->csvEventId;
  119. cmMidiToSciPitch(e->pitch,buf,5);
  120. }
  121. cmFilePrintf(fH,"m %5i %5i %5s %5i %4s %3i\n",
  122. loc==NULL ? 0 : loc->barNumb, // score evt bar
  123. scUid, // score event uuid
  124. buf, // score event pitch
  125. r->muid, // midi event uuid
  126. cmMidiToSciPitch(r->pitch,NULL,0), // midi event pitch
  127. r->vel); // midi event velocity
  128. return scUid;
  129. }
  130. void _cmMsf_ScoreFollowCb( struct cmScMatcher_str* p, void* arg, cmScMatcherResult_t* rp )
  131. {
  132. _cmMsf_ScoreFollow_t* r = (_cmMsf_ScoreFollow_t*)arg;
  133. r->rV[r->rN++] = *rp;
  134. }
  135. cmMsfRC_t cmMidiScoreFollowMain(
  136. cmCtx_t* ctx,
  137. const cmChar_t* scoreCsvFn, // score CSV file as generated from cmXScoreTest().
  138. const cmChar_t* midiFn, // MIDI file to track
  139. const cmChar_t* matchRptOutFn, // Score follow status report
  140. const cmChar_t* matchSvgOutFn, // Score follow graphic report
  141. const cmChar_t* midiOutFn, // (optional) midiFn with apply sostenuto and velocities from the score to the MIDI file
  142. const cmChar_t* tlBarOutFn // (optional) bar positions sutiable for use in a cmTimeLine description file.
  143. )
  144. {
  145. cmMsfRC_t rc = kOkMsfRC;
  146. double srate = 96000.0;
  147. cmScMatcher* smp = NULL;
  148. cmScH_t scH = cmScNullHandle;
  149. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  150. unsigned scWndN = 10;
  151. unsigned midiWndN = 7;
  152. const cmMidiTrackMsg_t** m = NULL;
  153. unsigned mN = 0;
  154. unsigned scLocIdx = 0;
  155. cmFileH_t fH = cmFileNullHandle;
  156. cmSmgH_t smgH = cmSmgNullHandle;
  157. unsigned i;
  158. cmErr_t err;
  159. _cmMsf_ScoreFollow_t sfr;
  160. memset(&sfr,0,sizeof(sfr));
  161. cmErrSetup(&err,&ctx->rpt,"cmMidiScoreFollow");
  162. cmCtx* prCtx = cmCtxAlloc(NULL, err.rpt, cmLHeapNullHandle, cmSymTblNullHandle );
  163. // initialize the score
  164. if( cmScoreInitialize( ctx, &scH, scoreCsvFn, srate, NULL, 0, NULL, NULL, cmSymTblNullHandle) != kOkScRC )
  165. {
  166. rc = cmErrMsg(&err,kFailMsfRC,"cmScoreInitialize() failed on %s",cmStringNullGuard(scoreCsvFn));
  167. goto errLabel;
  168. }
  169. // setup the callback record
  170. if((sfr.rAllocN = cmScoreEvtCount( scH )*2) == 0)
  171. {
  172. rc = cmErrMsg(&err,kFailMsfRC,"The score %s appears to be empty.",cmStringNullGuard(scoreCsvFn));
  173. goto errLabel;
  174. }
  175. sfr.rV = cmMemAllocZ(cmScMatcherResult_t,sfr.rAllocN);
  176. sfr.rN = 0;
  177. // create a matcher
  178. if((smp = cmScMatcherAlloc(prCtx, NULL, srate, scH, scWndN, midiWndN, _cmMsf_ScoreFollowCb, &sfr)) == NULL )
  179. {
  180. rc = cmErrMsg(&err,kFailMsfRC,"cmScMatcherAlloc() failed.");
  181. goto errLabel;
  182. }
  183. // open the MIDI file
  184. if( cmMidiFileOpen(ctx, &mfH, midiFn ) != kOkMfRC )
  185. {
  186. rc = cmErrMsg(&err,kFailMsfRC,"The MIDI file object could not be opened from '%s'.",cmStringNullGuard(midiFn));
  187. goto errLabel;
  188. }
  189. // get a pointer to the MIDI msg array
  190. if( (m = cmMidiFileMsgArray(mfH)) == NULL || (mN = cmMidiFileMsgCount(mfH)) == 0 )
  191. {
  192. rc = cmErrMsg(&err,kFailMsfRC,"The MIDI file object appears to be empty.");
  193. goto errLabel;
  194. }
  195. // feed each MIDI note-on to the score follower
  196. for(i=0; i<mN; ++i)
  197. if( (m[i]!=NULL) && cmMidiIsChStatus(m[i]->status) && cmMidiIsNoteOn(m[i]->status) && (m[i]->u.chMsgPtr->d1>0) )
  198. if( cmScMatcherExec( smp, m[i]->amicro * srate / 1000000.0, m[i]->uid, m[i]->status, m[i]->u.chMsgPtr->d0, m[i]->u.chMsgPtr->d1, &scLocIdx ) != cmOkRC )
  199. {
  200. rc = cmErrMsg(&err,kFailMsfRC,"The score matcher exec failed.");
  201. goto errLabel;
  202. }
  203. printf("MIDI notes:%i Score Events:%i\n",mN,cmScoreEvtCount(scH));
  204. // create the output file
  205. if( cmFileOpen(&fH,matchRptOutFn,kWriteFileFl,&ctx->rpt) != kOkFileRC )
  206. {
  207. rc = cmErrMsg(&err,kFailMsfRC,"Unable to create the file '%s'.",cmStringNullGuard(matchRptOutFn));
  208. goto errLabel;
  209. }
  210. // allocate the graphics object
  211. if( cmScoreMatchGraphicAlloc( ctx, &smgH, scoreCsvFn, midiFn ) != kOkSmgRC )
  212. {
  213. rc = cmErrMsg(&err,kFailMsfRC,"Score Match Graphics allocation failed..");
  214. goto errLabel;
  215. }
  216. // write the match report output file header
  217. _cmMsf_WriteMatchFileHeader(fH);
  218. // for each score follower callback record
  219. for(i=0; i<sfr.rN; ++i)
  220. {
  221. // write the record to the output file
  222. unsigned scUid = _cmMsf_WriteMatchFileLine( fH, scH, sfr.rV + i );
  223. // insert the event->score match in the score match graphics object
  224. if( cmScoreMatchGraphicInsertMidi( smgH, sfr.rV[i].muid, sfr.rV[i].pitch, sfr.rV[i].vel, scUid ) != kOkSmgRC )
  225. {
  226. rc = cmErrMsg(&err,kFailMsfRC,"Score Match Graphics MIDI event insertion failed.");
  227. goto errLabel;
  228. }
  229. }
  230. _cmMsf_ReportScoreErrors(&sfr, scH );
  231. //_cmMsf_ReportMidiErrors(&sfr, scH, m, mN);
  232. //cmScorePrint(scH,&ctx->rpt);
  233. //cmMidiFilePrintMsgs( mfH, &ctx->rpt );
  234. // write the tracking match file as an SVG file.
  235. cmScoreMatchGraphicWrite( smgH, matchSvgOutFn );
  236. // write a cmTimeLine file which contains markers at each bar position
  237. if( tlBarOutFn != NULL )
  238. cmScoreMatchGraphicGenTimeLineBars(smgH, tlBarOutFn, srate );
  239. if( midiOutFn != NULL )
  240. cmScoreMatchGraphicUpdateMidiFromScore( ctx, smgH, midiOutFn );
  241. errLabel:
  242. cmFileClose(&fH);
  243. cmMemFree(sfr.rV);
  244. cmMidiFileClose(&mfH);
  245. cmScMatcherFree(&smp);
  246. cmScoreFinalize(&scH);
  247. cmScoreMatchGraphicFree(&smgH);
  248. cmCtxFree(&prCtx);
  249. //cmFsFreeFn(scoreCsvFn);
  250. //cmFsFreeFn(midiFn);
  251. //cmFsFreeFn(matchRptOutFn);
  252. //cmFsFreeFn(matchSvgOutFn);
  253. //cmFsFreeFn(outMidiFn);
  254. //cmFsFreeFn(tlBarFn);
  255. return rc;
  256. }