|
-
-
-
- #define VERSION "1.9"
-
-
-
-
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include <stdbool.h>
- #include <time.h>
- #include <inttypes.h>
-
-
-
-
- struct midi_header {
- int8_t MThd[4];
- uint32_t header_size;
- uint16_t format_type;
- uint16_t number_of_tracks;
- uint16_t time_division;
- };
-
- struct track_header {
- int8_t MTrk[4];
- uint32_t track_size;
- };
-
-
-
-
- #define MAX_TONEGENS 16
- #define DEFAULT_TONEGENS 6
- #define MAX_TRACKS 24
-
- bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput, velocityoutput;
- FILE *infile, *outfile, *logfile;
- uint8_t *buffer, *hdrptr;
- unsigned long buflen;
- int num_tracks;
- int tracks_done = 0;
- int outfile_itemcount = 0;
- int num_tonegens = DEFAULT_TONEGENS;
- int num_tonegens_used = 0;
- unsigned channel_mask = 0xffff;
- int keyshift = 0;
- long int outfile_bytecount = 0;
- unsigned int ticks_per_beat = 240;
- unsigned long timenow = 0;
- unsigned long tempo;
-
- struct tonegen_status {
- bool playing;
- int track;
- int note;
- }
- tonegen [MAX_TONEGENS] = {
- {0}};
-
- struct track_status {
- uint8_t *trkptr;
- uint8_t *trkend;
- unsigned long time;
- unsigned long tempo;
- unsigned int preferred_tonegen;
- unsigned char cmd;
- unsigned char note;
- unsigned char velocity;
- unsigned char last_event;
- bool tonegens[MAX_TONEGENS];
- }
- track[MAX_TRACKS] = {
- {0}};
-
-
-
-
- #define CMD_PLAYNOTE 0x90
- #define CMD_STOPNOTE 0x80
- #define CMD_RESTART 0xe0
- #define CMD_STOP 0xf0
-
-
-
- #define CMD_TEMPO 0xFE
- #define CMD_TRACKDONE 0xFF
-
-
-
-
-
- void SayUsage(char *programName){
- static char *usage[] = {
- "Convert MIDI files to an Arduino PLAYTUNE bytestream",
- "miditones [-p] [-lg] [-lp] [-s1] [-tn] [-v] <basefilename>",
- " -p parse only, don't generate bytestream",
- " -lp log input parsing",
- " -lg log output generation",
- " -s1 strategy 1: favor track 1",
- " -s2 strategy 2: try to assign tracks to specific tone generators",
- " -tn use at most n tone generators (default is 6, max is 16)",
- " -b binary file output instead of C source text",
- " -cn mask for which tracks to process, e.g. -c3 for only 0 and 1",
- " -kn key shift in chromatic notes, positive or negative",
- " -v include velocity data in play note commands",
- "input file: <basefilename>.mid",
- "output file: <basefilename>.bin or .c",
- "log file: <basefilename>.log",
- "" };
- int i=0;
- while (usage[i][0] != '\0') fprintf(stderr, "%s\n", usage[i++]);
- }
-
- int HandleOptions(int argc,char *argv[]) {
-
-
-
- int i,firstnonoption=0;
-
-
- for (i=1; i< argc;i++) {
- if (argv[i][0] == '/' || argv[i][0] == '-') {
- switch (toupper(argv[i][1])) {
- case 'H':
- case '?':
- SayUsage(argv[0]);
- exit(1);
- case 'L':
- if (toupper(argv[i][2]) == 'G') loggen = true;
- else if (toupper(argv[i][2]) == 'P') logparse = true;
- else goto opterror;
- break;
- case 'P':
- parseonly = true;
- break;
- case 'B':
- binaryoutput = true;
- break;
- case 'V':
- velocityoutput = true;
- break;
- case 'S':
- if (argv[i][2] == '1') strategy1 = true;
- else if (argv[i][2] == '2') strategy2 = true;
- else goto opterror;
- break;
- case 'T':
- if (sscanf(&argv[i][2],"%d",&num_tonegens) != 1 || num_tonegens <1 || num_tonegens > MAX_TONEGENS) goto opterror;
- printf("Using %d tone generators.\n", num_tonegens);
- break;
- case 'C':
- if (sscanf(&argv[i][2],"%d",&channel_mask) != 1 || channel_mask > 0xffff) goto opterror;
- printf("Channel (track) mask is %04X.\n", channel_mask);
- break;
- case 'K':
- if (sscanf(&argv[i][2],"%d",&keyshift) != 1 || keyshift < -100 || keyshift > 100) goto opterror;
- printf("Using keyshift %d.\n", keyshift);
- break;
-
-
- opterror:
- default:
- fprintf(stderr,"unknown option: %s\n",argv[i]);
- SayUsage(argv[0]);
- exit(4);
- }
- }
- else {
- firstnonoption = i;
- 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");
- }
-
-
-
-
-
-
- size_t miditones_strlcpy(char *dst, const char *src, size_t siz) {
- char *d = dst;
- const char *s = src;
- size_t n = siz;
-
- if (n != 0)
- {
- while (--n != 0)
- {
- if ((*d++ = *s++) == '\0')
- break;
- }
- }
-
- if (n == 0)
- {
- if (siz != 0)
- *d = '\0';
- while (*s++)
- ;
- }
- return (s - src - 1);
- }
-
-
-
- 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;
-
- 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));
- }
-
-
-
- int charcmp (const char *buf, const char *match) {
- int len, i;
- len = strlen (match);
- for (i=0; i<len; ++i)
- if (buf[i] != match[i]) return 0;
- return 1;
- }
-
-
-
- void midi_error (char *msg, unsigned char *bufptr) {
- unsigned char *ptr;
- fprintf(stderr, "---> MIDI file error at position %04X (%d): %s\n", (uint16_t)(bufptr-buffer), (uint16_t)(bufptr-buffer), msg);
-
- ptr = bufptr - 16;
- if (ptr < buffer) ptr = buffer;
- for (; ptr <= bufptr+16 && ptr < buffer+buflen; ++ptr) fprintf (stderr, ptr==bufptr ? " [%02X] ":"%02X ", *ptr);
- fprintf(stderr, "\n");
- exit(8);
- }
-
-
-
- void chk_bufdata(unsigned char *ptr, unsigned long int len) {
- if ((unsigned)(ptr + len - buffer) > buflen) midi_error("data missing", ptr);
- }
-
-
-
-
- uint16_t rev_short (uint16_t val) {
- 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));
- }
-
-
-
- void outfile_items (int n) {
- outfile_bytecount += n;
- outfile_itemcount += n;
- if (!binaryoutput && outfile_itemcount > 20) {
- fprintf (outfile, "\n");
- outfile_itemcount = 0;
- }
- }
-
-
-
- void process_header (void) {
- struct midi_header *hdr;
- unsigned int time_division;
-
- chk_bufdata(hdrptr, sizeof(struct midi_header));
- hdr = (struct midi_header *) hdrptr;
- if (!charcmp((char*)hdr->MThd,"MThd")) midi_error("Missing 'MThd'", hdrptr);
-
- num_tracks = rev_short(hdr->number_of_tracks);
-
- time_division = rev_short(hdr->time_division);
- if (time_division < 0x8000) ticks_per_beat = time_division;
- else ticks_per_beat = ((time_division >> 8) & 0x7f) * (time_division & 0xff);
-
- if (logparse) {
- fprintf (logfile, "Header size %" PRId32 "\n", rev_long(hdr->header_size));
- 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);
-
- }
- hdrptr += rev_long(hdr->header_size) + 8;
- return;
- }
-
-
-
-
- void start_track (int tracknum) {
- struct track_header *hdr;
- unsigned long tracklen;
-
- chk_bufdata(hdrptr, sizeof(struct track_header));
- hdr = (struct track_header *) hdrptr;
- if (!charcmp((char *)(hdr->MTrk),"MTrk")) midi_error("Missing 'MTrk'", hdrptr);
-
- tracklen = rev_long(hdr->track_size);
- if (logparse) fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen);
- hdrptr += sizeof (struct track_header);
- chk_bufdata(hdrptr, tracklen);
- track[tracknum].trkptr = hdrptr;
- hdrptr += tracklen;
- track[tracknum].trkend = hdrptr;
- }
-
-
-
-
- unsigned long get_varlen (uint8_t **ptr) {
-
-
-
- unsigned long val;
- int i, byte;
-
- val = 0;
- for (i=0; i<4; ++i){
- byte = *(*ptr)++;
- val = (val<<7) | (byte&0x7f);
- if (!(byte&0x80)) return val;
- }
- return val;
- }
-
-
-
-
-
-
- void find_note (int tracknum) {
- unsigned long int delta_time;
- int event, chan;
- int i;
- int note, velocity;
- int meta_cmd, meta_length;
- unsigned long int sysex_length;
- struct track_status *t;
-
-
-
- t = &track[tracknum];
- while (t->trkptr < t->trkend) {
-
- delta_time = get_varlen(&t->trkptr);
- if (logparse) {
- fprintf (logfile, "trk %d ", tracknum);
- if (delta_time) {
- fprintf (logfile, "delta time %4ld, ", delta_time);
- } else {
- fprintf (logfile, " ");
- }
- }
- t->time += delta_time;
-
- if (*t->trkptr < 0x80) event = t->last_event;
- else {
- event = *t->trkptr++;
- t->last_event = event;
- }
-
- if (event == 0xff) {
- meta_cmd = *t->trkptr++;
- meta_length = *t->trkptr++;
- switch (meta_cmd) {
- case 0x2f:
- if (logparse) fprintf(logfile, "end of track\n");
- break;
- case 0x00:
- if (logparse) fprintf(logfile, "sequence number %d\n", rev_short(*(unsigned short *)t->trkptr));
- break;
- case 0x20:
- if (logparse) fprintf(logfile, "channel prefix %d\n", *t->trkptr);
- break;
- case 0x51:
- t->cmd = CMD_TEMPO;
- t->tempo = rev_long(*(unsigned long *)(t->trkptr-1)) & 0xffffffL;
- if (logparse) fprintf(logfile, "set tempo %ld usec/qnote\n", t->tempo);
- t->trkptr += meta_length;
- return;
- case 0x54:
- if (logparse) fprintf(logfile, "SMPTE offset %08" PRIx32 "\n", rev_long(*(unsigned long *)t->trkptr));
- break;
- case 0x58:
- if (logparse) fprintf(logfile, "time signature %08" PRIx32 "\n", rev_long(*(unsigned long *)t->trkptr));
- break;
- case 0x59:
- if (logparse) fprintf(logfile, "key signature %04X\n", rev_short(*(unsigned short *)t->trkptr));
- break;
- default:
- if (logparse) {
- fprintf(logfile, "meta cmd %02X, length %d, \"", meta_cmd, meta_length);
- for (i=0; i<meta_length; ++i) {
- int ch = t->trkptr[i];
- fprintf(logfile, "%c", isprint(ch) ? ch : '?');
- }
- fprintf(logfile, "\"\n");
- }
- if (tracknum==0 && meta_cmd==0x03 && !parseonly && !binaryoutput) {
-
-
- fprintf(outfile, "// ");
- for (i=0; i<meta_length; ++i) {
- int ch = t->trkptr[i];
- fprintf(outfile, "%c", isprint(ch) ? ch : '?');
- }
- fprintf(outfile, "\n");
- }
- break;
- }
- t->trkptr += meta_length;
- }
-
- else if (event <0x80) midi_error("Unknown MIDI event type", t->trkptr);
-
- else {
- chan = event & 0xf;
- switch (event>>4) {
- case 0x8:
- t->note = *t->trkptr++;
- velocity = *t->trkptr++;
- note_off:
- if (logparse) fprintf (logfile, "note %02X off, chan %d, velocity %d\n", t->note, chan, velocity);
- if ((1<<chan) & channel_mask) {
- t->cmd = CMD_STOPNOTE;
- return;
- }
- break;
- case 0x9:
- t->note = *t->trkptr++;
- velocity = *t->trkptr++;
- if (velocity == 0) goto note_off;
- t->velocity = velocity;
- if (logparse) fprintf (logfile, "note %02X on, chan %d, velocity %d\n", t->note, chan, velocity);
- if ((1<<chan) & channel_mask) {
- t->cmd = CMD_PLAYNOTE;
- return;
- }
- break;
- case 0xa:
- note = *t->trkptr++;
- velocity = *t->trkptr++;
- if (logparse) fprintf (logfile, "after-touch %02X, %02X\n", note, velocity);
- break;
- case 0xb:
- note = *t->trkptr++;
- velocity = *t->trkptr++;
- if (logparse) fprintf (logfile, "control change %02X, %02X\n", note, velocity);
- break;
- case 0xc:
- note = *t->trkptr++;
- if (logparse) fprintf(logfile, "program patch %02X\n", note);
- break;
- case 0xd:
- chan = *t->trkptr++;
- if (logparse) fprintf(logfile, "channel after-touch %02X\n", chan);
- break;
- case 0xe:
- note = *t->trkptr++;
- velocity = *t->trkptr++;
- if (logparse) fprintf(logfile, "pitch wheel change %02X, %02X\n", note, velocity);
- break;
- case 0xf:
- sysex_length = get_varlen(&t->trkptr);
- if (logparse) fprintf(logfile, "SysEx event %02X, %ld bytes\n", event, sysex_length);
- t->trkptr += sysex_length;
- break;
- default:
- midi_error("Unknown MIDI command", t->trkptr);
- }
- }
- }
- t->cmd = CMD_TRACKDONE;
- ++tracks_done;
- }
-
-
-
-
- int main(int argc,char *argv[]) {
- int argno;
- char *filebasename;
- #define MAXPATH 120
- char filename[MAXPATH];
-
- int tracknum;
- int earliest_tracknum;
- unsigned long earliest_time;
- int notes_skipped = 0;
-
- printf("MIDITONES V%s, (C) 2011,2015,2016 Len Shustek\n", VERSION);
- printf("See the source code for license information.\n\n");
- if (argc == 1) {
- SayUsage(argv[0]);
- return 1;
- }
-
-
-
- argno = HandleOptions(argc,argv);
- filebasename = argv[argno];
-
-
-
- if (logparse || loggen) {
- miditones_strlcpy(filename, filebasename, MAXPATH);
- miditones_strlcat(filename, ".log", MAXPATH);
- logfile = fopen(filename, "w");
- if (!logfile) {
- fprintf(stderr, "Unable to open log file %s", filename);
- return 1;
- }
- }
-
-
-
- miditones_strlcpy(filename, filebasename, MAXPATH);
- miditones_strlcat(filename, ".mid", MAXPATH);
- infile = fopen(filename, "rb");
- if (!infile) {
- fprintf(stderr, "Unable to open input file %s", filename);
- return 1;
- }
-
-
-
- fseek(infile, 0, SEEK_END);
- buflen = ftell(infile);
- fseek(infile, 0, SEEK_SET);
-
- buffer = (unsigned char *) malloc (buflen+1);
- if (!buffer) {
- fprintf(stderr, "Unable to allocate %ld bytes for the file", buflen);
- return 1;
- }
-
- fread(buffer, buflen, 1, infile);
- fclose(infile);
- if (logparse) fprintf(logfile, "Processing %s, %ld bytes\n", filename, buflen);
-
-
-
- if (!parseonly) {
- miditones_strlcpy(filename, filebasename, MAXPATH);
- if (binaryoutput) {
- miditones_strlcat(filename, ".bin", MAXPATH);
- outfile = fopen(filename, "wb");
- }
- else {
- miditones_strlcat(filename, ".c", MAXPATH);
- outfile = fopen(filename, "w");
- }
- if (!outfile) {
- fprintf(stderr, "Unable to open output file %s", filename);
- return 1;
- }
- if (!binaryoutput) {
- time_t rawtime;
- time (&rawtime);
- fprintf(outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename);
- fprintf(outfile, "created by MIDITONES V%s on %s", VERSION, asctime(localtime(&rawtime)));
- print_command_line(argc,argv);
- if (channel_mask != 0xffff)
- fprintf(outfile, "// Only the masked channels were processed: %04X\n", channel_mask);
- if (keyshift != 0)
- fprintf(outfile, "// Keyshift was %d chromatic notes\n", keyshift);
- 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");
- }
- }
-
-
-
- hdrptr = buffer;
- process_header ();
- printf (" Processing %d tracks.\n", num_tracks);
- if (num_tracks > MAX_TRACKS) midi_error ("Too many tracks", buffer);
-
-
-
- for (tracknum=0; tracknum < num_tracks; ++tracknum) {
- start_track (tracknum);
- find_note (tracknum);
-
-
- if (parseonly) while (track[tracknum].cmd != CMD_TRACKDONE) find_note(tracknum);
- }
-
-
-
-
- tracknum = 0;
- if (!parseonly) do {
- struct track_status *trk;
- struct tonegen_status *tg;
- int tgnum;
- int count_tracks;
- unsigned long delta_time, delta_msec;
-
-
-
-
- earliest_time = 0x7fffffff;
-
-
-
-
- count_tracks = num_tracks;
- if (strategy1) tracknum = num_tracks;
- do {
- if (++tracknum >= num_tracks) tracknum=0;
- trk = &track[tracknum];
- if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) {
- earliest_time = trk->time;
- earliest_tracknum = tracknum;
- }
- }
- while (--count_tracks);
-
- tracknum = earliest_tracknum;
- trk = &track[tracknum];
-
- if (loggen) fprintf (logfile, "Earliest time is trk %d, time %ld\n", tracknum, earliest_time);
- if (earliest_time < timenow) midi_error ("INTERNAL: time went backwards", trk->trkptr);
-
-
-
- delta_time = earliest_time - timenow;
- if (delta_time) {
-
- delta_msec = ((unsigned long) delta_time * tempo) / ticks_per_beat / 1000;
- if (loggen) fprintf (logfile, "->Delay %ld msec (%ld ticks)\n", delta_msec, delta_time);
- if (delta_msec > 0x7fff) midi_error ("INTERNAL: time delta too big", trk->trkptr);
-
- if (binaryoutput) {
- putc ((unsigned char) (delta_msec >> 8), outfile);
- putc ((unsigned char) (delta_msec & 0xff), outfile);
- outfile_bytecount += 2;
- }
- else {
- fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff);
- outfile_items(2);
- }
- }
- timenow = earliest_time;
-
-
-
-
- if (trk->cmd == CMD_TEMPO) {
- tempo = trk->tempo;
- if (loggen) fprintf (logfile, "Tempo changed to %ld usec/qnote\n", tempo);
- find_note (tracknum);
- }
-
-
-
-
- else if (trk->cmd == CMD_STOPNOTE) do {
-
-
- for (tgnum=0; tgnum < num_tonegens; ++tgnum) {
- tg = &tonegen[tgnum];
- if (tg->playing && tg->track == tracknum && tg->note == trk->note) {
- if (loggen) fprintf (logfile, "->Stop note %02X, generator %d, track %d\n", tg->note, tgnum, tracknum);
- if (binaryoutput) {
- putc (CMD_STOPNOTE | tgnum, outfile);
- outfile_bytecount += 1;
- }
- else {
- fprintf (outfile, "0x%02X, ", CMD_STOPNOTE | tgnum);
- outfile_items (1);
- }
- tg->playing = false;
- trk->tonegens[tgnum] = false;
- }
- }
- find_note (tracknum);
- }
- while (trk->cmd == CMD_STOPNOTE && trk->time == timenow);
-
-
-
-
- else if (trk->cmd == CMD_PLAYNOTE) {
- bool foundgen = false;
-
- if (strategy2) {
- tg = &tonegen [trk->preferred_tonegen];
- if (!tg->playing) {
- tgnum = trk->preferred_tonegen;
- foundgen = true;
- }
- }
- if (!foundgen) for (tgnum=0; tgnum < num_tonegens; ++tgnum) {
- tg = &tonegen[tgnum];
- if (!tg->playing) {
- foundgen = true;
- break;
- }
- }
- if (foundgen) {
- int shifted_note;
- if (tgnum+1 > num_tonegens_used) num_tonegens_used = tgnum+1;
- tg->playing = true;
- tg->track = tracknum;
- tg->note = trk->note;
- trk->tonegens[tgnum] = true;
- trk->preferred_tonegen = tgnum;
- if (loggen) fprintf (logfile, "->Start note %02X, generator %d, track %d\n", trk->note, tgnum, tracknum);
- shifted_note = trk->note + keyshift;
- if (shifted_note < 0) shifted_note = 0;
- if (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 {
- if (velocityoutput == 0) {
- 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 {
- if (loggen) fprintf (logfile, "----> No free generator, skipping note %02X, track %d\n", trk->note, tracknum);
- ++notes_skipped;
- }
- find_note (tracknum);
- }
-
- }
- while (tracks_done < num_tracks);
-
- if (!parseonly) {
-
- if(binaryoutput) putc(CMD_STOP, outfile);
- else {
- fprintf(outfile, "0x%02x};\n// This score contains %ld bytes, and %d tone generator%s used.\n", 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);
- }
- printf (" %s %d tone generators were used.\n", num_tonegens_used < num_tonegens ? "Only":"All", num_tonegens_used);
- if (notes_skipped) printf(" %d notes were skipped because there weren't enough tone generators.\n", notes_skipped);
- printf (" %ld bytes of score data were generated.\n", outfile_bytecount);
- }
-
-
- printf (" Done.\n");
- return 0;
- }
|