cmtools is a collection of utilities for generating and processing machine readable musical scores based on MusicXML, MIDI and other data files.
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.

cmtools.c 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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 "cmFileSys.h"
  12. #include "cmText.h"
  13. #include "cmPgmOpts.h"
  14. #include "cmXScore.h"
  15. #include "cmMidiScoreFollow.h"
  16. #include "cmScoreProc.h"
  17. #include "cmSymTbl.h"
  18. #include "cmTime.h"
  19. #include "cmMidi.h"
  20. #include "cmScore.h"
  21. #include "cmMidiFile.h"
  22. #include "cmFloatTypes.h"
  23. #include "cmAudioFile.h"
  24. #include "cmTimeLine.h"
  25. enum
  26. {
  27. kOkCtRC = cmOkRC,
  28. kNoActionIdSelectedCtRC,
  29. kMissingRequiredFileNameCtRC,
  30. kScoreGenFailedCtRC,
  31. kScoreEditMergeFailedCtRC,
  32. kScoreFollowFailedCtRC,
  33. kMidiFileRptFailedCtRC,
  34. kTimeLineRptFailedCtRC,
  35. kAudioFileRptFailedCtRC
  36. };
  37. const cmChar_t poEndHelpStr[] = "";
  38. const cmChar_t poBegHelpStr[] =
  39. "xscore_proc Music XML to electronic score generator\n"
  40. "\n"
  41. "USAGE:\n"
  42. "\n"
  43. "Parse an XML score file and 'edit' file to produce a score file in CSV format.\n"
  44. "\n"
  45. "cmtool --score_gen -x <xml_file> -d <edit_fn> {-c <csvScoreOutFn} {-m <midiOutFn>} {-s <svgOutFn>} {-r report} {-b begMeasNumb} {t begTempoBPM}\n"
  46. "\n"
  47. "Notes:\n"
  48. "1. If <edit_fn> does not exist then a edit template file will be generated based on the MusicXML file. \n"
  49. "2. Along with the CSV score file MIDI and HTML/SVG files will also be produced based on the contents of the MusicXML and edit file.\n"
  50. "3. See README.md for a detailed description of the how to edit the edit file.\n"
  51. "\n"
  52. "\n"
  53. "Use the score follower to generate a timeline configuration file.\n"
  54. "\n"
  55. "cmtool --timeline_gen -c <csvScoreFn> -i <midiInFn> -r <matchRptFn> -s <matchSvgFn> {-m <midiOutFn>} {-t timelineOutFn} \n"
  56. "\n"
  57. "Measure some perforamance attributes:\n"
  58. "\n"
  59. "cmtool --meas_gen -g <pgmRsrcFn> -r <measRptFn>\n"
  60. "\n"
  61. "Generate a score file report\n"
  62. "\n"
  63. "cmtool --score_report -c <csvScoreFn> -r <scoreRptFn>\n"
  64. "\n"
  65. "Generate a MIDI file report and optional SVG piano roll image\n"
  66. "\n"
  67. "cmtool --midi_report -i <midiInFn> -r <midiRptFn> {-s <svgOutFn>}\n"
  68. "\n"
  69. "Generate a timeline report\n"
  70. "\n"
  71. "cmtool --timeline_report -t <timelineFn> -r <timelineRptFn>\n"
  72. "\n"
  73. "Generate an audio file report\n"
  74. "\n"
  75. "cmtool --audiofile_report -a <audioFn> -r <rptFn>\n"
  76. "\n";
  77. void print( void* arg, const char* text )
  78. {
  79. printf("%s",text);
  80. }
  81. bool verify_file_exists( cmCtx_t* ctx, const cmChar_t* fn, const cmChar_t* msg )
  82. {
  83. if( fn == NULL || cmFsIsFile(fn)==false )
  84. return cmErrMsg(&ctx->err,kMissingRequiredFileNameCtRC,"The required file <%s> does not exist.",msg);
  85. return kOkCtRC;
  86. }
  87. bool verify_non_null_filename( cmCtx_t* ctx, const cmChar_t* fn, const cmChar_t* msg )
  88. {
  89. if( fn == NULL )
  90. return cmErrMsg(&ctx->err,kMissingRequiredFileNameCtRC,"The required file name <%s> is blank.",msg);
  91. return kOkCtRC;
  92. }
  93. cmRC_t score_gen( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* editFn, const cmChar_t* csvOutFn, const cmChar_t* midiOutFn, const cmChar_t* svgOutFn, unsigned reportFl, int begMeasNumb, int begTempoBPM, bool svgStandAloneFl, bool svgPanZoomFl, bool damperRptFl )
  94. {
  95. cmRC_t rc;
  96. if((rc = verify_file_exists(ctx,xmlFn,"XML file")) != kOkCtRC )
  97. return rc;
  98. if( cmXScoreTest( ctx, xmlFn, editFn, csvOutFn, midiOutFn, svgOutFn, reportFl, begMeasNumb, begTempoBPM, svgStandAloneFl, svgPanZoomFl, damperRptFl ) != kOkXsRC )
  99. return cmErrMsg(&ctx->err,kScoreGenFailedCtRC,"score_gen failed.");
  100. return kOkCtRC;
  101. }
  102. cmRC_t score_edit_merge( cmCtx_t* ctx, const cmChar_t* xmlFn, const cmChar_t* editFn, unsigned begMeasNumb, const cmChar_t* keyEditFn, unsigned keyMeasNumb, const cmChar_t* outFn )
  103. {
  104. cmRC_t rc;
  105. if((rc = verify_file_exists(ctx,xmlFn,"XML file")) != kOkCtRC )
  106. return rc;
  107. if((rc = verify_file_exists(ctx,editFn,"reference edit file")) != kOkCtRC )
  108. return rc;
  109. if((rc = verify_file_exists(ctx,editFn,"key edit file")) != kOkCtRC )
  110. return rc;
  111. if( cmXScoreMergeEditFiles( ctx, xmlFn, editFn, begMeasNumb, keyEditFn, keyMeasNumb, outFn ) != kOkXsRC )
  112. return cmErrMsg(&ctx->err,kScoreEditMergeFailedCtRC,"Score merge failed failed.");
  113. return kOkCtRC;
  114. }
  115. cmRC_t score_follow( cmCtx_t* ctx, const cmChar_t* csvScoreFn, const cmChar_t* midiInFn, const cmChar_t* matchRptOutFn, const cmChar_t* matchSvgOutFn, const cmChar_t* midiOutFn, const cmChar_t* timelineFn )
  116. {
  117. cmRC_t rc;
  118. if((rc = verify_file_exists(ctx,csvScoreFn,"Score CSV file")) != kOkCtRC )
  119. return rc;
  120. if((rc = verify_file_exists(ctx,midiInFn,"MIDI input file")) != kOkCtRC )
  121. return rc;
  122. //if((rc = verify_file_exists(ctx,matchRptOutFn,"Match report file")) != kOkCtRC )
  123. // return rc;
  124. //if((rc = verify_file_exists(ctx,matchSvgOutFn,"Match HTML/SVG file")) != kOkCtRC )
  125. // return rc;
  126. if(cmMidiScoreFollowMain(ctx, csvScoreFn, midiInFn, matchRptOutFn, matchSvgOutFn, midiOutFn, timelineFn) != kOkMsfRC )
  127. return cmErrMsg(&ctx->err,kScoreFollowFailedCtRC,"score_follow failed.");
  128. return kOkCtRC;
  129. }
  130. cmRC_t meas_gen( cmCtx_t* ctx, const cmChar_t* pgmRsrcFn, const cmChar_t* outFn )
  131. {
  132. cmRC_t rc;
  133. if((rc = verify_file_exists(ctx,pgmRsrcFn,"Program resource file")) != kOkCtRC )
  134. return rc;
  135. if((rc = verify_non_null_filename( ctx,outFn,"Measurements output file.")) != kOkCtRC )
  136. return rc;
  137. return cmScoreProc(ctx, "meas", pgmRsrcFn, outFn );
  138. }
  139. cmRC_t score_report( cmCtx_t* ctx, const cmChar_t* csvScoreFn, const cmChar_t* rptFn )
  140. {
  141. cmRC_t rc;
  142. if((rc = verify_file_exists(ctx,csvScoreFn,"Score CSV file")) != kOkCtRC )
  143. return rc;
  144. cmScoreReport(ctx,csvScoreFn,rptFn);
  145. return rc;
  146. }
  147. cmRC_t midi_file_report( cmCtx_t* ctx, const cmChar_t* midiFn, const cmChar_t* rptFn, const cmChar_t* svgFn, bool standAloneFl, bool panZoomFl )
  148. {
  149. cmRC_t rc ;
  150. if((rc = verify_file_exists(ctx,midiFn,"MIDI file")) != kOkCtRC )
  151. return rc;
  152. if((rc = cmMidiFileReport(ctx, midiFn, rptFn )) != kOkMfRC )
  153. return cmErrMsg(&ctx->err,kMidiFileRptFailedCtRC,"MIDI file report generation failed.");
  154. if( svgFn != NULL )
  155. if((rc = cmMidiFileGenSvgFile(ctx, midiFn, svgFn, "midi_file_svg.css", standAloneFl, panZoomFl )) != kOkMfRC )
  156. return cmErrMsg(&ctx->err,kMidiFileRptFailedCtRC,"MIDI file SVG output generation failed.");
  157. return kOkCtRC;
  158. }
  159. cmRC_t timeline_report( cmCtx_t* ctx, const cmChar_t* timelineFn, const cmChar_t* tlPrefixPath, const cmChar_t* rptFn )
  160. {
  161. cmRC_t rc ;
  162. if((rc = verify_file_exists(ctx,timelineFn,"Timeline file")) != kOkCtRC )
  163. return rc;
  164. if((rc = cmTimeLineReport( ctx, timelineFn, tlPrefixPath, rptFn )) != kOkTlRC )
  165. return cmErrMsg(&ctx->err,kTimeLineRptFailedCtRC,"The timeline file report failed.");
  166. return rc;
  167. }
  168. cmRC_t audio_file_report( cmCtx_t* ctx, const cmChar_t* audioFn, const cmChar_t* rptFn )
  169. {
  170. cmRC_t rc;
  171. if((rc = verify_file_exists(ctx,audioFn,"Audio file")) != kOkCtRC )
  172. return rc;
  173. if((rc = cmAudioFileReportInfo( ctx, audioFn, rptFn )) != kOkTlRC )
  174. return cmErrMsg(&ctx->err,kAudioFileRptFailedCtRC,"The audio file report failed.");
  175. return rc;
  176. }
  177. int main( int argc, char* argv[] )
  178. {
  179. cmRC_t rc = cmOkRC;
  180. enum
  181. {
  182. kInvalidPoId = kBasePoId,
  183. kActionPoId,
  184. kXmlFileNamePoId,
  185. kEditFileNamePoId,
  186. kKeyEditFileNamePoId,
  187. kKeyMeasNumbPoId,
  188. kOutEditFileNamePoId,
  189. kCsvOutFileNamePoId,
  190. kPgmRsrcFileNamePoId,
  191. kMidiOutFileNamePoId,
  192. kMidiInFileNamePoId,
  193. kSvgOutFileNamePoId,
  194. kStatusOutFileNamePoId,
  195. kTimelineFileNamePoId,
  196. kTimelinePrefixPoId,
  197. kAudioFileNamePoId,
  198. kReportFlagPoId,
  199. kSvgStandAloneFlPoId,
  200. kSvgPanZoomFlPoId,
  201. kBegMeasPoId,
  202. kBegBpmPoId,
  203. kDamperRptPoId,
  204. kBegMidiUidPoId,
  205. kEndMidiUidPoId
  206. };
  207. enum {
  208. kNoSelId,
  209. kScoreGenSelId,
  210. kScoreEditMergeSelId,
  211. kScoreFollowSelId,
  212. kMeasGenSelId,
  213. kScoreReportSelId,
  214. kMidiReportSelId,
  215. kTimelineReportSelId,
  216. kAudioReportSelId
  217. };
  218. // initialize the heap check library
  219. bool memDebugFl = 0; //cmDEBUG_FL;
  220. unsigned memGuardByteCnt = memDebugFl ? 8 : 0;
  221. unsigned memAlignByteCnt = 16;
  222. unsigned memFlags = memDebugFl ? kTrackMmFl | kDeferFreeMmFl | kFillUninitMmFl : 0;
  223. cmPgmOptH_t poH = cmPgmOptNullHandle;
  224. const cmChar_t* appTitle = "cmtools";
  225. cmCtx_t ctx;
  226. const cmChar_t* xmlFn = NULL;
  227. const cmChar_t* editFn = NULL;
  228. const cmChar_t* keyEditFn = NULL;
  229. const cmChar_t* outEditFn = NULL;
  230. const cmChar_t* pgmRsrcFn = NULL;
  231. const cmChar_t* csvScoreFn = NULL;
  232. const cmChar_t* midiOutFn = NULL;
  233. const cmChar_t* midiInFn = NULL;
  234. const cmChar_t* audioFn = NULL;
  235. const cmChar_t* svgOutFn = NULL;
  236. const cmChar_t* timelineFn = NULL;
  237. const cmChar_t* timelinePrefix = NULL;
  238. const cmChar_t* rptFn = NULL;
  239. unsigned reportFl = 0;
  240. unsigned svgStandAloneFl = 1;
  241. unsigned svgPanZoomFl = 1;
  242. int begMeasNumb = 0;
  243. int keyMeasNumb = 0;
  244. int begTempoBPM = 60;
  245. unsigned damperRptFl = 0;
  246. unsigned begMidiUId = cmInvalidId;
  247. unsigned endMidiUId = cmInvalidId;
  248. unsigned actionSelId = kNoSelId;
  249. cmCtxSetup(&ctx,appTitle,print,print,NULL,memGuardByteCnt,memAlignByteCnt,memFlags);
  250. cmMdInitialize( memGuardByteCnt, memAlignByteCnt, memFlags, &ctx.rpt );
  251. cmFsInitialize( &ctx, appTitle);
  252. cmTsInitialize(&ctx );
  253. cmPgmOptInitialize(&ctx, &poH, poBegHelpStr, poEndHelpStr );
  254. cmPgmOptInstallEnum( poH, kActionPoId, 'S', "score_gen", 0, kScoreGenSelId, kNoSelId, &actionSelId, 1,
  255. "Run the score generation tool.","Action selector");
  256. cmPgmOptInstallEnum( poH, kActionPoId, 'D', "merge_edit", 0, kScoreEditMergeSelId, kNoSelId, &actionSelId, 1,
  257. "Synchronize and copy the edit information from one edit file into another.","Action selector");
  258. cmPgmOptInstallEnum( poH, kActionPoId, 'F', "score_follow", 0, kScoreFollowSelId, kNoSelId, &actionSelId, 1,
  259. "Run the time line marker generation tool.",NULL);
  260. cmPgmOptInstallEnum( poH, kActionPoId, 'M', "meas_gen", 0, kMeasGenSelId, kNoSelId, &actionSelId, 1,
  261. "Generate perfomance measurements.",NULL);
  262. cmPgmOptInstallEnum( poH, kActionPoId, 'R', "score_report", 0, kScoreReportSelId, kNoSelId, &actionSelId, 1,
  263. "Generate a score file report.",NULL);
  264. cmPgmOptInstallEnum( poH, kActionPoId, 'I', "midi_report", 0, kMidiReportSelId, kNoSelId, &actionSelId, 1,
  265. "Generate a MIDI file report and optional SVG piano roll output.",NULL);
  266. cmPgmOptInstallEnum( poH, kActionPoId, 'E', "timeline_report", 0, kTimelineReportSelId, kNoSelId, &actionSelId, 1,
  267. "Generate a timeline report.",NULL);
  268. cmPgmOptInstallEnum( poH, kActionPoId, 'A', "audio_report", 0, kAudioReportSelId, kNoSelId, &actionSelId, 1,
  269. "Generate an audio file report.",NULL);
  270. cmPgmOptInstallStr( poH, kXmlFileNamePoId, 'x', "music_xml_fn",0, NULL, &xmlFn, 1,
  271. "Name of the input MusicXML file.");
  272. cmPgmOptInstallStr( poH, kEditFileNamePoId, 'd', "edit_fn", 0, NULL, &editFn, 1,
  273. "Name of a score edit file.");
  274. cmPgmOptInstallStr( poH, kKeyEditFileNamePoId, 'k', "key_edit_fn", 0, NULL, &keyEditFn, 1,
  275. "Name of a score edit key file.");
  276. cmPgmOptInstallInt( poH, kKeyMeasNumbPoId, 'q', "key_meas", 0, 1, &keyMeasNumb, 1,
  277. "Number of the first measure number to merge in the edit key filke (see --key_edit_fn)." );
  278. cmPgmOptInstallStr( poH, kOutEditFileNamePoId, 'o', "out_edit_fn", 0, NULL, &outEditFn, 1,
  279. "Name of a score edit merge file.");
  280. cmPgmOptInstallStr( poH, kCsvOutFileNamePoId, 'c', "score_csv_fn",0, NULL, &csvScoreFn, 1,
  281. "Name of a CSV score file.");
  282. cmPgmOptInstallStr( poH, kPgmRsrcFileNamePoId, 'g', "pgm_rsrc_fn", 0, NULL, &pgmRsrcFn, 1,
  283. "Name of program resource file.");
  284. cmPgmOptInstallStr( poH, kMidiOutFileNamePoId, 'm', "midi_out_fn", 0, NULL, &midiOutFn, 1,
  285. "Name of a MIDI file to generate as output.");
  286. cmPgmOptInstallStr( poH, kMidiInFileNamePoId, 'i', "midi_in_fn", 0, NULL, &midiInFn, 1,
  287. "Name of a MIDI file to generate as output.");
  288. cmPgmOptInstallStr( poH, kSvgOutFileNamePoId, 's', "svg_fn", 0, NULL, &svgOutFn, 1,
  289. "Name of a HTML/SVG file to generate as output.");
  290. cmPgmOptInstallStr( poH, kTimelineFileNamePoId, 't', "timeline_fn", 0, NULL, &timelineFn, 1,
  291. "Name of a timeline to generate as output.");
  292. cmPgmOptInstallStr( poH, kTimelinePrefixPoId, 'l', "tl_prefix", 0, NULL, &timelinePrefix,1,
  293. "Timeline data path prefix.");
  294. cmPgmOptInstallStr( poH, kAudioFileNamePoId, 'a', "audio_fn", 0, NULL, &audioFn, 1,
  295. "Audio file name.");
  296. cmPgmOptInstallStr( poH, kStatusOutFileNamePoId,'r', "report_fn", 0, NULL, &rptFn, 1,
  297. "Name of a status file to generate as output.");
  298. cmPgmOptInstallFlag( poH, kReportFlagPoId, 'f', "debug_fl", 0, 1, &reportFl, 1,
  299. "Print a report of the score following processing." );
  300. cmPgmOptInstallInt( poH, kBegMeasPoId, 'b', "beg_meas", 0, 1, &begMeasNumb, 1,
  301. "The first measure the to be written to the output CSV, MIDI and SVG files." );
  302. cmPgmOptInstallInt( poH, kBegBpmPoId, 'e', "beg_bpm", 0, 0, &begTempoBPM, 1,
  303. "Set to 0 to use the tempo from the score otherwise set to use the tempo at begMeasNumb." );
  304. cmPgmOptInstallFlag( poH, kDamperRptPoId, 'u', "damper", 0, 1, &damperRptFl, 1,
  305. "Print the pedal events during 'score_gen' processing.");
  306. cmPgmOptInstallFlag( poH, kSvgStandAloneFlPoId, 'n', "svg_stand_alone_fl",0, 1, &svgStandAloneFl, 1,
  307. "Write the SVG file as a stand alone HTML file. Enabled by default." );
  308. cmPgmOptInstallFlag( poH, kSvgPanZoomFlPoId, 'z', "svg_pan_zoom_fl", 0, 1, &svgPanZoomFl, 1,
  309. "Include the pan-zoom control. Enabled by default." );
  310. cmPgmOptInstallUInt( poH, kBegMidiUidPoId, 'w', "beg_midi_uid", 0, 1, &begMidiUId, 1,
  311. "Begin MIDI msg. uuid." );
  312. cmPgmOptInstallUInt( poH, kEndMidiUidPoId, 'y', "end_midi_uid", 0, 1, &endMidiUId, 1,
  313. "End MIDI msg. uuid." );
  314. // parse the command line arguments
  315. if( cmPgmOptParse(poH, argc, argv ) == kOkPoRC )
  316. {
  317. // handle the built-in arg's (e.g. -v,-p,-h)
  318. // (returns false if only built-in options were selected)
  319. if( cmPgmOptHandleBuiltInActions(poH, &ctx.rpt ) == false )
  320. goto errLabel;
  321. switch( actionSelId )
  322. {
  323. case kScoreGenSelId:
  324. rc = score_gen( &ctx, xmlFn, editFn, csvScoreFn, midiOutFn, svgOutFn, reportFl, begMeasNumb, begTempoBPM, svgStandAloneFl, svgPanZoomFl, damperRptFl );
  325. break;
  326. case kScoreEditMergeSelId:
  327. rc = score_edit_merge( &ctx, xmlFn, editFn, begMeasNumb, keyEditFn, keyMeasNumb, outEditFn );
  328. break;
  329. case kScoreFollowSelId:
  330. rc = score_follow( &ctx, csvScoreFn, midiInFn, rptFn, svgOutFn, midiOutFn, timelineFn );
  331. break;
  332. case kMeasGenSelId:
  333. rc = meas_gen(&ctx, pgmRsrcFn, rptFn);
  334. break;
  335. case kScoreReportSelId:
  336. rc = score_report(&ctx, csvScoreFn, rptFn );
  337. break;
  338. case kMidiReportSelId:
  339. rc = midi_file_report(&ctx, midiInFn, rptFn, svgOutFn, svgStandAloneFl, svgPanZoomFl );
  340. break;
  341. case kTimelineReportSelId:
  342. rc = timeline_report(&ctx, timelineFn, timelinePrefix, rptFn );
  343. break;
  344. case kAudioReportSelId:
  345. rc = audio_file_report(&ctx, audioFn, rptFn );
  346. break;
  347. default:
  348. rc = cmErrMsg(&ctx.err, kNoActionIdSelectedCtRC,"No action selector was selected.");
  349. }
  350. }
  351. errLabel:
  352. cmPgmOptFinalize(&poH);
  353. cmTsFinalize();
  354. cmFsFinalize();
  355. cmMdReport( kIgnoreNormalMmFl );
  356. cmMdFinalize();
  357. return rc;
  358. }