| * Convert a MIDI file into a bytestream of notes | * Convert a MIDI file into a bytestream of notes | ||||
| * | * | ||||
| * | * | ||||
| * (C) Copyright 2011, Len Shustek | |||||
| * (C) Copyright 2011,2013,2015,2016 Len Shustek | |||||
| * | * | ||||
| * This program is free software: you can redistribute it and/or modify | * This program is free software: you can redistribute it and/or modify | ||||
| * it under the terms of version 3 of the GNU General Public License as | * it under the terms of version 3 of the GNU General Public License as | ||||
| * -Changed to allow compilation and execution in 64-bit environments | * -Changed to allow compilation and execution in 64-bit environments | ||||
| * by using C99 standard intN_t and uintN_t types for MIDI structures, | * by using C99 standard intN_t and uintN_t types for MIDI structures, | ||||
| * and formatting specifications like "PRId32" instead of "ld". | * and formatting specifications like "PRId32" instead of "ld". | ||||
| * 04 April 2015, L. Shustek, V1.7 | |||||
| * -Made friendlier to other compilers: import source of strlcpy and strlcat, | |||||
| * fixed various type mismatches that the LCC compiler didn't fret about. | |||||
| * Generate "const" for data initialization for compatibility with Arduino IDE v1.6.x. | |||||
| * 23 January 2016, D. Blackketter, V1.8 | |||||
| * -Fix warnings and errors building on Mac OS X via "gcc miditones.c" | |||||
| * 25 January 2016, D. Blackketter, Paul Stoffregen, V1.9 | |||||
| -Merge in velocity output option from Arduino/Teensy Audio Library | |||||
| */ | */ | ||||
| // Sept 2014 - Add option for velocity output | |||||
| #define VERSION "1.6" | |||||
| #define VERSION "1.9" | |||||
| /*-------------------------------------------------------------------------------- | /*-------------------------------------------------------------------------------- | ||||
| * | * | ||||
| * If the high-order bit of the byte is 1, then it is one of the following commands: | * If the high-order bit of the byte is 1, then it is one of the following commands: | ||||
| * | * | ||||
| * 9t nn Start playing note nn on tone generator t. Generators are numbered | |||||
| * 9t nn [vv] Start playing note nn on tone generator t. Generators are numbered | |||||
| * starting with 0. The notes numbers are the MIDI numbers for the chromatic | * starting with 0. The notes numbers are the MIDI numbers for the chromatic | ||||
| * scale, with decimal 60 being Middle C, and decimal 69 being Middle A (440 Hz). | * scale, with decimal 60 being Middle C, and decimal 69 being Middle A (440 Hz). | ||||
| * if the -v option is enabled, a second byte is added to indicate velocity | |||||
| * | * | ||||
| * 8t Stop playing the note on tone generator t. | * 8t Stop playing the note on tone generator t. | ||||
| * | * | ||||
| void SayUsage(char *programName){ | void SayUsage(char *programName){ | ||||
| static char *usage[] = { | static char *usage[] = { | ||||
| "Convert MIDI files to an Arduino PLAYTUNE bytestream", | "Convert MIDI files to an Arduino PLAYTUNE bytestream", | ||||
| "miditones [-p] [-lg] [-lp] [-s1] [-tn] <basefilename>", | |||||
| "miditones [-p] [-lg] [-lp] [-s1] [-tn] [-v] <basefilename>", | |||||
| " -p parse only, don't generate bytestream", | " -p parse only, don't generate bytestream", | ||||
| " -lp log input parsing", | " -lp log input parsing", | ||||
| " -lg log output generation", | " -lg log output generation", | ||||
| " -b binary file output instead of C source text", | " -b binary file output instead of C source text", | ||||
| " -cn mask for which tracks to process, e.g. -c3 for only 0 and 1", | " -cn mask for which tracks to process, e.g. -c3 for only 0 and 1", | ||||
| " -kn key shift in chromatic notes, positive or negative", | " -kn key shift in chromatic notes, positive or negative", | ||||
| " -v include velocity data in play note commands", | |||||
| "input file: <basefilename>.mid", | "input file: <basefilename>.mid", | ||||
| "output file: <basefilename>.bin or .c", | "output file: <basefilename>.bin or .c", | ||||
| "log file: <basefilename>.log", | "log file: <basefilename>.log", | ||||
| } | } | ||||
| /**************** utility routines **********************/ | /**************** utility routines **********************/ | ||||
| size_t strlcat (char *dst, const char *src, size_t siz) | |||||
| { | |||||
| char *d = dst; | |||||
| const char *s = src; | |||||
| size_t n = siz; | |||||
| size_t dlen; | |||||
| /* Find the end of dst and adjust bytes left but don't go past end */ | |||||
| while (n-- != 0 && *d != '\0') | |||||
| d++; | |||||
| dlen = d - dst; | |||||
| n = siz - dlen; | |||||
| if (n == 0) | |||||
| return (dlen + strlen(s)); | |||||
| while (*s != '\0') { | |||||
| if (n != 1) { | |||||
| *d++ = *s; | |||||
| n--; | |||||
| } | |||||
| s++; | |||||
| } | |||||
| *d = '\0'; | |||||
| return (dlen + (s - src)); /* count does not include NUL */ | |||||
| } | |||||
| size_t strlcpy(char *dst, const char *src, size_t siz) | |||||
| { | |||||
| char *d = dst; | |||||
| const char *s = src; | |||||
| size_t n = siz; | |||||
| /* Copy as many bytes as will fit */ | |||||
| if (n != 0) { | |||||
| while (--n != 0) { | |||||
| if ((*d++ = *s++) == '\0') break; | |||||
| } | |||||
| } | |||||
| /* Not enough room in dst, add NUL and traverse rest of src */ | |||||
| if (n == 0) { | |||||
| if (siz != 0) | |||||
| *d = '\0'; /* NUL-terminate dst */ | |||||
| while (*s++) | |||||
| ; | |||||
| } | |||||
| return (s - src - 1); /* count does not include NUL */ | |||||
| /* safe string copy */ | |||||
| size_t miditones_strlcpy(char *dst, const char *src, size_t siz) { | |||||
| char *d = dst; | |||||
| const char *s = src; | |||||
| size_t n = siz; | |||||
| /* Copy as many bytes as will fit */ | |||||
| if (n != 0) | |||||
| { | |||||
| while (--n != 0) | |||||
| { | |||||
| if ((*d++ = *s++) == '\0') | |||||
| break; | |||||
| } | |||||
| } | |||||
| /* Not enough room in dst, add NUL and traverse rest of src */ | |||||
| if (n == 0) | |||||
| { | |||||
| if (siz != 0) | |||||
| *d = '\0'; /* NUL-terminate dst */ | |||||
| while (*s++) | |||||
| ; | |||||
| } | |||||
| return (s - src - 1); /* count does not include NUL */ | |||||
| } | } | ||||
| /* safe string concatenation */ | |||||
| size_t miditones_strlcat(char *dst, const char *src, size_t siz) { | |||||
| char *d = dst; | |||||
| const char *s = src; | |||||
| size_t n = siz; | |||||
| size_t dlen; | |||||
| /* Find the end of dst and adjust bytes left but don't go past end */ | |||||
| while (n-- != 0 && *d != '\0') | |||||
| d++; | |||||
| dlen = d - dst; | |||||
| n = siz - dlen; | |||||
| if (n == 0) | |||||
| return (dlen + strlen(s)); | |||||
| while (*s != '\0') | |||||
| { | |||||
| if (n != 1) | |||||
| { | |||||
| *d++ = *s; | |||||
| n--; | |||||
| } | |||||
| s++; | |||||
| } | |||||
| *d = '\0'; | |||||
| return (dlen + (s - src)); /* count does not include NUL */ | |||||
| } | |||||
| /* match a constant character sequence */ | /* match a constant character sequence */ | ||||
| /* check that we have a specified number of bytes left in the buffer */ | /* check that we have a specified number of bytes left in the buffer */ | ||||
| void chk_bufdata(unsigned char *ptr, int len) { | |||||
| if (ptr + len - buffer > buflen) midi_error("data missing", ptr); | |||||
| void chk_bufdata(unsigned char *ptr, unsigned long int len) { | |||||
| if ((unsigned)(ptr + len - buffer) > buflen) midi_error("data missing", ptr); | |||||
| } | } | ||||
| chk_bufdata(hdrptr, sizeof(struct midi_header)); | chk_bufdata(hdrptr, sizeof(struct midi_header)); | ||||
| hdr = (struct midi_header *) hdrptr; | hdr = (struct midi_header *) hdrptr; | ||||
| if (!charcmp((char *)(hdr->MThd),"MThd")) midi_error("Missing 'MThd'", hdrptr); | |||||
| if (!charcmp((char*)hdr->MThd,"MThd")) midi_error("Missing 'MThd'", hdrptr); | |||||
| num_tracks = rev_short(hdr->number_of_tracks); | num_tracks = rev_short(hdr->number_of_tracks); | ||||
| unsigned long int delta_time; | unsigned long int delta_time; | ||||
| int event, chan; | int event, chan; | ||||
| int i; | int i; | ||||
| int note, velocity; // , parm; | |||||
| int note, velocity; | |||||
| int meta_cmd, meta_length; | int meta_cmd, meta_length; | ||||
| unsigned long int sysex_length; | unsigned long int sysex_length; | ||||
| struct track_status *t; | struct track_status *t; | ||||
| delta_time = get_varlen(&t->trkptr); | delta_time = get_varlen(&t->trkptr); | ||||
| if (logparse) { | if (logparse) { | ||||
| fprintf (logfile, "trk %d ", tracknum); | fprintf (logfile, "trk %d ", tracknum); | ||||
| fprintf (logfile, delta_time ? "delta time %4ld, " : " ", delta_time); | |||||
| if (delta_time) { | |||||
| fprintf (logfile, "delta time %4ld, ", delta_time); | |||||
| } else { | |||||
| fprintf (logfile, " "); | |||||
| } | |||||
| } | } | ||||
| t->time += delta_time; | t->time += delta_time; | ||||
| #define MAXPATH 120 | #define MAXPATH 120 | ||||
| char filename[MAXPATH]; | char filename[MAXPATH]; | ||||
| //int i; | |||||
| int tracknum; | int tracknum; | ||||
| int earliest_tracknum; | int earliest_tracknum; | ||||
| unsigned long earliest_time; | unsigned long earliest_time; | ||||
| int notes_skipped = 0; | int notes_skipped = 0; | ||||
| printf("MIDITONES V%s, (C) 2011 Len Shustek\n", VERSION); | |||||
| printf("MIDITONES V%s, (C) 2011,2015,2016 Len Shustek\n", VERSION); | |||||
| printf("See the source code for license information.\n\n"); | printf("See the source code for license information.\n\n"); | ||||
| if (argc == 1) { /* no arguments */ | if (argc == 1) { /* no arguments */ | ||||
| SayUsage(argv[0]); | SayUsage(argv[0]); | ||||
| /* Open the log file */ | /* Open the log file */ | ||||
| if (logparse || loggen) { | if (logparse || loggen) { | ||||
| strlcpy(filename, filebasename, MAXPATH); | |||||
| strlcat(filename, ".log", MAXPATH); | |||||
| miditones_strlcpy(filename, filebasename, MAXPATH); | |||||
| miditones_strlcat(filename, ".log", MAXPATH); | |||||
| logfile = fopen(filename, "w"); | logfile = fopen(filename, "w"); | ||||
| if (!logfile) { | if (!logfile) { | ||||
| fprintf(stderr, "Unable to open log file %s", filename); | fprintf(stderr, "Unable to open log file %s", filename); | ||||
| /* Open the input file */ | /* Open the input file */ | ||||
| strlcpy(filename, filebasename, MAXPATH); | |||||
| strlcat(filename, ".mid", MAXPATH); | |||||
| miditones_strlcpy(filename, filebasename, MAXPATH); | |||||
| miditones_strlcat(filename, ".mid", MAXPATH); | |||||
| infile = fopen(filename, "rb"); | infile = fopen(filename, "rb"); | ||||
| if (!infile) { | if (!infile) { | ||||
| fprintf(stderr, "Unable to open input file %s", filename); | fprintf(stderr, "Unable to open input file %s", filename); | ||||
| /* Create the output file */ | /* Create the output file */ | ||||
| if (!parseonly) { | if (!parseonly) { | ||||
| strlcpy(filename, filebasename, MAXPATH); | |||||
| miditones_strlcpy(filename, filebasename, MAXPATH); | |||||
| if (binaryoutput) { | if (binaryoutput) { | ||||
| strlcat(filename, ".bin", MAXPATH); | |||||
| miditones_strlcat(filename, ".bin", MAXPATH); | |||||
| outfile = fopen(filename, "wb"); | outfile = fopen(filename, "wb"); | ||||
| } | } | ||||
| else { | else { | ||||
| strlcat(filename, ".c", MAXPATH); | |||||
| miditones_strlcat(filename, ".c", MAXPATH); | |||||
| outfile = fopen(filename, "w"); | outfile = fopen(filename, "w"); | ||||
| } | } | ||||
| if (!outfile) { | if (!outfile) { | ||||
| } | } | ||||
| if (!binaryoutput) { /* create header of C file that initializes score data */ | if (!binaryoutput) { /* create header of C file that initializes score data */ | ||||
| time_t rawtime; | time_t rawtime; | ||||
| //struct tm *ptime; | |||||
| time (&rawtime); | time (&rawtime); | ||||
| fprintf(outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename); | fprintf(outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename); | ||||
| fprintf(outfile, "created by MIDITONES V%s on %s", VERSION, asctime(localtime(&rawtime))); | fprintf(outfile, "created by MIDITONES V%s on %s", VERSION, asctime(localtime(&rawtime))); |