|
|
@ -5,7 +5,7 @@ |
|
|
|
* Convert a MIDI file into a bytestream of notes |
|
|
|
* Convert a MIDI file into a bytestream of notes |
|
|
|
* |
|
|
|
* |
|
|
|
* |
|
|
|
* |
|
|
|
* (C) Copyright 2011,2013,2015, 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 |
|
|
@ -52,9 +52,11 @@ |
|
|
|
* Generate "const" for data initialization for compatibility with Arduino IDE v1.6.x. |
|
|
|
* Generate "const" for data initialization for compatibility with Arduino IDE v1.6.x. |
|
|
|
* 23 January 2016, D. Blackketter, V1.8 |
|
|
|
* 23 January 2016, D. Blackketter, V1.8 |
|
|
|
* -Fix warnings and errors building on Mac OS X via "gcc miditones.c" |
|
|
|
* -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 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
#define VERSION "1.8" |
|
|
|
#define VERSION "1.9" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------------------------
|
|
|
|
/*--------------------------------------------------------------------------------
|
|
|
@ -146,6 +148,8 @@ |
|
|
|
* -kn Change the musical key of the output by n chromatic notes. |
|
|
|
* -kn Change the musical key of the output by n chromatic notes. |
|
|
|
* -k-12 goes one octave down, -k12 goes one octave up, etc. |
|
|
|
* -k-12 goes one octave down, -k12 goes one octave up, etc. |
|
|
|
* |
|
|
|
* |
|
|
|
|
|
|
|
* -v Add velocity information to output |
|
|
|
|
|
|
|
* |
|
|
|
* |
|
|
|
* |
|
|
|
* ***** The score bytestream ***** |
|
|
|
* ***** The score bytestream ***** |
|
|
|
* |
|
|
|
* |
|
|
@ -155,9 +159,10 @@ |
|
|
|
* |
|
|
|
* |
|
|
|
* 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. |
|
|
|
* |
|
|
|
* |
|
|
@ -213,7 +218,7 @@ struct track_header { |
|
|
|
#define DEFAULT_TONEGENS 6 /* default number of tone generators */ |
|
|
|
#define DEFAULT_TONEGENS 6 /* default number of tone generators */ |
|
|
|
#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */ |
|
|
|
#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */ |
|
|
|
|
|
|
|
|
|
|
|
bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput; |
|
|
|
bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, velocityoutput; |
|
|
|
FILE *infile, *outfile, *logfile; |
|
|
|
FILE *infile, *outfile, *logfile; |
|
|
|
uint8_t *buffer, *hdrptr; |
|
|
|
uint8_t *buffer, *hdrptr; |
|
|
|
unsigned long buflen; |
|
|
|
unsigned long buflen; |
|
|
@ -235,7 +240,7 @@ struct tonegen_status { /* current status of a tone generator */ |
|
|
|
int note; /* what note is playing? */ |
|
|
|
int note; /* what note is playing? */ |
|
|
|
} |
|
|
|
} |
|
|
|
tonegen [MAX_TONEGENS] = { |
|
|
|
tonegen [MAX_TONEGENS] = { |
|
|
|
0}; |
|
|
|
{0}}; |
|
|
|
|
|
|
|
|
|
|
|
struct track_status { /* current processing point of a MIDI track */ |
|
|
|
struct track_status { /* current processing point of a MIDI track */ |
|
|
|
uint8_t *trkptr; /* ptr to the next note change */ |
|
|
|
uint8_t *trkptr; /* ptr to the next note change */ |
|
|
@ -245,11 +250,12 @@ struct track_status { /* current processing point of a MIDI track */ |
|
|
|
unsigned int preferred_tonegen; /* for strategy2: try to use this generator */ |
|
|
|
unsigned int preferred_tonegen; /* for strategy2: try to use this generator */ |
|
|
|
unsigned char cmd; /* CMD_xxxx next to do */ |
|
|
|
unsigned char cmd; /* CMD_xxxx next to do */ |
|
|
|
unsigned char note; /* for which note */ |
|
|
|
unsigned char note; /* for which note */ |
|
|
|
|
|
|
|
unsigned char velocity; |
|
|
|
unsigned char last_event; /* the last event, for MIDI's "running status" */ |
|
|
|
unsigned char last_event; /* the last event, for MIDI's "running status" */ |
|
|
|
bool tonegens[MAX_TONEGENS];/* which tone generators our notes are playing on */ |
|
|
|
bool tonegens[MAX_TONEGENS];/* which tone generators our notes are playing on */ |
|
|
|
} |
|
|
|
} |
|
|
|
track[MAX_TRACKS] = { |
|
|
|
track[MAX_TRACKS] = { |
|
|
|
0}; |
|
|
|
{0}}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* output bytestream commands, which are also stored in track_status.cmd */ |
|
|
|
/* output bytestream commands, which are also stored in track_status.cmd */ |
|
|
@ -271,7 +277,7 @@ track[MAX_TRACKS] = { |
|
|
|
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", |
|
|
@ -281,6 +287,7 @@ void SayUsage(char *programName){ |
|
|
|
" -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", |
|
|
@ -314,6 +321,9 @@ int HandleOptions(int argc,char *argv[]) { |
|
|
|
case 'B': |
|
|
|
case 'B': |
|
|
|
binaryoutput = true; |
|
|
|
binaryoutput = true; |
|
|
|
break; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'V': |
|
|
|
|
|
|
|
velocityoutput = true; |
|
|
|
|
|
|
|
break; |
|
|
|
case 'S': |
|
|
|
case 'S': |
|
|
|
if (argv[i][2] == '1') strategy1 = true; |
|
|
|
if (argv[i][2] == '1') strategy1 = true; |
|
|
|
else if (argv[i][2] == '2') strategy2 = true; |
|
|
|
else if (argv[i][2] == '2') strategy2 = true; |
|
|
@ -413,7 +423,7 @@ size_t miditones_strlcat(char *dst, const char *src, size_t siz) { |
|
|
|
|
|
|
|
|
|
|
|
/* match a constant character sequence */ |
|
|
|
/* match a constant character sequence */ |
|
|
|
|
|
|
|
|
|
|
|
int charcmp (char *buf, char *match) { |
|
|
|
int charcmp (const char *buf, const char *match) { |
|
|
|
int len, i; |
|
|
|
int len, i; |
|
|
|
len = strlen (match); |
|
|
|
len = strlen (match); |
|
|
|
for (i=0; i<len; ++i) |
|
|
|
for (i=0; i<len; ++i) |
|
|
@ -501,7 +511,7 @@ void start_track (int tracknum) { |
|
|
|
|
|
|
|
|
|
|
|
chk_bufdata(hdrptr, sizeof(struct track_header)); |
|
|
|
chk_bufdata(hdrptr, sizeof(struct track_header)); |
|
|
|
hdr = (struct track_header *) hdrptr; |
|
|
|
hdr = (struct track_header *) hdrptr; |
|
|
|
if (!charcmp((char*)hdr->MTrk,"MTrk")) midi_error("Missing 'MTrk'", hdrptr); |
|
|
|
if (!charcmp((char *)(hdr->MTrk),"MTrk")) midi_error("Missing 'MTrk'", hdrptr); |
|
|
|
|
|
|
|
|
|
|
|
tracklen = rev_long(hdr->track_size); |
|
|
|
tracklen = rev_long(hdr->track_size); |
|
|
|
if (logparse) fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen); |
|
|
|
if (logparse) fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen); |
|
|
@ -640,6 +650,7 @@ note_off: |
|
|
|
t->note = *t->trkptr++; |
|
|
|
t->note = *t->trkptr++; |
|
|
|
velocity = *t->trkptr++; |
|
|
|
velocity = *t->trkptr++; |
|
|
|
if (velocity == 0) /* some scores use note-on with zero velocity for off! */ goto note_off; |
|
|
|
if (velocity == 0) /* some scores use note-on with zero velocity for off! */ goto note_off; |
|
|
|
|
|
|
|
t->velocity = velocity; |
|
|
|
if (logparse) fprintf (logfile, "note %02X on, chan %d, velocity %d\n", t->note, chan, velocity); |
|
|
|
if (logparse) fprintf (logfile, "note %02X on, chan %d, velocity %d\n", t->note, chan, velocity); |
|
|
|
if ((1<<chan) & channel_mask) { // if we're processing this channel
|
|
|
|
if ((1<<chan) & channel_mask) { // if we're processing this channel
|
|
|
|
t->cmd = CMD_PLAYNOTE; |
|
|
|
t->cmd = CMD_PLAYNOTE; |
|
|
@ -697,7 +708,7 @@ int main(int argc,char *argv[]) { |
|
|
|
unsigned long earliest_time; |
|
|
|
unsigned long earliest_time; |
|
|
|
int notes_skipped = 0; |
|
|
|
int notes_skipped = 0; |
|
|
|
|
|
|
|
|
|
|
|
printf("MIDITONES V%s, (C) 2011,2015 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]); |
|
|
@ -773,7 +784,12 @@ int main(int argc,char *argv[]) { |
|
|
|
fprintf(outfile, "// Only the masked channels were processed: %04X\n", channel_mask); |
|
|
|
fprintf(outfile, "// Only the masked channels were processed: %04X\n", channel_mask); |
|
|
|
if (keyshift != 0) |
|
|
|
if (keyshift != 0) |
|
|
|
fprintf(outfile, "// Keyshift was %d chromatic notes\n", keyshift); |
|
|
|
fprintf(outfile, "// Keyshift was %d chromatic notes\n", keyshift); |
|
|
|
fprintf(outfile, "const byte PROGMEM score [] = {\n"); |
|
|
|
fprintf(outfile, "#ifdef __AVR__\n"); |
|
|
|
|
|
|
|
fprintf(outfile, "#include <avr/pgmspace.h>\n"); |
|
|
|
|
|
|
|
fprintf(outfile, "#else\n"); |
|
|
|
|
|
|
|
fprintf(outfile, "#define PROGMEM\n"); |
|
|
|
|
|
|
|
fprintf(outfile, "#endif\n"); |
|
|
|
|
|
|
|
fprintf(outfile, "const unsigned char PROGMEM score [] = {\n"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -932,10 +948,19 @@ int main(int argc,char *argv[]) { |
|
|
|
putc (CMD_PLAYNOTE | tgnum, outfile); |
|
|
|
putc (CMD_PLAYNOTE | tgnum, outfile); |
|
|
|
putc (shifted_note, outfile); |
|
|
|
putc (shifted_note, outfile); |
|
|
|
outfile_bytecount += 2; |
|
|
|
outfile_bytecount += 2; |
|
|
|
|
|
|
|
if (velocityoutput) { |
|
|
|
|
|
|
|
putc (trk->velocity, outfile); |
|
|
|
|
|
|
|
outfile_bytecount++; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); |
|
|
|
if (velocityoutput == 0) { |
|
|
|
outfile_items(2); |
|
|
|
fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); |
|
|
|
|
|
|
|
outfile_items(2); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
fprintf (outfile, "0x%02X,%d,%d, ", CMD_PLAYNOTE | tgnum, shifted_note, trk->velocity); |
|
|
|
|
|
|
|
outfile_items(3); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|