|
|
@ -111,6 +111,10 @@ |
|
|
|
* |
|
|
|
* |
|
|
|
* -pi Ignore notes in the MIDI percussion track 9 (also called 10 by some) |
|
|
|
* -pi Ignore notes in the MIDI percussion track 9 (also called 10 by some) |
|
|
|
* |
|
|
|
* |
|
|
|
|
|
|
|
* -mx Merge events that are within x milliseconds, to reduce the number of "delay" |
|
|
|
|
|
|
|
* commands and thus make the bytestream smaller. The deficits are accumulated |
|
|
|
|
|
|
|
* so that there is no loss of synchronization in the long term. |
|
|
|
|
|
|
|
* |
|
|
|
* -dp Generate IDE-dependent C code to define PROGMEM |
|
|
|
* -dp Generate IDE-dependent C code to define PROGMEM |
|
|
|
* |
|
|
|
* |
|
|
|
* -r Terminate the output file with a "restart" command instead of a "stop" command. |
|
|
|
* -r Terminate the output file with a "restart" command instead of a "stop" command. |
|
|
@ -260,7 +264,7 @@ |
|
|
|
* seen, this makes the bytestream 21% smaller! |
|
|
|
* seen, this makes the bytestream 21% smaller! |
|
|
|
* 13 November 2017, Earle Philhower, V1.15 |
|
|
|
* 13 November 2017, Earle Philhower, V1.15 |
|
|
|
* - Allow META fields to be larger than 127 bytes. |
|
|
|
* - Allow META fields to be larger than 127 bytes. |
|
|
|
* 2 January 2018, Kodest, V1.16 |
|
|
|
* 2 January 2018, Kodest, V1 |
|
|
|
* - Don't generate zero-length delays |
|
|
|
* - Don't generate zero-length delays |
|
|
|
* 13 September 2018, Paul Stoffregen, V1.17 |
|
|
|
* 13 September 2018, Paul Stoffregen, V1.17 |
|
|
|
- Fix compile errors on Linux with gcc run in default mode |
|
|
|
- Fix compile errors on Linux with gcc run in default mode |
|
|
@ -270,8 +274,13 @@ |
|
|
|
- Abandon LCC and compile under Microsoft Visual Studio 2017. |
|
|
|
- Abandon LCC and compile under Microsoft Visual Studio 2017. |
|
|
|
- Reformat to condense the source code, so you see more protein and less |
|
|
|
- Reformat to condense the source code, so you see more protein and less |
|
|
|
syntactic sugar on each screen. |
|
|
|
syntactic sugar on each screen. |
|
|
|
|
|
|
|
* 4 January 2019, Len Shustek, V1.19 |
|
|
|
|
|
|
|
- As suggested by Chris van Marle, add the "-mx" parameter to allow timing to be |
|
|
|
|
|
|
|
flexible in order to avoid small delays and thus save space in the bytestream. |
|
|
|
|
|
|
|
- Don't discard fractions of a millisecond in the delay timing, to avoid gradual |
|
|
|
|
|
|
|
drift of the music. This has been a minor problem since V1.0 in 2011. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
#define VERSION "1.18" |
|
|
|
#define VERSION "1.19" |
|
|
|
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------------------------------------
|
|
|
|
/*--------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
@ -291,9 +300,13 @@ a MIDI file is: |
|
|
|
|
|
|
|
|
|
|
|
a header_chunk is: |
|
|
|
a header_chunk is: |
|
|
|
"MThd" 00000006 ffff nnnn dddd |
|
|
|
"MThd" 00000006 ffff nnnn dddd |
|
|
|
|
|
|
|
ffff is the format type (we have only seen 1) |
|
|
|
|
|
|
|
nnnn is the number of tracks |
|
|
|
|
|
|
|
dddd is the number of ticks per beat (ie, quarter note) |
|
|
|
|
|
|
|
|
|
|
|
a track_chunk is: |
|
|
|
a track_chunk is: |
|
|
|
"MTrk" llllllll {<deltatime> track_event}... |
|
|
|
"MTrk" llllllll {<deltatime> track_event}... |
|
|
|
|
|
|
|
<deltatime> is the number of ticks to delay before this event |
|
|
|
|
|
|
|
|
|
|
|
a running status track_event is: |
|
|
|
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 |
|
|
|
0x to 7x: assume a missing 8n to En event code which is the same as the last MIDI-event track_event |
|
|
@ -337,8 +350,43 @@ a meta event track_event is: |
|
|
|
FF 58 04 nnddccbb set time signature |
|
|
|
FF 58 04 nnddccbb set time signature |
|
|
|
FF 59 02 sfmi set key signature |
|
|
|
FF 59 02 sfmi set key signature |
|
|
|
FF 7F <len> data sequencer-specific data |
|
|
|
FF 7F <len> data sequencer-specific data |
|
|
|
|
|
|
|
--------------------------------------------------------------------------------------------*/ |
|
|
|
--------------------------------------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
/*--------------- processing outline -------------------------------------------------------
|
|
|
|
|
|
|
|
main |
|
|
|
|
|
|
|
forall trks, find_note |
|
|
|
|
|
|
|
do |
|
|
|
|
|
|
|
find trk with earliest_time = min(trk->time) |
|
|
|
|
|
|
|
if timenow-earliest_time > minimum delay |
|
|
|
|
|
|
|
gen_stopnotes |
|
|
|
|
|
|
|
output: delay timenow-earliest_time |
|
|
|
|
|
|
|
if CMD_TEMPO, |
|
|
|
|
|
|
|
set global tempo |
|
|
|
|
|
|
|
find_note |
|
|
|
|
|
|
|
else if CMD_STOPNOTE
|
|
|
|
|
|
|
|
do |
|
|
|
|
|
|
|
tgen->stopnote_pending = true |
|
|
|
|
|
|
|
find_note |
|
|
|
|
|
|
|
while CMD_STOPNOTE |
|
|
|
|
|
|
|
else if CMD_PLAYNOTE |
|
|
|
|
|
|
|
find tgen |
|
|
|
|
|
|
|
stopnote_pending = false |
|
|
|
|
|
|
|
output: CMD_PLAYNOTE |
|
|
|
|
|
|
|
find_note |
|
|
|
|
|
|
|
while not all CMD_TRACKDONE |
|
|
|
|
|
|
|
exit |
|
|
|
|
|
|
|
find_note |
|
|
|
|
|
|
|
do forever |
|
|
|
|
|
|
|
t->time = varlen |
|
|
|
|
|
|
|
if "note off", CMD_STOPNOTE, return |
|
|
|
|
|
|
|
if "note on", CMD_PLAYNOTE, return |
|
|
|
|
|
|
|
if "tempo", CMD_TEMPO, return |
|
|
|
|
|
|
|
if end of track, CMD_TRACKDONE, return |
|
|
|
|
|
|
|
gen_stopnotes |
|
|
|
|
|
|
|
forall tgen |
|
|
|
|
|
|
|
if stopnote_pending,
|
|
|
|
|
|
|
|
output: CMD_STOPNOTE, stopnote_pending = false |
|
|
|
|
|
|
|
--------------------------------------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h> |
|
|
|
#include <stdio.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#include <stdlib.h> |
|
|
@ -388,8 +436,12 @@ unsigned channel_mask = 0xffff; // bit mask of channels to process |
|
|
|
int keyshift = 0; // optional chromatic note shift for output file
|
|
|
|
int keyshift = 0; // optional chromatic note shift for output file
|
|
|
|
long int outfile_bytecount = 0; |
|
|
|
long int outfile_bytecount = 0; |
|
|
|
unsigned int ticks_per_beat = 240; |
|
|
|
unsigned int ticks_per_beat = 240; |
|
|
|
unsigned long timenow = 0; |
|
|
|
unsigned long timenow = 0; // time now, in ticks
|
|
|
|
unsigned long tempo; /* current tempo in usec/qnote */ |
|
|
|
unsigned long tempo; // current global tempo in usec/beat
|
|
|
|
|
|
|
|
unsigned long long songtime_usec = 0; |
|
|
|
|
|
|
|
int tempo_changes = 0; // how many times we changed the global tempo
|
|
|
|
|
|
|
|
unsigned closetime_msec = 0; // how close, in msec, events should be before they are merged
|
|
|
|
|
|
|
|
long int delays_saved = 0; // how many delays were saved because of non-zero merge time
|
|
|
|
|
|
|
|
|
|
|
|
struct tonegen_status { /* current status of a tone generator */ |
|
|
|
struct tonegen_status { /* current status of a tone generator */ |
|
|
|
bool playing; /* is it playing? */ |
|
|
|
bool playing; /* is it playing? */ |
|
|
@ -404,8 +456,9 @@ struct tonegen_status { /* current status of a tone generator */ |
|
|
|
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 */ |
|
|
|
uint8_t *trkend; /* ptr past the end of the track */ |
|
|
|
uint8_t *trkend; /* ptr past the end of the track */ |
|
|
|
unsigned long time; /* what time we're at in the score */ |
|
|
|
unsigned long time; /* what time we're at in the score, in ticks */ |
|
|
|
unsigned long tempo; /* the tempo last set, in usec per qnote */ |
|
|
|
unsigned long deficit_usec; /* how much behind we are, in usec */ |
|
|
|
|
|
|
|
unsigned long tempo; /* the tempo last set, in usec per beat */ |
|
|
|
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 */ |
|
|
@ -482,6 +535,7 @@ void SayUsage (char *programName) { |
|
|
|
" -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", |
|
|
|
" -pi ignore notes in the percussion track (9)", |
|
|
|
" -pi ignore notes in the percussion track (9)", |
|
|
|
|
|
|
|
" -mx merge events that are within x msec, to save bytestream space", |
|
|
|
" -dp define PROGMEM in output C code", |
|
|
|
" -dp define PROGMEM in output C code", |
|
|
|
" -r terminate output file with \"restart\" instead of \"stop\" command", |
|
|
|
" -r terminate output file with \"restart\" instead of \"stop\" command", |
|
|
|
NULL }; |
|
|
|
NULL }; |
|
|
@ -505,97 +559,78 @@ int HandleOptions (int argc, char *argv[]) { |
|
|
|
SayUsage (argv[0]); |
|
|
|
SayUsage (argv[0]); |
|
|
|
exit (1); |
|
|
|
exit (1); |
|
|
|
case 'L': |
|
|
|
case 'L': |
|
|
|
if (toupper (argv[i][2]) == 'G') |
|
|
|
if (toupper (argv[i][2]) == 'G') loggen = true; |
|
|
|
loggen = true; |
|
|
|
else if (toupper (argv[i][2]) == 'P') logparse = true; |
|
|
|
else if (toupper (argv[i][2]) == 'P') |
|
|
|
else goto opterror; |
|
|
|
logparse = true; |
|
|
|
if (argv[i][3] != '\0') goto opterror; |
|
|
|
else |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
if (argv[i][3] != '\0') |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'P': |
|
|
|
case 'P': |
|
|
|
if (argv[i][2] == '\0') { |
|
|
|
if (argv[i][2] == '\0') { |
|
|
|
parseonly = true; |
|
|
|
parseonly = true; |
|
|
|
break; } |
|
|
|
break; } |
|
|
|
else if (toupper (argv[i][2]) == 'I') |
|
|
|
else if (toupper (argv[i][2]) == 'I') percussion_ignore = true; |
|
|
|
percussion_ignore = true; |
|
|
|
else if (toupper (argv[i][2]) == 'T') percussion_translate = true; |
|
|
|
else if (toupper (argv[i][2]) == 'T') |
|
|
|
else goto opterror; |
|
|
|
percussion_translate = true; |
|
|
|
if (argv[i][3] != '\0') goto opterror; |
|
|
|
else |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
if (argv[i][3] != '\0') |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'B': |
|
|
|
case 'B': |
|
|
|
binaryoutput = true; |
|
|
|
binaryoutput = true; |
|
|
|
if (argv[i][2] != '\0') |
|
|
|
if (argv[i][2] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'V': |
|
|
|
case 'V': |
|
|
|
velocityoutput = true; |
|
|
|
velocityoutput = true; |
|
|
|
if (argv[i][2] != '\0') |
|
|
|
if (argv[i][2] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'I': |
|
|
|
case 'I': |
|
|
|
instrumentoutput = true; |
|
|
|
instrumentoutput = true; |
|
|
|
if (argv[i][2] != '\0') |
|
|
|
if (argv[i][2] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'S': |
|
|
|
case 'S': |
|
|
|
if (argv[i][2] == '1') |
|
|
|
if (argv[i][2] == '1') strategy1 = true; |
|
|
|
strategy1 = true; |
|
|
|
else if (argv[i][2] == '2') strategy2 = true; |
|
|
|
else if (argv[i][2] == '2') |
|
|
|
else goto opterror; |
|
|
|
strategy2 = true; |
|
|
|
if (argv[i][3] != '\0') goto opterror; |
|
|
|
else |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
if (argv[i][3] != '\0') |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'T': |
|
|
|
case 'T': |
|
|
|
if (sscanf (&argv[i][2], "%d%n", &num_tonegens, &nch) != 1 |
|
|
|
if (sscanf (&argv[i][2], "%d%n", &num_tonegens, &nch) != 1 |
|
|
|
|| num_tonegens < 1 || num_tonegens > MAX_TONEGENS) |
|
|
|
|| num_tonegens < 1 || num_tonegens > MAX_TONEGENS) |
|
|
|
goto opterror; |
|
|
|
goto opterror; |
|
|
|
printf ("Using %d tone generators.\n", num_tonegens); |
|
|
|
printf ("Using %d tone generators.\n", num_tonegens); |
|
|
|
if (argv[i][2 + nch] != '\0') |
|
|
|
if (argv[i][2 + nch] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'N': |
|
|
|
case 'N': |
|
|
|
if (sscanf (&argv[i][2], "%d%n", &outfile_maxitems, &nch) != 1 || outfile_maxitems < 1) |
|
|
|
if (sscanf (&argv[i][2], "%d%n", &outfile_maxitems, &nch) != 1 || outfile_maxitems < 1) |
|
|
|
goto opterror; |
|
|
|
goto opterror; |
|
|
|
if (argv[i][2 + nch] != '\0') |
|
|
|
if (argv[i][2 + nch] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
break; |
|
|
|
|
|
|
|
case 'M': |
|
|
|
|
|
|
|
if (sscanf(&argv[i][2], "%d%n", &closetime_msec, &nch) != 1) goto opterror; |
|
|
|
|
|
|
|
if (argv[i][2 + nch] != '\0') goto opterror; |
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'C': |
|
|
|
case 'C': |
|
|
|
if (sscanf (&argv[i][2], "%i%n", &channel_mask, &nch) != 1 || channel_mask > 0xffff) |
|
|
|
if (sscanf (&argv[i][2], "%i%n", &channel_mask, &nch) != 1 || channel_mask > 0xffff) |
|
|
|
goto opterror; |
|
|
|
goto opterror; |
|
|
|
printf ("Channel (track) mask is %04X.\n", channel_mask); |
|
|
|
printf ("Channel (track) mask is %04X.\n", channel_mask); |
|
|
|
if (argv[i][2 + nch] != '\0') |
|
|
|
if (argv[i][2 + nch] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'K': |
|
|
|
case 'K': |
|
|
|
if (sscanf (&argv[i][2], "%d%n", &keyshift, &nch) != 1 || keyshift < -100 |
|
|
|
if (sscanf (&argv[i][2], "%d%n", &keyshift, &nch) != 1 || keyshift < -100 |
|
|
|
|| keyshift > 100) |
|
|
|
|| keyshift > 100) |
|
|
|
goto opterror; |
|
|
|
goto opterror; |
|
|
|
printf ("Using keyshift %d.\n", keyshift); |
|
|
|
printf ("Using keyshift %d.\n", keyshift); |
|
|
|
if (argv[i][2 + nch] != '\0') |
|
|
|
if (argv[i][2 + nch] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'D': |
|
|
|
case 'D': |
|
|
|
if (argv[i][2] == '\0') { |
|
|
|
if (argv[i][2] == '\0') { |
|
|
|
do_header = true; |
|
|
|
do_header = true; |
|
|
|
break; } |
|
|
|
break; } |
|
|
|
if (toupper (argv[i][2]) == 'P') |
|
|
|
if (toupper (argv[i][2]) == 'P') define_progmem = true; |
|
|
|
define_progmem = true; |
|
|
|
else goto opterror; |
|
|
|
else |
|
|
|
if (argv[i][3] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
if (argv[i][3] != '\0') |
|
|
|
|
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
case 'R': |
|
|
|
case 'R': |
|
|
|
gen_restart = true; |
|
|
|
gen_restart = true; |
|
|
|
if (argv[i][2] != '\0') |
|
|
|
if (argv[i][2] != '\0') goto opterror; |
|
|
|
goto opterror; |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
/* add more option switches here */ |
|
|
|
/* add more option switches here */ |
|
|
|
opterror: |
|
|
|
opterror: |
|
|
@ -608,12 +643,12 @@ opterror: |
|
|
|
break; } } |
|
|
|
break; } } |
|
|
|
return firstnonoption; } |
|
|
|
return firstnonoption; } |
|
|
|
|
|
|
|
|
|
|
|
void print_command_line (int argc, char *argv[]) { |
|
|
|
void print_command_line (FILE *file, int argc, char *argv[]) { |
|
|
|
int i; |
|
|
|
int i; |
|
|
|
fprintf (outfile, "// command line: "); |
|
|
|
fprintf (file, "// command line: "); |
|
|
|
for (i = 0; i < argc; i++) |
|
|
|
for (i = 0; i < argc; i++) |
|
|
|
fprintf (outfile, "%s ", argv[i]); |
|
|
|
fprintf (file, "%s ", argv[i]); |
|
|
|
fprintf (outfile, "\n"); } |
|
|
|
fprintf (file, "\n"); } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**************** utility routines **********************/ |
|
|
|
/**************** utility routines **********************/ |
|
|
@ -632,30 +667,25 @@ size_t miditones_strlcpy (char *dst, const char *src, size_t siz) { |
|
|
|
/* Copy as many bytes as will fit */ |
|
|
|
/* Copy as many bytes as will fit */ |
|
|
|
if (n != 0) { |
|
|
|
if (n != 0) { |
|
|
|
while (--n != 0) { |
|
|
|
while (--n != 0) { |
|
|
|
if ((*d++ = *s++) == '\0') |
|
|
|
if ((*d++ = *s++) == '\0') break; } } |
|
|
|
break; } } |
|
|
|
|
|
|
|
/* Not enough room in dst, add NUL and traverse rest of src */ |
|
|
|
/* Not enough room in dst, add NUL and traverse rest of src */ |
|
|
|
if (n == 0) { |
|
|
|
if (n == 0) { |
|
|
|
if (siz != 0) |
|
|
|
if (siz != 0) *d = '\0'; /* NUL-terminate dst */ |
|
|
|
*d = '\0'; /* NUL-terminate dst */ |
|
|
|
while (*s++) ; } |
|
|
|
while (*s++); } |
|
|
|
|
|
|
|
return (s - src - 1); /* count does not include NUL */ |
|
|
|
return (s - src - 1); /* count does not include NUL */ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* safe string concatenation */ |
|
|
|
/* safe string concatenation */ |
|
|
|
|
|
|
|
|
|
|
|
size_t miditones_strlcat (char *dst, const char *src, size_t siz) { |
|
|
|
size_t miditones_strlcat (char *dst, const char *src, size_t siz) { |
|
|
|
char *d = dst; |
|
|
|
char *d = dst; |
|
|
|
const char *s = src; |
|
|
|
const char *s = src; |
|
|
|
size_t n = siz; |
|
|
|
size_t n = siz; |
|
|
|
size_t dlen; |
|
|
|
size_t dlen; |
|
|
|
/* Find the end of dst and adjust bytes left but don't go past end */ |
|
|
|
/* Find the end of dst and adjust bytes left but don't go past end */ |
|
|
|
while (n-- != 0 && *d != '\0') |
|
|
|
while (n-- != 0 && *d != '\0') d++; |
|
|
|
d++; |
|
|
|
|
|
|
|
dlen = d - dst; |
|
|
|
dlen = d - dst; |
|
|
|
n = siz - dlen; |
|
|
|
n = siz - dlen; |
|
|
|
if (n == 0) |
|
|
|
if (n == 0) return (dlen + strlength (s)); |
|
|
|
return (dlen + strlength (s)); |
|
|
|
|
|
|
|
while (*s != '\0') { |
|
|
|
while (*s != '\0') { |
|
|
|
if (n != 1) { |
|
|
|
if (n != 1) { |
|
|
|
*d++ = *s; |
|
|
|
*d++ = *s; |
|
|
@ -666,7 +696,6 @@ size_t miditones_strlcat (char *dst, const char *src, size_t siz) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* match a constant character sequence */ |
|
|
|
/* match a constant character sequence */ |
|
|
|
|
|
|
|
|
|
|
|
int charcmp (const char *buf, const char *match) { |
|
|
|
int charcmp (const char *buf, const char *match) { |
|
|
|
int len, i; |
|
|
|
int len, i; |
|
|
|
len = strlength (match); |
|
|
|
len = strlength (match); |
|
|
@ -1024,7 +1053,8 @@ int main (int argc, char *argv[]) { |
|
|
|
if (!logfile) { |
|
|
|
if (!logfile) { |
|
|
|
fprintf (stderr, "Unable to open log file %s\n", filename); |
|
|
|
fprintf (stderr, "Unable to open log file %s\n", filename); |
|
|
|
return 1; } |
|
|
|
return 1; } |
|
|
|
fprintf (logfile, "MIDITONES V%s log file\n", VERSION); } |
|
|
|
fprintf (logfile, "MIDITONES V%s log file\n", VERSION); |
|
|
|
|
|
|
|
print_command_line(logfile, argc, argv); } |
|
|
|
|
|
|
|
|
|
|
|
/* Open the input file */ |
|
|
|
/* Open the input file */ |
|
|
|
|
|
|
|
|
|
|
@ -1072,7 +1102,7 @@ int main (int argc, char *argv[]) { |
|
|
|
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, |
|
|
|
fprintf (outfile, "created by MIDITONES V%s on %s", VERSION, |
|
|
|
asctime (localtime (&rawtime))); |
|
|
|
asctime (localtime (&rawtime))); |
|
|
|
print_command_line (argc, argv); |
|
|
|
print_command_line (outfile, argc, argv); |
|
|
|
if (channel_mask != 0xffff) |
|
|
|
if (channel_mask != 0xffff) |
|
|
|
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) |
|
|
@ -1128,7 +1158,7 @@ int main (int argc, char *argv[]) { |
|
|
|
struct tonegen_status *tg; |
|
|
|
struct tonegen_status *tg; |
|
|
|
int tgnum; |
|
|
|
int tgnum; |
|
|
|
int count_tracks; |
|
|
|
int count_tracks; |
|
|
|
unsigned long delta_time, delta_msec; |
|
|
|
unsigned long delta_ticks, delta_msec; |
|
|
|
|
|
|
|
|
|
|
|
/* Find the track with the earliest event time,
|
|
|
|
/* Find the track with the earliest event time,
|
|
|
|
and output a delay command if time has advanced. |
|
|
|
and output a delay command if time has advanced. |
|
|
@ -1151,8 +1181,7 @@ int main (int argc, char *argv[]) { |
|
|
|
if (strategy1) |
|
|
|
if (strategy1) |
|
|
|
tracknum = num_tracks; /* beyond the end, so we start with track 0 */ |
|
|
|
tracknum = num_tracks; /* beyond the end, so we start with track 0 */ |
|
|
|
do { |
|
|
|
do { |
|
|
|
if (++tracknum >= num_tracks) |
|
|
|
if (++tracknum >= num_tracks) tracknum = 0; |
|
|
|
tracknum = 0; |
|
|
|
|
|
|
|
trk = &track[tracknum]; |
|
|
|
trk = &track[tracknum]; |
|
|
|
if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) { |
|
|
|
if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) { |
|
|
|
earliest_time = trk->time; |
|
|
|
earliest_time = trk->time; |
|
|
@ -1166,18 +1195,25 @@ int main (int argc, char *argv[]) { |
|
|
|
if (earliest_time < timenow) |
|
|
|
if (earliest_time < timenow) |
|
|
|
midi_error ("INTERNAL: time went backwards", trk->trkptr); |
|
|
|
midi_error ("INTERNAL: time went backwards", trk->trkptr); |
|
|
|
|
|
|
|
|
|
|
|
/* If time has advanced, output a "delay" command */ |
|
|
|
/* If time has advanced, maybe output a "delay" command */ |
|
|
|
|
|
|
|
|
|
|
|
delta_time = earliest_time - timenow; |
|
|
|
delta_ticks = earliest_time - timenow; |
|
|
|
if (delta_time) { |
|
|
|
unsigned long delta_usec = ((unsigned long long) delta_ticks * tempo) / ticks_per_beat; |
|
|
|
/* Convert ticks to milliseconds based on the current tempo */ |
|
|
|
songtime_usec += delta_usec; |
|
|
|
delta_msec = ((unsigned long long) delta_time * tempo) / ticks_per_beat / 1000; |
|
|
|
// We round up closetime_ticks, because otherwise the default tempo of 500,000 usec/beat with
|
|
|
|
if (delta_msec) { // if time delay didn't round down to zero msec
|
|
|
|
// the default 480 ticks/beat results in 1.04166 msec/tick, so -m1 doesn't work as expected.
|
|
|
|
gen_stopnotes(); /* first check if any tone generators have "stop note" commands pending */ |
|
|
|
unsigned long closetime_ticks = (((unsigned long)closetime_msec * 1000 + 500) * ticks_per_beat) / tempo; |
|
|
|
if (loggen) |
|
|
|
unsigned long deficit_ticks = (trk->deficit_usec * ticks_per_beat) / tempo; |
|
|
|
fprintf (logfile, "->Delay %ld msec (%ld ticks)\n", delta_msec, delta_time); |
|
|
|
// Generate a delay only if it is larger than the specified "merge if closer than x msec" time.
|
|
|
|
if (delta_msec > 0x7fff) |
|
|
|
// But we accumulate the deficit for each track so that we eventually catch up.
|
|
|
|
midi_error ("INTERNAL: time delta too big", trk->trkptr); |
|
|
|
if (delta_ticks + deficit_ticks > closetime_ticks) { // yes, we should generate a delay
|
|
|
|
|
|
|
|
gen_stopnotes(); /* first check if any tone generators have "stop note" commands pending */ |
|
|
|
|
|
|
|
delta_usec += trk->deficit_usec; // include any previously accumulated deficit
|
|
|
|
|
|
|
|
delta_msec = delta_usec / 1000; // the integral number of milliseconds
|
|
|
|
|
|
|
|
trk->deficit_usec = delta_usec % 1000; // save the fraction of a milliscond as the new deficit
|
|
|
|
|
|
|
|
if (delta_msec) { // really do it if the delay didn't round down to zero msec
|
|
|
|
|
|
|
|
if (loggen) fprintf (logfile, "->Delay %ld msec (%ld ticks)\n", delta_msec, delta_ticks); |
|
|
|
|
|
|
|
if (delta_msec > 0x7fff) midi_error ("INTERNAL: time delta too big", trk->trkptr); |
|
|
|
/* output a 15-bit delay in big-endian format */ |
|
|
|
/* output a 15-bit delay in big-endian format */ |
|
|
|
if (binaryoutput) { |
|
|
|
if (binaryoutput) { |
|
|
|
putc ((unsigned char) (delta_msec >> 8), outfile); |
|
|
|
putc ((unsigned char) (delta_msec >> 8), outfile); |
|
|
@ -1186,15 +1222,19 @@ int main (int argc, char *argv[]) { |
|
|
|
else { |
|
|
|
else { |
|
|
|
fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); |
|
|
|
fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); |
|
|
|
outfile_items (2); } } } |
|
|
|
outfile_items (2); } } } |
|
|
|
|
|
|
|
else if (delta_usec) { |
|
|
|
|
|
|
|
trk->deficit_usec += delta_usec; // accumulate time for delays we skip
|
|
|
|
|
|
|
|
++delays_saved; } |
|
|
|
timenow = earliest_time; |
|
|
|
timenow = earliest_time; |
|
|
|
|
|
|
|
|
|
|
|
/* If this track event is "set tempo", just change the global tempo.
|
|
|
|
/* If this track event is "set tempo", just change the global tempo.
|
|
|
|
That affects how we generate "delay" commands. */ |
|
|
|
That affects how we generate "delay" commands. */ |
|
|
|
|
|
|
|
|
|
|
|
if (trk->cmd == CMD_TEMPO) { |
|
|
|
if (trk->cmd == CMD_TEMPO) { |
|
|
|
|
|
|
|
if (tempo != trk->tempo) ++tempo_changes; |
|
|
|
tempo = trk->tempo; |
|
|
|
tempo = trk->tempo; |
|
|
|
if (loggen) |
|
|
|
if (loggen) |
|
|
|
fprintf (logfile, "Tempo changed to %ld usec/qnote\n", tempo); |
|
|
|
fprintf (logfile, "Tempo set 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
|
|
|
|
/* If this track event is "stop note", process it and all subsequent "stop notes" for this track
|
|
|
@ -1277,10 +1317,8 @@ int main (int argc, char *argv[]) { |
|
|
|
} |
|
|
|
} |
|
|
|
else { /* shift notes as requested */ |
|
|
|
else { /* shift notes as requested */ |
|
|
|
shifted_note = trk->note + keyshift; |
|
|
|
shifted_note = trk->note + keyshift; |
|
|
|
if (shifted_note < 0) |
|
|
|
if (shifted_note < 0) shifted_note = 0; |
|
|
|
shifted_note = 0; |
|
|
|
if (shifted_note > 127) shifted_note = 127; } |
|
|
|
if (shifted_note > 127) |
|
|
|
|
|
|
|
shifted_note = 127; } |
|
|
|
|
|
|
|
if (binaryoutput) { |
|
|
|
if (binaryoutput) { |
|
|
|
putc (CMD_PLAYNOTE | tgnum, outfile); |
|
|
|
putc (CMD_PLAYNOTE | tgnum, outfile); |
|
|
|
putc (shifted_note, outfile); |
|
|
|
putc (shifted_note, outfile); |
|
|
@ -1293,13 +1331,11 @@ int main (int argc, char *argv[]) { |
|
|
|
fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); |
|
|
|
fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); |
|
|
|
outfile_items (2); } |
|
|
|
outfile_items (2); } |
|
|
|
else { |
|
|
|
else { |
|
|
|
fprintf (outfile, "0x%02X,%d,%d, ", |
|
|
|
fprintf (outfile, "0x%02X,%d,%d, ", CMD_PLAYNOTE | tgnum, shifted_note, trk->velocity); |
|
|
|
CMD_PLAYNOTE | tgnum, shifted_note, trk->velocity); |
|
|
|
|
|
|
|
outfile_items (3); } } } |
|
|
|
outfile_items (3); } } } |
|
|
|
else { |
|
|
|
else { |
|
|
|
if (loggen) |
|
|
|
if (loggen) |
|
|
|
fprintf (logfile, |
|
|
|
fprintf (logfile, "----> No free generator, skipping note %d, track %d\n", |
|
|
|
"----> No free generator, skipping note %d, track %d\n", |
|
|
|
|
|
|
|
trk->note, tracknum); |
|
|
|
trk->note, tracknum); |
|
|
|
++notes_skipped; } } |
|
|
|
++notes_skipped; } } |
|
|
|
find_note (tracknum); // use up the note
|
|
|
|
find_note (tracknum); // use up the note
|
|
|
@ -1323,10 +1359,14 @@ int main (int argc, char *argv[]) { |
|
|
|
printf (" %s %d tone generators were used.\n", |
|
|
|
printf (" %s %d tone generators were used.\n", |
|
|
|
num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used); |
|
|
|
num_tonegens_used < num_tonegens ? "Only" : "All", num_tonegens_used); |
|
|
|
if (notes_skipped) |
|
|
|
if (notes_skipped) |
|
|
|
printf |
|
|
|
printf (" %d notes were skipped because there weren't enough tone generators.\n", |
|
|
|
(" %d notes were skipped because there weren't enough tone generators.\n", |
|
|
|
notes_skipped); |
|
|
|
notes_skipped); |
|
|
|
printf (" %ld bytes of score data were generated, ", outfile_bytecount); |
|
|
|
printf (" %ld bytes of score data were generated.\n", outfile_bytecount); |
|
|
|
printf("representing %u.%03u seconds of music with %d tempo changes\n", |
|
|
|
|
|
|
|
(unsigned)(songtime_usec / 1000000), (unsigned)(songtime_usec/1000 % 1000), tempo_changes); |
|
|
|
|
|
|
|
if (closetime_msec) |
|
|
|
|
|
|
|
printf (" %ld delays were removed because events within %u msec were merged\n", |
|
|
|
|
|
|
|
delays_saved, closetime_msec); |
|
|
|
if (loggen) |
|
|
|
if (loggen) |
|
|
|
fprintf (logfile, "%d note-on commands, %d instrument changes.\n", |
|
|
|
fprintf (logfile, "%d note-on commands, %d instrument changes.\n", |
|
|
|
note_on_commands, instrument_changes); |
|
|
|
note_on_commands, instrument_changes); |
|
|
|