--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+
+int trackno = 0;
+int tickres;
+
+unsigned char fgetuc(FILE *in)
+{
+ return((unsigned char)fgetc(in));
+}
+
+int fgetl(FILE *in)
+{
+ return((fgetuc(in) << 24) |
+ (fgetuc(in) << 16) |
+ (fgetuc(in) << 8) |
+ fgetuc(in));
+}
+
+int fgetus(FILE *in)
+{
+ return((fgetuc(in) << 8) | fgetuc(in));
+}
+
+int fget3b(FILE *in)
+{
+ return((fgetuc(in) << 16) | (fgetuc(in) << 8) | fgetuc(in));
+}
+
+int fgetv(FILE *in, int *nb)
+{
+ int ret;
+ unsigned char c;
+
+ for(ret = 0, c = 0x80, *nb = 0; (c & 0x80); ret = (ret << 7) | (c & 0x7f)) {
+ c = fgetuc(in);
+ (*nb)++;
+ if(*nb == 5) {
+ fprintf(stderr, "miditool: reading too many bytes from a variant number\n");
+ exit(1);
+ }
+ }
+ return(ret);
+}
+
+void fputl(uint32_t val, FILE *out)
+{
+ int i;
+
+ for(i = 24; i >= 0; i -= 8)
+ fputc((val & (0xff << i)) >> i, out);
+}
+
+void fputus(uint16_t val, FILE *out)
+{
+ int i;
+
+ for(i = 8; i >= 0; i -= 8)
+ fputc((val & (0xff << i)) >> i, out);
+}
+
+void fskip(FILE *in, int bytes)
+{
+ char *dummy;
+
+ dummy = malloc(bytes);
+ fread(dummy, 1, bytes, in);
+ free(dummy);
+}
+
+void dumphead(FILE *in, FILE *out)
+{
+ int type, ntrk, res;
+
+ type = fgetus(in);
+ ntrk = fgetus(in);
+ tickres = res = fgetus(in);
+ fprintf(out, "header %i %i %i\n", type, ntrk, res);
+ fprintf(out, "# Type: %i ", type);
+ if(type == 0)
+ fprintf(out, "(Single track)\n");
+ else if(type == 1)
+ fprintf(out, "(Simultaneous tracks)\n");
+ else if(type == 2)
+ fprintf(out, "(Sequential tracks)\n");
+ else
+ fprintf(out, "(Unknown)\n");
+ fprintf(out, "# Number of tracks: %i\n", ntrk);
+ fprintf(out, "# Resolution: %i ticks/qn\n", res);
+}
+
+int dumptrack(FILE *in, FILE *out, ssize_t len)
+{
+ int nb, dt, cmd, pcmd, mlen, llen;
+ int chan, note, val, id;
+ int tick;
+
+ fprintf(out, "track\n");
+ fprintf(out, "# Number %i\n", trackno++);
+ fprintf(out, "# Length: %zi bytes\n", len);
+ pcmd = 0;
+ tick = 0;
+ while(len > 0) {
+ dt = fgetv(in, &nb);
+ tick += dt;
+ len -= nb;
+ cmd = fgetuc(in);
+ len--;
+
+ if(!(cmd & 0x80))
+ cmd = pcmd;
+ pcmd = cmd;
+ if(!(cmd & 0x80)) {
+ fprintf(stderr, "miditool: illegal command (< 0x80)\n");
+ fskip(in, len);
+ return(1);
+ }
+
+ chan = cmd & 0x0f;
+ cmd >>= 4;
+ if(cmd != 15)
+ fprintf(out, "ev %i %i ", tick, chan);
+ switch(cmd) {
+ case 8:
+ case 9:
+ note = fgetuc(in);
+ val = fgetuc(in);
+ len -= 2;
+ fprintf(out, "%s %i %i", (cmd == 9)?"on":"off", note, val);
+ break;
+ case 10:
+ val = fgetus(in);
+ len -= 2;
+ fprintf(out, "poly %i", val);
+ break;
+ case 11:
+ id = fgetuc(in);
+ val = fgetuc(in);
+ len -= 2;
+ fprintf(out, "ctrl %i %i", id, val);
+ break;
+ case 12:
+ val = fgetuc(in);
+ len--;
+ fprintf(out, "prgm %i", val);
+ break;
+ case 13:
+ val = fgetuc(in);
+ len--;
+ fprintf(out, "mono %i", val);
+ break;
+ case 14:
+ val = fgetus(in) - 8192;
+ len -= 2;
+ fprintf(out, "bend %i", val);
+ break;
+ case 15:
+ switch(chan) {
+ case 0:
+ fprintf(out, "msg0 ");
+ mlen = fgetv(in, &nb);
+ len -= nb;
+ for(; mlen > 0; mlen--) {
+ fprintf(out, "%02x", fgetuc(in));
+ len--;
+ if(mlen > 0)
+ fputc(' ', out);
+ }
+ fputc('\n', out);
+ break;
+ case 7:
+ fprintf(out, "msg7 ");
+ mlen = fgetv(in, &nb);
+ len -= nb;
+ for(; mlen > 0; mlen--) {
+ fprintf(out, "%02x", fgetuc(in));
+ len--;
+ if(mlen > 0)
+ fputc(' ', out);
+ }
+ fputc('\n', out);
+ break;
+ case 15:
+ id = fgetuc(in);
+ len--;
+ mlen = fgetv(in, &nb);
+ len -= nb;
+ llen = 0;
+ switch(id) {
+ case 1 ... 9:
+ fprintf(out, "text %i ", id);
+ for(llen = 0; llen < mlen; llen++) {
+ val = fgetuc(in);
+ if((val >= 20) && (val <= 126)) {
+ fputc(val, out);
+ } else {
+ fprintf(out, "\\%03o", val);
+ }
+ }
+ fputc('\n', out);
+ break;
+ case 0x2f:
+ fprintf(out, "end\n");
+ break;
+ case 0x51:
+ val = fget3b(in);
+ llen = 3;
+ fprintf(out, "tempo %i\n", val);
+ fprintf(out, "# (%i us/qn) / (%i ticks/qn) = (%i us/tick)\n", val, tickres, val / tickres);
+ break;
+ default:
+ fprintf(out, "meta %i ", id);
+ for(; mlen > 0; mlen--) {
+ fprintf(out, "%02x", fgetuc(in));
+ len--;
+ if(mlen > 0)
+ fputc(' ', out);
+ }
+ fputc('\n', out);
+ fprintf(stderr, "miditool: warning: unknown midi meta event %i\n", id);
+ fskip(in, mlen);
+ len -= mlen;
+ llen = -1;
+ break;
+ }
+ if(llen != -1) {
+ len -= llen;
+ if(llen < mlen) {
+ fskip(in, mlen - llen);
+ len -= mlen - llen;
+ fprintf(stderr, "miditool: warning: too little data read from meta event %i (%i < %i)\n", id, llen, mlen);
+ } else if(llen > mlen) {
+ fprintf(stderr, "miditool: too much data read from meta event %i (%i > %i)\n", id, llen, mlen);
+ fskip(in, len);
+ return(1);
+ }
+ }
+ break;
+ default:
+ fprintf(stderr, "miditool: unknown midi special event %i\n", chan);
+ fskip(in, len);
+ return(1);
+ }
+ break;
+ }
+ if(cmd != 15)
+ fputc('\n', out);
+ }
+ if(len < 0) {
+ fprintf(stderr, "miditool: read %i bytes too much from track from track\n", -len);
+ return(1);
+ }
+ return(0);
+}
+
+int dumpchunk(FILE *in, FILE *out)
+{
+ char id[4];
+ size_t len;
+
+ if((fread(id, 1, 4, in)) == 0)
+ return(0);
+ len = fgetl(in);
+ if(!memcmp(id, "MThd", 4)) {
+ if(len != 6) {
+ fprintf(stderr, "miditool: invalid header chunk of length %zi\n", len);
+ return(1);
+ }
+ dumphead(in, out);
+ } else if(!memcmp(id, "MTrk", 4)) {
+ if(dumptrack(in, out, len))
+ return(1);
+ } else {
+ fprintf(out, "# Unknown chunk type (%.4s) of length %zi\n", id, len);
+ fskip(in, len);
+ }
+ return(0);
+}
+
+char *constrack(FILE *in, int *len)
+{
+ unsigned char *bp;
+ char *buf;
+ char line[128], *p;
+ int sz;
+ int ltime, chan;
+
+ void putv(int v, int c) {
+ if(v >= 128)
+ putv(v >> 7, 1);
+ *(bp++) = (c?0x80:0) | (v & 0x7f);
+ }
+ void putb(int v, int n) {
+ if(n > 1)
+ putb(v >> 8, n - 1);
+ *(bp++) = v & 0xff;
+ }
+
+ buf = malloc(sz = 65536);
+ bp = (unsigned char *)buf;
+ ltime = 0;
+ while(fgets(line, sizeof(line), in) != NULL) {
+ if(((char *)bp - buf) < 32768)
+ buf = realloc(buf, sz += 65536);
+ p = strtok(line, " ");
+ if((p == NULL) || (p[0] == '#'))
+ continue;
+ if(!strcmp(p, "ev")) {
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev line\n");
+ goto err;
+ }
+ putv(atoi(p) - ltime, 0);
+ ltime = atoi(p);
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev line\n");
+ goto err;
+ }
+ chan = atoi(p);
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev line\n");
+ goto err;
+ }
+ if(!strcmp(p, "on")) {
+ *(bp++) = 0x90 | chan;
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev on line\n");
+ goto err;
+ }
+ *(bp++) = atoi(p);
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev on line\n");
+ goto err;
+ }
+ *(bp++) = atoi(p);
+ } else if(!strcmp(p, "off")) {
+ *(bp++) = 0x80 | chan;
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev on line\n");
+ goto err;
+ }
+ *(bp++) = atoi(p);
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: truncated ev on line\n");
+ goto err;
+ }
+ *(bp++) = atoi(p);
+ } else {
+ fprintf(stderr, "miditool: ignoring unknown event %s\n", p);
+ }
+ } else if(!strcmp(p, "tempo")) {
+ *(bp++) = 0xff;
+ *(bp++) = 0x51;
+ putv(3, 0);
+ if((p = strtok(NULL, " ")) == NULL) {
+ fprintf(stderr, "miditool: tempo line without tempo\n");
+ goto err;
+ }
+ putb(atoi(p), 3);
+ } else if(!strcmp(p, "text")) {
+ } else if(!strcmp(p, "end")) {
+ *(bp++) = 0xff;
+ *(bp++) = 0x2f;
+ putv(0, 0);
+ } else {
+ fprintf(stderr, "miditool: ignoring unknown command %s\n", p);
+ }
+ }
+ return(buf);
+
+err:
+ free(buf);
+ return(NULL);
+}
+
+int main(int argc, char **argv)
+{
+ FILE *in, *out;
+ char *cmd;
+
+ if(argc < 2) {
+ fprintf(stderr, "usage: miditool command (see `miditool help' for a list of commands)\n");
+ exit(1);
+ }
+ cmd = argv[1];
+ argc--; argv++;
+ if(!strcmp(cmd, "help")) {
+ printf("commands:\n");
+ printf("\tdump\tDumps a MIDI file to text format\n");
+ printf("\tconsh\tConstructs a header chunk\n");
+ } else if(!strcmp(cmd, "consh")) {
+ if(argc < 4) {
+ fprintf(stderr, "usage: miditool consh TYPE NTRACKS RES\n");
+ exit(1);
+ }
+ out = stdout;
+ fputs("MThd", out);
+ fputl(6, out);
+ fputus(atoi(argv[1]), out);
+ fputus(atoi(argv[2]), out);
+ fputus(atoi(argv[3]), out);
+ } else if(!strcmp(cmd, "dump")) {
+ in = stdin;
+ if(argc > 1) {
+ if((in = fopen(argv[1], "r")) == NULL) {
+ perror(argv[1]);
+ exit(1);
+ }
+ }
+ while(!feof(in)) {
+ dumpchunk(in, stdout);
+ }
+ }
+ return(0);
+}