|
|
|
|
|
|
|
|
* 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))); |