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

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