|
|
|
@ -191,7 +191,7 @@ |
|
|
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR |
|
|
|
|
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
|
*********************************************************************************************/ |
|
|
|
|
// formatted with: indent miditones.c -br -brf -brs -ce -npsl -nut -i3 -l100 -lc100
|
|
|
|
|
// formatted with: Astyle -style=lisp -indent=spaces=3 -mode=c
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Change log |
|
|
|
@ -264,8 +264,14 @@ |
|
|
|
|
* - Don't generate zero-length delays |
|
|
|
|
* 13 September 2018, Paul Stoffregen, V1.17 |
|
|
|
|
- Fix compile errors on Linux with gcc run in default mode |
|
|
|
|
* 1 January 2019, Len Shustek, V1.18 |
|
|
|
|
- Fix the bug found by Chris van Marle (thanks!) that caused delays not to be |
|
|
|
|
generated until the tempo was set. (The default is 500,000 usec/beat, not 0.) |
|
|
|
|
- Abandon LCC and compile under Microsoft Visual Studio 2017. |
|
|
|
|
- Reformat to condense the source code, so you see more protein and less |
|
|
|
|
syntactic sugar on each screen. |
|
|
|
|
*/ |
|
|
|
|
#define VERSION "1.17" |
|
|
|
|
#define VERSION "1.18" |
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
@ -280,21 +286,19 @@ lower case letters are hex digits. If preceeded by 0, only low 7 bits are used. |
|
|
|
|
"xx" are ascii text characters |
|
|
|
|
{xxx}... means indefinite repeat of xxx |
|
|
|
|
|
|
|
|
|
A MIDI file is: header_chunk {track_chunk}... |
|
|
|
|
a MIDI file is: |
|
|
|
|
header_chunk {track_chunk}... |
|
|
|
|
|
|
|
|
|
header_chunk |
|
|
|
|
a header_chunk is: |
|
|
|
|
"MThd" 00000006 ffff nnnn dddd |
|
|
|
|
|
|
|
|
|
track_chunk |
|
|
|
|
|
|
|
|
|
a track_chunk is: |
|
|
|
|
"MTrk" llllllll {<deltatime> track_event}... |
|
|
|
|
|
|
|
|
|
"running status" track_event |
|
|
|
|
|
|
|
|
|
a running status track_event is: |
|
|
|
|
0x to 7x: assume a missing 8n to En event code which is the same as the last MIDI-event track_event |
|
|
|
|
|
|
|
|
|
MIDI-event track_event |
|
|
|
|
|
|
|
|
|
a MIDI-event track_event is: |
|
|
|
|
8n 0kk 0vv note off, channel n, note kk, velocity vv |
|
|
|
|
9n 0kk 0vv note on, channel n, note kk, velocity vv |
|
|
|
|
An 0kk 0vv key pressure, channel n, note kk, pressure vv |
|
|
|
@ -305,8 +309,7 @@ En 0ll 0mm pitch wheel change, value llmm |
|
|
|
|
|
|
|
|
|
Note that channel 9 (called 10 by some programs) is used for percussion, particularly notes 35 to 81. |
|
|
|
|
|
|
|
|
|
Sysex event track_event |
|
|
|
|
|
|
|
|
|
a Sysex event track_event is: |
|
|
|
|
F0 0ii {0dd}... F7 system-dependent data for manufacture ii. See www.gweep.net/~prefect/eng/reference/protocol/midispec.html |
|
|
|
|
F2 0ll 0mm song position pointer |
|
|
|
|
F3 0ss song select |
|
|
|
@ -318,8 +321,7 @@ FB continue playing |
|
|
|
|
FC stop playing |
|
|
|
|
FE active sensing (hearbeat) |
|
|
|
|
|
|
|
|
|
Meta event track_event |
|
|
|
|
|
|
|
|
|
a meta event track_event is: |
|
|
|
|
FF 00 02 ssss specify sequence number |
|
|
|
|
FF 01 <len> "xx"... arbitrary text |
|
|
|
|
FF 02 <len> "xx"... copyright notice |
|
|
|
@ -353,13 +355,11 @@ struct midi_header { |
|
|
|
|
uint32_t header_size; |
|
|
|
|
uint16_t format_type; |
|
|
|
|
uint16_t number_of_tracks; |
|
|
|
|
uint16_t time_division; |
|
|
|
|
}; |
|
|
|
|
uint16_t time_division; }; |
|
|
|
|
|
|
|
|
|
struct track_header { |
|
|
|
|
int8_t MTrk[4]; |
|
|
|
|
uint32_t track_size; |
|
|
|
|
}; |
|
|
|
|
uint32_t track_size; }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********** Global variables ******************/ |
|
|
|
@ -368,6 +368,7 @@ struct track_header { |
|
|
|
|
#define DEFAULT_TONEGENS 6 /* default number of tone generators */ |
|
|
|
|
#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */ |
|
|
|
|
#define PERCUSSION_TRACK 9 /* the track MIDI uses for percussion sounds */ |
|
|
|
|
#define DEFAULT_TEMPO 500000L /* the MIDI-specified default tempo in usec/beat */ |
|
|
|
|
|
|
|
|
|
bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, define_progmem, |
|
|
|
|
velocityoutput, instrumentoutput, percussion_ignore, percussion_translate, do_header, |
|
|
|
@ -398,8 +399,7 @@ struct tonegen_status { /* current status of a tone generator */ |
|
|
|
|
int instrument; /* what instrument? */ |
|
|
|
|
} tonegen[MAX_TONEGENS] = { |
|
|
|
|
{ |
|
|
|
|
0} |
|
|
|
|
}; |
|
|
|
|
0 } }; |
|
|
|
|
|
|
|
|
|
struct track_status { /* current processing point of a MIDI track */ |
|
|
|
|
uint8_t *trkptr; /* ptr to the next note change */ |
|
|
|
@ -415,12 +415,10 @@ struct track_status { /* current processing point of a MIDI track */ |
|
|
|
|
bool tonegens[MAX_TONEGENS]; /* which tone generators our notes are playing on */ |
|
|
|
|
} track[MAX_TRACKS] = { |
|
|
|
|
{ |
|
|
|
|
0} |
|
|
|
|
}; |
|
|
|
|
0 } }; |
|
|
|
|
|
|
|
|
|
int midi_chan_instrument[16] = { |
|
|
|
|
0 |
|
|
|
|
}; /* which instrument is currently being played on each channel */ |
|
|
|
|
0 }; /* which instrument is currently being played on each channel */ |
|
|
|
|
|
|
|
|
|
/* output bytestream commands, which are also stored in track_status.cmd */ |
|
|
|
|
|
|
|
|
@ -486,12 +484,10 @@ void SayUsage (char *programName) { |
|
|
|
|
" -pi ignore notes in the percussion track (9)", |
|
|
|
|
" -dp define PROGMEM in output C code", |
|
|
|
|
" -r terminate output file with \"restart\" instead of \"stop\" command", |
|
|
|
|
NULL |
|
|
|
|
}; |
|
|
|
|
NULL }; |
|
|
|
|
int i = 0; |
|
|
|
|
while (usage[i] != NULL) |
|
|
|
|
fprintf (stderr, "%s\n", usage[i++]); |
|
|
|
|
} |
|
|
|
|
fprintf (stderr, "%s\n", usage[i++]); } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int HandleOptions (int argc, char *argv[]) { |
|
|
|
@ -521,8 +517,7 @@ does not start with a dash or a slash*/ |
|
|
|
|
case 'P': |
|
|
|
|
if (argv[i][2] == '\0') { |
|
|
|
|
parseonly = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
break; } |
|
|
|
|
else if (toupper (argv[i][2]) == 'I') |
|
|
|
|
percussion_ignore = true; |
|
|
|
|
else if (toupper (argv[i][2]) == 'T') |
|
|
|
@ -589,8 +584,7 @@ does not start with a dash or a slash*/ |
|
|
|
|
case 'D': |
|
|
|
|
if (argv[i][2] == '\0') { |
|
|
|
|
do_header = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
break; } |
|
|
|
|
if (toupper (argv[i][2]) == 'P') |
|
|
|
|
define_progmem = true; |
|
|
|
|
else |
|
|
|
@ -608,23 +602,18 @@ does not start with a dash or a slash*/ |
|
|
|
|
default: |
|
|
|
|
fprintf (stderr, "\n*** unknown option: %s\n\n", argv[i]); |
|
|
|
|
SayUsage (argv[0]); |
|
|
|
|
exit (4); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
exit (4); } } |
|
|
|
|
else { |
|
|
|
|
firstnonoption = i; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return firstnonoption; |
|
|
|
|
} |
|
|
|
|
break; } } |
|
|
|
|
return firstnonoption; } |
|
|
|
|
|
|
|
|
|
void print_command_line (int argc, char *argv[]) { |
|
|
|
|
int i; |
|
|
|
|
fprintf (outfile, "// command line: "); |
|
|
|
|
for (i = 0; i < argc; i++) |
|
|
|
|
fprintf (outfile, "%s ", argv[i]); |
|
|
|
|
fprintf (outfile, "\n"); |
|
|
|
|
} |
|
|
|
|
fprintf (outfile, "\n"); } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**************** utility routines **********************/ |
|
|
|
@ -633,8 +622,7 @@ void print_command_line (int argc, char *argv[]) { |
|
|
|
|
int strlength (const char *str) { |
|
|
|
|
int i; |
|
|
|
|
for (i = 0; str[i] != '\0'; ++i); |
|
|
|
|
return i; |
|
|
|
|
} |
|
|
|
|
return i; } |
|
|
|
|
|
|
|
|
|
/* safe string copy */ |
|
|
|
|
size_t miditones_strlcpy (char *dst, const char *src, size_t siz) { |
|
|
|
@ -645,15 +633,12 @@ size_t miditones_strlcpy (char *dst, const char *src, size_t siz) { |
|
|
|
|
if (n != 0) { |
|
|
|
|
while (--n != 0) { |
|
|
|
|
if ((*d++ = *s++) == '\0') |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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++); |
|
|
|
|
} |
|
|
|
|
while (*s++); } |
|
|
|
|
return (s - src - 1); /* count does not include NUL */ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -674,10 +659,8 @@ size_t miditones_strlcat (char *dst, const char *src, size_t siz) { |
|
|
|
|
while (*s != '\0') { |
|
|
|
|
if (n != 1) { |
|
|
|
|
*d++ = *s; |
|
|
|
|
n--; |
|
|
|
|
} |
|
|
|
|
s++; |
|
|
|
|
} |
|
|
|
|
n--; } |
|
|
|
|
s++; } |
|
|
|
|
*d = '\0'; |
|
|
|
|
return (dlen + (s - src)); /* count does not include NUL */ |
|
|
|
|
} |
|
|
|
@ -690,8 +673,7 @@ int charcmp (const char *buf, const char *match) { |
|
|
|
|
for (i = 0; i < len; ++i) |
|
|
|
|
if (buf[i] != match[i]) |
|
|
|
|
return 0; |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
return 1; } |
|
|
|
|
|
|
|
|
|
/* announce a fatal MIDI file format error */ |
|
|
|
|
|
|
|
|
@ -706,26 +688,22 @@ void midi_error (char *msg, unsigned char *bufptr) { |
|
|
|
|
for (; ptr <= bufptr + 16 && ptr < buffer + buflen; ++ptr) |
|
|
|
|
fprintf (stderr, ptr == bufptr ? " [%02X] " : "%02X ", *ptr); |
|
|
|
|
fprintf (stderr, "\n"); |
|
|
|
|
exit (8); |
|
|
|
|
} |
|
|
|
|
exit (8); } |
|
|
|
|
|
|
|
|
|
/* check that we have a specified number of bytes left in the buffer */ |
|
|
|
|
|
|
|
|
|
void chk_bufdata (unsigned char *ptr, unsigned long int len) { |
|
|
|
|
if ((unsigned) (ptr + len - buffer) > buflen) |
|
|
|
|
midi_error ("data missing", ptr); |
|
|
|
|
} |
|
|
|
|
midi_error ("data missing", ptr); } |
|
|
|
|
|
|
|
|
|
/* fetch big-endian numbers */ |
|
|
|
|
|
|
|
|
|
uint16_t rev_short (uint16_t val) { |
|
|
|
|
return ((val & 0xff) << 8) | ((val >> 8) & 0xff); |
|
|
|
|
} |
|
|
|
|
return ((val & 0xff) << 8) | ((val >> 8) & 0xff); } |
|
|
|
|
|
|
|
|
|
uint32_t rev_long (uint32_t val) { |
|
|
|
|
return (((rev_short ((uint16_t) val) & 0xffff) << 16) | |
|
|
|
|
(rev_short ((uint16_t) (val >> 16)) & 0xffff)); |
|
|
|
|
} |
|
|
|
|
(rev_short ((uint16_t) (val >> 16)) & 0xffff)); } |
|
|
|
|
|
|
|
|
|
/* account for new items in the non-binary output file
|
|
|
|
|
and generate a newline every so often. */ |
|
|
|
@ -735,9 +713,7 @@ void outfile_items (int n) { |
|
|
|
|
outfile_itemcount += n; |
|
|
|
|
if (!binaryoutput && outfile_itemcount >= outfile_maxitems) { |
|
|
|
|
fprintf (outfile, "\n"); |
|
|
|
|
outfile_itemcount = 0; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
outfile_itemcount = 0; } } |
|
|
|
|
|
|
|
|
|
/************** process the MIDI file header *****************/ |
|
|
|
|
|
|
|
|
@ -760,11 +736,9 @@ void process_header (void) { |
|
|
|
|
fprintf (logfile, "Format type %d\n", rev_short (hdr->format_type)); |
|
|
|
|
fprintf (logfile, "Number of tracks %d\n", num_tracks); |
|
|
|
|
fprintf (logfile, "Time division %04X\n", time_division); |
|
|
|
|
fprintf (logfile, "Ticks/beat = %d\n", ticks_per_beat); |
|
|
|
|
} |
|
|
|
|
fprintf (logfile, "Ticks/beat = %d\n", ticks_per_beat); } |
|
|
|
|
hdrptr += rev_long (hdr->header_size) + 8; /* point past header to track header, presumably. */ |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
return; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**************** Process a MIDI track header *******************/ |
|
|
|
@ -802,10 +776,8 @@ These are a succession of 7-bit values with a MSB bit of zero marking the end */ |
|
|
|
|
byte = *(*ptr)++; |
|
|
|
|
val = (val << 7) | (byte & 0x7f); |
|
|
|
|
if (!(byte & 0x80)) |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
return val; } |
|
|
|
|
return val; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*************** Process the MIDI track data ***************************/ |
|
|
|
@ -832,17 +804,14 @@ void find_note (int tracknum) { |
|
|
|
|
if (logparse) { |
|
|
|
|
fprintf (logfile, "trk %d ", tracknum); |
|
|
|
|
if (delta_time) { |
|
|
|
|
fprintf (logfile, "delta time %4ld, ", delta_time); |
|
|
|
|
} else { |
|
|
|
|
fprintf (logfile, " "); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
fprintf (logfile, "delta time %4ld, ", delta_time); } |
|
|
|
|
else { |
|
|
|
|
fprintf (logfile, " "); } } |
|
|
|
|
t->time += delta_time; |
|
|
|
|
if (*t->trkptr < 0x80) |
|
|
|
|
event = t->last_event; /* using "running status": same event as before */ |
|
|
|
|
else { /* otherwise get new "status" (event type) */ |
|
|
|
|
event = *t->trkptr++; |
|
|
|
|
} |
|
|
|
|
event = *t->trkptr++; } |
|
|
|
|
if (event == 0xff) { /* meta-event */ |
|
|
|
|
meta_cmd = *t->trkptr++; |
|
|
|
|
meta_length = get_varlen (&t->trkptr); |
|
|
|
@ -865,10 +834,8 @@ void find_note (int tracknum) { |
|
|
|
|
fprintf (outfile, "// "); |
|
|
|
|
for (i = 0; i < meta_length; ++i) { |
|
|
|
|
int ch = t->trkptr[i]; |
|
|
|
|
fprintf (outfile, "%c", isprint (ch) ? ch : '?'); |
|
|
|
|
} |
|
|
|
|
fprintf (outfile, "\n"); |
|
|
|
|
} |
|
|
|
|
fprintf (outfile, "%c", isprint (ch) ? ch : '?'); } |
|
|
|
|
fprintf (outfile, "\n"); } |
|
|
|
|
goto show_text; |
|
|
|
|
case 0x04: |
|
|
|
|
tag = "instrument name"; |
|
|
|
@ -886,10 +853,8 @@ void find_note (int tracknum) { |
|
|
|
|
fprintf (logfile, "meta cmd %02X, length %d, %s: \"", meta_cmd, meta_length, tag); |
|
|
|
|
for (i = 0; i < meta_length; ++i) { |
|
|
|
|
int ch = t->trkptr[i]; |
|
|
|
|
fprintf (logfile, "%c", isprint (ch) ? ch : '?'); |
|
|
|
|
} |
|
|
|
|
fprintf (logfile, "\"\n"); |
|
|
|
|
} |
|
|
|
|
fprintf (logfile, "%c", isprint (ch) ? ch : '?'); } |
|
|
|
|
fprintf (logfile, "\"\n"); } |
|
|
|
|
break; |
|
|
|
|
case 0x20: |
|
|
|
|
if (logparse) |
|
|
|
@ -930,13 +895,10 @@ void find_note (int tracknum) { |
|
|
|
|
fprintf (logfile, "meta cmd %02X, length %d, %s: ", meta_cmd, meta_length, tag); |
|
|
|
|
for (i = 0; i < meta_length; ++i) |
|
|
|
|
fprintf (logfile, "%02X ", t->trkptr[i]); |
|
|
|
|
fprintf (logfile, "\n"); |
|
|
|
|
} |
|
|
|
|
fprintf (logfile, "\n"); } |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
t->trkptr += meta_length; |
|
|
|
|
} |
|
|
|
|
break; } |
|
|
|
|
t->trkptr += meta_length; } |
|
|
|
|
|
|
|
|
|
else if (event < 0x80) |
|
|
|
|
midi_error ("Unknown MIDI event type", t->trkptr); |
|
|
|
@ -1006,13 +968,9 @@ void find_note (int tracknum) { |
|
|
|
|
t->trkptr += sysex_length; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
midi_error ("Unknown MIDI command", t->trkptr); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
midi_error ("Unknown MIDI command", t->trkptr); } } } |
|
|
|
|
t->cmd = CMD_TRACKDONE; /* no more notes to process */ |
|
|
|
|
++tracks_done; |
|
|
|
|
} |
|
|
|
|
++tracks_done; } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* generate "stop note" commands for any channels that have them pending */ |
|
|
|
@ -1025,15 +983,11 @@ void gen_stopnotes(void) { |
|
|
|
|
if (tg->stopnote_pending) { |
|
|
|
|
if (binaryoutput) { |
|
|
|
|
putc (CMD_STOPNOTE | tgnum, outfile); |
|
|
|
|
outfile_bytecount += 1; |
|
|
|
|
} else { |
|
|
|
|
outfile_bytecount += 1; } |
|
|
|
|
else { |
|
|
|
|
fprintf (outfile, "0x%02X, ", CMD_STOPNOTE | tgnum); |
|
|
|
|
outfile_items (1); |
|
|
|
|
} |
|
|
|
|
tg->stopnote_pending = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
outfile_items (1); } |
|
|
|
|
tg->stopnote_pending = false; } } } |
|
|
|
|
|
|
|
|
|
/********************* main ****************************/ |
|
|
|
|
|
|
|
|
@ -1050,8 +1004,7 @@ int main (int argc, char *argv[]) { |
|
|
|
|
printf ("MIDITONES V%s, (C) 2011-2016 Len Shustek\n", VERSION); |
|
|
|
|
if (argc == 1) { /* no arguments */ |
|
|
|
|
SayUsage (argv[0]); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
return 1; } |
|
|
|
|
|
|
|
|
|
/* process options */ |
|
|
|
|
|
|
|
|
@ -1059,8 +1012,7 @@ int main (int argc, char *argv[]) { |
|
|
|
|
if (argno == 0) { |
|
|
|
|
fprintf (stderr, "\n*** No basefilename given\n\n"); |
|
|
|
|
SayUsage (argv[0]); |
|
|
|
|
exit (4); |
|
|
|
|
} |
|
|
|
|
exit (4); } |
|
|
|
|
filebasename = argv[argno]; |
|
|
|
|
|
|
|
|
|
/* Open the log file */ |
|
|
|
@ -1071,10 +1023,8 @@ int main (int argc, char *argv[]) { |
|
|
|
|
logfile = fopen (filename, "w"); |
|
|
|
|
if (!logfile) { |
|
|
|
|
fprintf (stderr, "Unable to open log file %s\n", filename); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
fprintf (logfile, "MIDITONES V%s log file\n", VERSION); |
|
|
|
|
} |
|
|
|
|
return 1; } |
|
|
|
|
fprintf (logfile, "MIDITONES V%s log file\n", VERSION); } |
|
|
|
|
|
|
|
|
|
/* Open the input file */ |
|
|
|
|
|
|
|
|
@ -1083,8 +1033,7 @@ int main (int argc, char *argv[]) { |
|
|
|
|
infile = fopen (filename, "rb"); |
|
|
|
|
if (!infile) { |
|
|
|
|
fprintf (stderr, "Unable to open input file %s\n", filename); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
return 1; } |
|
|
|
|
|
|
|
|
|
/* Read the whole input file into memory */ |
|
|
|
|
|
|
|
|
@ -1094,8 +1043,7 @@ int main (int argc, char *argv[]) { |
|
|
|
|
buffer = (unsigned char *) malloc (buflen + 1); |
|
|
|
|
if (!buffer) { |
|
|
|
|
fprintf (stderr, "Unable to allocate %ld bytes for the file\n", buflen); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
return 1; } |
|
|
|
|
fread (buffer, buflen, 1, infile); |
|
|
|
|
fclose (infile); |
|
|
|
|
if (logparse) |
|
|
|
@ -1107,15 +1055,13 @@ int main (int argc, char *argv[]) { |
|
|
|
|
miditones_strlcpy (filename, filebasename, MAXPATH); |
|
|
|
|
if (binaryoutput) { |
|
|
|
|
miditones_strlcat (filename, ".bin", MAXPATH); |
|
|
|
|
outfile = fopen (filename, "wb"); |
|
|
|
|
} else { |
|
|
|
|
outfile = fopen (filename, "wb"); } |
|
|
|
|
else { |
|
|
|
|
miditones_strlcat (filename, ".c", MAXPATH); |
|
|
|
|
outfile = fopen (filename, "w"); |
|
|
|
|
} |
|
|
|
|
outfile = fopen (filename, "w"); } |
|
|
|
|
if (!outfile) { |
|
|
|
|
fprintf (stderr, "Unable to open output file %s\n", filename); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
return 1; } |
|
|
|
|
file_header.f1 = (velocityoutput ? HDR_F1_VOLUME_PRESENT : 0) |
|
|
|
|
| (instrumentoutput ? HDR_F1_INSTRUMENTS_PRESENT : 0) |
|
|
|
|
| (percussion_translate ? HDR_F1_PERCUSSION_PRESENT : 0); |
|
|
|
@ -1136,24 +1082,20 @@ int main (int argc, char *argv[]) { |
|
|
|
|
fprintf (outfile, "#include <avr/pgmspace.h>\n"); |
|
|
|
|
fprintf (outfile, "#else\n"); |
|
|
|
|
fprintf (outfile, "#define PROGMEM\n"); |
|
|
|
|
fprintf (outfile, "#endif\n"); |
|
|
|
|
} |
|
|
|
|
fprintf (outfile, "#endif\n"); } |
|
|
|
|
fprintf (outfile, "const unsigned char PROGMEM score [] = {\n"); |
|
|
|
|
if (do_header) { // write the C initialization for the file header
|
|
|
|
|
fprintf (outfile, "'P','t', 6, 0x%02X, 0x%02X, ", file_header.f1, file_header.f2); |
|
|
|
|
fflush (outfile); |
|
|
|
|
file_header_num_tgens_position = ftell (outfile); // remember where the number of tone generators is
|
|
|
|
|
fprintf (outfile, "%2d, // (Playtune file header)\n", file_header.num_tgens); |
|
|
|
|
outfile_bytecount += 6; |
|
|
|
|
} |
|
|
|
|
} else if (do_header) { // write the binary file header
|
|
|
|
|
outfile_bytecount += 6; } } |
|
|
|
|
else if (do_header) { // write the binary file header
|
|
|
|
|
int i; |
|
|
|
|
for (i = 0; i < sizeof (file_header); ++i) |
|
|
|
|
fputc (((unsigned char *) &file_header)[i], outfile); |
|
|
|
|
file_header_num_tgens_position = (char *) &file_header.num_tgens - (char *) &file_header; |
|
|
|
|
outfile_bytecount += sizeof (file_header); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
outfile_bytecount += sizeof (file_header); } } |
|
|
|
|
|
|
|
|
|
/* process the MIDI file header */ |
|
|
|
|
|
|
|
|
@ -1165,15 +1107,16 @@ int main (int argc, char *argv[]) { |
|
|
|
|
|
|
|
|
|
/* initialize processing of all the tracks */ |
|
|
|
|
|
|
|
|
|
tempo = DEFAULT_TEMPO; |
|
|
|
|
for (tracknum = 0; tracknum < num_tracks; ++tracknum) { |
|
|
|
|
track[tracknum].tempo = DEFAULT_TEMPO; |
|
|
|
|
start_track (tracknum); /* process the track header */ |
|
|
|
|
find_note (tracknum); /* position to the first note on/off */ |
|
|
|
|
/* if we are in "parse only" mode, do the whole track,
|
|
|
|
|
so we do them one at a time instead of time-synchronized. */ |
|
|
|
|
if (parseonly) |
|
|
|
|
while (track[tracknum].cmd != CMD_TRACKDONE) |
|
|
|
|
find_note (tracknum); |
|
|
|
|
} |
|
|
|
|
find_note (tracknum); } |
|
|
|
|
|
|
|
|
|
/* Continue processing all tracks, in an order based on the simulated time.
|
|
|
|
|
This is not unlike multiway merging used for tape sorting algoritms in the 50's! */ |
|
|
|
@ -1213,9 +1156,7 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
trk = &track[tracknum]; |
|
|
|
|
if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) { |
|
|
|
|
earliest_time = trk->time; |
|
|
|
|
earliest_tracknum = tracknum; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
earliest_tracknum = tracknum; } } |
|
|
|
|
while (--count_tracks); |
|
|
|
|
|
|
|
|
|
tracknum = earliest_tracknum; /* the track we picked */ |
|
|
|
@ -1230,9 +1171,7 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
delta_time = earliest_time - timenow; |
|
|
|
|
if (delta_time) { |
|
|
|
|
/* Convert ticks to milliseconds based on the current tempo */ |
|
|
|
|
unsigned long long temp; |
|
|
|
|
temp = ((unsigned long long) delta_time * tempo) / ticks_per_beat; |
|
|
|
|
delta_msec = temp / 1000; // get around LCC compiler bug
|
|
|
|
|
delta_msec = ((unsigned long long) delta_time * tempo) / ticks_per_beat / 1000; |
|
|
|
|
if (delta_msec) { // if time delay didn't round down to zero msec
|
|
|
|
|
gen_stopnotes(); /* first check if any tone generators have "stop note" commands pending */ |
|
|
|
|
if (loggen) |
|
|
|
@ -1243,13 +1182,10 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
if (binaryoutput) { |
|
|
|
|
putc ((unsigned char) (delta_msec >> 8), outfile); |
|
|
|
|
putc ((unsigned char) (delta_msec & 0xff), outfile); |
|
|
|
|
outfile_bytecount += 2; |
|
|
|
|
} else { |
|
|
|
|
outfile_bytecount += 2; } |
|
|
|
|
else { |
|
|
|
|
fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); |
|
|
|
|
outfile_items (2); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
outfile_items (2); } } } |
|
|
|
|
timenow = earliest_time; |
|
|
|
|
|
|
|
|
|
/* If this track event is "set tempo", just change the global tempo.
|
|
|
|
@ -1259,8 +1195,7 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
tempo = trk->tempo; |
|
|
|
|
if (loggen) |
|
|
|
|
fprintf (logfile, "Tempo changed to %ld usec/qnote\n", tempo); |
|
|
|
|
find_note (tracknum); |
|
|
|
|
} |
|
|
|
|
find_note (tracknum); } |
|
|
|
|
|
|
|
|
|
/* If this track event is "stop note", process it and all subsequent "stop notes" for this track
|
|
|
|
|
that are happening at the same time. Doing so frees up as many tone generators as possible. */ |
|
|
|
@ -1278,9 +1213,7 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
tg->note, tgnum, tracknum); |
|
|
|
|
tg->stopnote_pending = true; /* must stop the current note if another doesn't start first */ |
|
|
|
|
tg->playing = false; |
|
|
|
|
trk->tonegens[tgnum] = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
trk->tonegens[tgnum] = false; } } |
|
|
|
|
find_note (tracknum); // use up the note
|
|
|
|
|
} |
|
|
|
|
while (trk->cmd == CMD_STOPNOTE && trk->time == timenow); |
|
|
|
@ -1296,27 +1229,21 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
tg = &tonegen[trk->preferred_tonegen]; |
|
|
|
|
if (!tg->playing) { |
|
|
|
|
tgnum = trk->preferred_tonegen; |
|
|
|
|
foundgen = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
foundgen = true; } } |
|
|
|
|
/* if not, then try for a free tone generator that had been playing the same instrument we need */ |
|
|
|
|
if (!foundgen) |
|
|
|
|
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { |
|
|
|
|
tg = &tonegen[tgnum]; |
|
|
|
|
if (!tg->playing && tg->instrument == midi_chan_instrument[trk->chan]) { |
|
|
|
|
foundgen = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
break; } } |
|
|
|
|
/* if not, then try for any free tone generator */ |
|
|
|
|
if (!foundgen) |
|
|
|
|
for (tgnum = 0; tgnum < num_tonegens; ++tgnum) { |
|
|
|
|
tg = &tonegen[tgnum]; |
|
|
|
|
if (!tg->playing) { |
|
|
|
|
foundgen = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
break; } } |
|
|
|
|
if (foundgen) { |
|
|
|
|
int shifted_note; |
|
|
|
|
if (tgnum + 1 > num_tonegens_used) |
|
|
|
@ -1337,52 +1264,44 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
if (instrumentoutput) { /* output a "change instrument" command */ |
|
|
|
|
if (binaryoutput) { |
|
|
|
|
putc (CMD_INSTRUMENT | tgnum, outfile); |
|
|
|
|
putc (tg->instrument, outfile); |
|
|
|
|
} else { |
|
|
|
|
putc (tg->instrument, outfile); } |
|
|
|
|
else { |
|
|
|
|
fprintf (outfile, "0x%02X,%d, ", CMD_INSTRUMENT | tgnum, tg->instrument); |
|
|
|
|
outfile_items (2); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
outfile_items (2); } } } |
|
|
|
|
if (loggen) |
|
|
|
|
fprintf (logfile, |
|
|
|
|
"->Start note %d, generator %d, instrument %d, track %d\n", |
|
|
|
|
trk->note, tgnum, tg->instrument, tracknum); |
|
|
|
|
if (percussion_translate && trk->chan == PERCUSSION_TRACK) { /* if requested, */ |
|
|
|
|
shifted_note = trk->note + 128; // shift percussion notes up to 128..255
|
|
|
|
|
} else { /* shift notes as requested */ |
|
|
|
|
} |
|
|
|
|
else { /* shift notes as requested */ |
|
|
|
|
shifted_note = trk->note + keyshift; |
|
|
|
|
if (shifted_note < 0) |
|
|
|
|
shifted_note = 0; |
|
|
|
|
if (shifted_note > 127) |
|
|
|
|
shifted_note = 127; |
|
|
|
|
} |
|
|
|
|
shifted_note = 127; } |
|
|
|
|
if (binaryoutput) { |
|
|
|
|
putc (CMD_PLAYNOTE | tgnum, outfile); |
|
|
|
|
putc (shifted_note, outfile); |
|
|
|
|
outfile_bytecount += 2; |
|
|
|
|
if (velocityoutput) { |
|
|
|
|
putc (trk->velocity, outfile); |
|
|
|
|
outfile_bytecount++; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
outfile_bytecount++; } } |
|
|
|
|
else { |
|
|
|
|
if (velocityoutput == 0) { |
|
|
|
|
fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); |
|
|
|
|
outfile_items (2); |
|
|
|
|
} else { |
|
|
|
|
outfile_items (2); } |
|
|
|
|
else { |
|
|
|
|
fprintf (outfile, "0x%02X,%d,%d, ", |
|
|
|
|
CMD_PLAYNOTE | tgnum, shifted_note, trk->velocity); |
|
|
|
|
outfile_items (3); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
outfile_items (3); } } } |
|
|
|
|
else { |
|
|
|
|
if (loggen) |
|
|
|
|
fprintf (logfile, |
|
|
|
|
"----> No free generator, skipping note %d, track %d\n", |
|
|
|
|
trk->note, tracknum); |
|
|
|
|
++notes_skipped; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
++notes_skipped; } } |
|
|
|
|
find_note (tracknum); // use up the note
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1400,8 +1319,7 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
gen_restart ? CMD_RESTART : CMD_STOP, outfile_bytecount, num_tonegens_used, |
|
|
|
|
num_tonegens_used == 1 ? " is" : "s are"); |
|
|
|
|
if (notes_skipped) |
|
|
|
|
fprintf (outfile, "// %d notes had to be skipped.\n", notes_skipped); |
|
|
|
|
} |
|
|
|
|
fprintf (outfile, "// %d notes had to be skipped.\n", notes_skipped); } |
|
|
|
|
printf (" %s %d tone generators were used.\n", |
|
|
|
|
num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used); |
|
|
|
|
if (notes_skipped) |
|
|
|
@ -1419,14 +1337,10 @@ This is not unlike multiway merging used for tape sorting algoritms in the 50's! |
|
|
|
|
if (binaryoutput) |
|
|
|
|
putc (num_tonegens_used, outfile); |
|
|
|
|
else |
|
|
|
|
fprintf (outfile, "%2d", num_tonegens_used); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
fclose (outfile); |
|
|
|
|
} /* if (!parseonly) */ |
|
|
|
|
fprintf (outfile, "%2d", num_tonegens_used); } } |
|
|
|
|
fclose (outfile); } /* if (!parseonly) */ |
|
|
|
|
|
|
|
|
|
if (loggen || logparse) |
|
|
|
|
fclose (logfile); |
|
|
|
|
printf (" Done.\n"); |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
return 0; } |
|
|
|
|