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.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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. // This is the score follower callback function
  131. void _cmMsf_ScoreFollowCb( struct cmScMatcher_str* p, void* arg, cmScMatcherResult_t* rp )
  132. {
  133. _cmMsf_ScoreFollow_t* r = (_cmMsf_ScoreFollow_t*)arg;
  134. r->rV[r->rN++] = *rp;
  135. }
  136. cmMsfRC_t cmMidiScoreFollowMain(
  137. cmCtx_t* ctx,
  138. const cmChar_t* scoreCsvFn, // score CSV file as generated from cmXScoreTest().
  139. const cmChar_t* midiFn, // MIDI file to track
  140. const cmChar_t* matchRptOutFn, // Score follow status report
  141. const cmChar_t* matchSvgOutFn, // Score follow graphic report
  142. const cmChar_t* midiOutFn, // (optional) midiFn with apply sostenuto and velocities from the score to the MIDI file
  143. const cmChar_t* tlBarOutFn // (optional) bar positions sutiable for use in a cmTimeLine description file.
  144. )
  145. {
  146. cmMsfRC_t rc = kOkMsfRC;
  147. double srate = 96000.0;
  148. cmScMatcher* smp = NULL;
  149. cmScH_t scH = cmScNullHandle;
  150. cmMidiFileH_t mfH = cmMidiFileNullHandle;
  151. unsigned scWndN = 10;
  152. unsigned midiWndN = 7;
  153. const cmMidiTrackMsg_t** m = NULL;
  154. unsigned mN = 0;
  155. unsigned scLocIdx = 0;
  156. cmFileH_t fH = cmFileNullHandle;
  157. cmSmgH_t smgH = cmSmgNullHandle;
  158. unsigned i;
  159. cmErr_t err;
  160. _cmMsf_ScoreFollow_t sfr;
  161. memset(&sfr,0,sizeof(sfr));
  162. cmErrSetup(&err,&ctx->rpt,"cmMidiScoreFollow");
  163. cmCtx* prCtx = cmCtxAlloc(NULL, err.rpt, cmLHeapNullHandle, cmSymTblNullHandle );
  164. // initialize the score
  165. if( cmScoreInitialize( ctx, &scH, scoreCsvFn, srate, NULL, 0, NULL, NULL, cmSymTblNullHandle) != kOkScRC )
  166. {
  167. rc = cmErrMsg(&err,kFailMsfRC,"cmScoreInitialize() failed on %s",cmStringNullGuard(scoreCsvFn));
  168. goto errLabel;
  169. }
  170. // setup the callback record with an array that has twice as many records as there are score events
  171. if((sfr.rAllocN = cmScoreEvtCount( scH )*2) == 0)
  172. {
  173. rc = cmErrMsg(&err,kFailMsfRC,"The score %s appears to be empty.",cmStringNullGuard(scoreCsvFn));
  174. goto errLabel;
  175. }
  176. sfr.rV = cmMemAllocZ(cmScMatcherResult_t,sfr.rAllocN);
  177. sfr.rN = 0;
  178. // create a matcher
  179. if((smp = cmScMatcherAlloc(prCtx, NULL, srate, scH, scWndN, midiWndN, _cmMsf_ScoreFollowCb, &sfr)) == NULL )
  180. {
  181. rc = cmErrMsg(&err,kFailMsfRC,"cmScMatcherAlloc() failed.");
  182. goto errLabel;
  183. }
  184. // open the MIDI file
  185. if( cmMidiFileOpen(ctx, &mfH, midiFn ) != kOkMfRC )
  186. {
  187. rc = cmErrMsg(&err,kFailMsfRC,"The MIDI file object could not be opened from '%s'.",cmStringNullGuard(midiFn));
  188. goto errLabel;
  189. }
  190. // get a pointer to the MIDI msg array
  191. if( (m = cmMidiFileMsgArray(mfH)) == NULL || (mN = cmMidiFileMsgCount(mfH)) == 0 )
  192. {
  193. rc = cmErrMsg(&err,kFailMsfRC,"The MIDI file object appears to be empty.");
  194. goto errLabel;
  195. }
  196. // feed each MIDI note-on to the score follower
  197. for(i=0; i<mN; ++i)
  198. if( (m[i]!=NULL) && cmMidiIsChStatus(m[i]->status) && cmMidiIsNoteOn(m[i]->status) && (m[i]->u.chMsgPtr->d1>0) )
  199. 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 )
  200. {
  201. rc = cmErrMsg(&err,kFailMsfRC,"The score matcher exec failed.");
  202. goto errLabel;
  203. }
  204. printf("MIDI notes:%i Score Events:%i\n",mN,cmScoreEvtCount(scH));
  205. // create the output file
  206. if( cmFileOpen(&fH,matchRptOutFn,kWriteFileFl,&ctx->rpt) != kOkFileRC )
  207. {
  208. rc = cmErrMsg(&err,kFailMsfRC,"Unable to create the file '%s'.",cmStringNullGuard(matchRptOutFn));
  209. goto errLabel;
  210. }
  211. // allocate the graphics object
  212. if( cmScoreMatchGraphicAlloc( ctx, &smgH, scoreCsvFn, midiFn ) != kOkSmgRC )
  213. {
  214. rc = cmErrMsg(&err,kFailMsfRC,"Score Match Graphics allocation failed..");
  215. goto errLabel;
  216. }
  217. // write the match report output file header
  218. _cmMsf_WriteMatchFileHeader(fH);
  219. // for each score follower callback record
  220. for(i=0; i<sfr.rN; ++i)
  221. {
  222. // write the record to the output file
  223. unsigned scUid = _cmMsf_WriteMatchFileLine( fH, scH, sfr.rV + i );
  224. // insert the event->score match in the score match graphics object
  225. if( cmScoreMatchGraphicInsertMidi( smgH, sfr.rV[i].muid, sfr.rV[i].pitch, sfr.rV[i].vel, scUid ) != kOkSmgRC )
  226. {
  227. rc = cmErrMsg(&err,kFailMsfRC,"Score Match Graphics MIDI event insertion failed.");
  228. goto errLabel;
  229. }
  230. }
  231. _cmMsf_ReportScoreErrors(&sfr, scH );
  232. //_cmMsf_ReportMidiErrors(&sfr, scH, m, mN);
  233. //cmScorePrint(scH,&ctx->rpt);
  234. //cmMidiFilePrintMsgs( mfH, &ctx->rpt );
  235. // write the tracking match file as an SVG file.
  236. cmScoreMatchGraphicWrite( smgH, matchSvgOutFn );
  237. // write a cmTimeLine file which contains markers at each bar position
  238. if( tlBarOutFn != NULL )
  239. cmScoreMatchGraphicGenTimeLineBars(smgH, tlBarOutFn, srate );
  240. if( midiOutFn != NULL )
  241. cmScoreMatchGraphicUpdateMidiFromScore( ctx, smgH, midiOutFn );
  242. errLabel:
  243. cmFileClose(&fH);
  244. cmMemFree(sfr.rV);
  245. cmMidiFileClose(&mfH);
  246. cmScMatcherFree(&smp);
  247. cmScoreFinalize(&scH);
  248. cmScoreMatchGraphicFree(&smgH);
  249. cmCtxFree(&prCtx);
  250. //cmFsFreeFn(scoreCsvFn);
  251. //cmFsFreeFn(midiFn);
  252. //cmFsFreeFn(matchRptOutFn);
  253. //cmFsFreeFn(matchSvgOutFn);
  254. //cmFsFreeFn(outMidiFn);
  255. //cmFsFreeFn(tlBarFn);
  256. return rc;
  257. }