mpsync improvements.
[utils.git] / miditool.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <stdint.h>
6
7 int trackno = 0;
8 int tickres;
9
10 unsigned char fgetuc(FILE *in)
11 {
12     return((unsigned char)fgetc(in));
13 }
14
15 int fgetl(FILE *in)
16 {
17     return((fgetuc(in) << 24) |
18            (fgetuc(in) << 16) |
19            (fgetuc(in) << 8) |
20            fgetuc(in));
21 }
22
23 int fgetus(FILE *in)
24 {
25     return((fgetuc(in) << 8) | fgetuc(in));
26 }
27
28 int fget3b(FILE *in)
29 {
30     return((fgetuc(in) << 16) | (fgetuc(in) << 8) | fgetuc(in));
31 }
32
33 int fgetv(FILE *in, int *nb)
34 {
35     int ret;
36     unsigned char c;
37     
38     for(ret = 0, c = 0x80, *nb = 0; (c & 0x80); ret = (ret << 7) | (c & 0x7f)) {
39         c = fgetuc(in);
40         (*nb)++;
41         if(*nb == 5) {
42             fprintf(stderr, "miditool: reading too many bytes from a variant number\n");
43             exit(1);
44         }
45     }
46     return(ret);
47 }
48
49 void fputl(uint32_t val, FILE *out)
50 {
51     int i;
52     
53     for(i = 24; i >= 0; i -= 8)
54         fputc((val & (0xff << i)) >> i, out);
55 }
56
57 void fputus(uint16_t val, FILE *out)
58 {
59     int i;
60     
61     for(i = 8; i >= 0; i -= 8)
62         fputc((val & (0xff << i)) >> i, out);
63 }
64
65 void fskip(FILE *in, int bytes)
66 {
67     char *dummy;
68     
69     dummy = malloc(bytes);
70     fread(dummy, 1, bytes, in);
71     free(dummy);
72 }
73
74 void dumphead(FILE *in, FILE *out)
75 {
76     int type, ntrk, res;
77     
78     type = fgetus(in);
79     ntrk = fgetus(in);
80     tickres = res = fgetus(in);
81     fprintf(out, "header %i %i %i\n", type, ntrk, res);
82     fprintf(out, "# Type: %i ", type);
83     if(type == 0)
84         fprintf(out, "(Single track)\n");
85     else if(type == 1)
86         fprintf(out, "(Simultaneous tracks)\n");
87     else if(type == 2)
88         fprintf(out, "(Sequential tracks)\n");
89     else
90         fprintf(out, "(Unknown)\n");
91     fprintf(out, "# Number of tracks: %i\n", ntrk);
92     fprintf(out, "# Resolution: %i ticks/qn\n", res);
93 }
94
95 int dumptrack(FILE *in, FILE *out, ssize_t len)
96 {
97     int nb, dt, cmd, pcmd, mlen, llen;
98     int chan, note, val, id;
99     int tick;
100     
101     fprintf(out, "track\n");
102     fprintf(out, "# Number %i\n", trackno++);
103     fprintf(out, "# Length: %zi bytes\n", len);
104     pcmd = 0;
105     tick = 0;
106     while(len > 0) {
107         dt = fgetv(in, &nb);
108         tick += dt;
109         len -= nb;
110         cmd = fgetuc(in);
111         len--;
112         
113         if(!(cmd & 0x80))
114             cmd = pcmd;
115         pcmd = cmd;
116         if(!(cmd & 0x80)) {
117             fprintf(stderr, "miditool: illegal command (< 0x80)\n");
118             fskip(in, len);
119             return(1);
120         }
121         
122         chan = cmd & 0x0f;
123         cmd >>= 4;
124         if(cmd != 15)
125             fprintf(out, "ev %i %i ", tick, chan);
126         switch(cmd) {
127         case 8:
128         case 9:
129             note = fgetuc(in);
130             val = fgetuc(in);
131             len -= 2;
132             fprintf(out, "%s %i %i", (cmd == 9)?"on":"off", note, val);
133             break;
134         case 10:
135             val = fgetus(in);
136             len -= 2;
137             fprintf(out, "poly %i", val);
138             break;
139         case 11:
140             id = fgetuc(in);
141             val = fgetuc(in);
142             len -= 2;
143             fprintf(out, "ctrl %i %i", id, val);
144             break;
145         case 12:
146             val = fgetuc(in);
147             len--;
148             fprintf(out, "prgm %i", val);
149             break;
150         case 13:
151             val = fgetuc(in);
152             len--;
153             fprintf(out, "mono %i", val);
154             break;
155         case 14:
156             val = fgetus(in) - 8192;
157             len -= 2;
158             fprintf(out, "bend %i", val);
159             break;
160         case 15:
161             switch(chan) {
162             case 0:
163                 fprintf(out, "msg0 ");
164                 mlen = fgetv(in, &nb);
165                 len -= nb;
166                 for(; mlen > 0; mlen--) {
167                     fprintf(out, "%02x", fgetuc(in));
168                     len--;
169                     if(mlen > 0)
170                         fputc(' ', out);
171                 }
172                 fputc('\n', out);
173                 break;
174             case 7:
175                 fprintf(out, "msg7 ");
176                 mlen = fgetv(in, &nb);
177                 len -= nb;
178                 for(; mlen > 0; mlen--) {
179                     fprintf(out, "%02x", fgetuc(in));
180                     len--;
181                     if(mlen > 0)
182                         fputc(' ', out);
183                 }
184                 fputc('\n', out);
185                 break;
186             case 15:
187                 id = fgetuc(in);
188                 len--;
189                 mlen = fgetv(in, &nb);
190                 len -= nb;
191                 llen = 0;
192                 switch(id) {
193                 case 1 ... 9:
194                     fprintf(out, "text %i ", id);
195                     for(llen = 0; llen < mlen; llen++) {
196                         val = fgetuc(in);
197                         if((val >= 20) && (val <= 126)) {
198                             fputc(val, out);
199                         } else {
200                             fprintf(out, "\\%03o", val);
201                         }
202                     }
203                     fputc('\n', out);
204                     break;
205                 case 0x2f:
206                     fprintf(out, "end\n");
207                     break;
208                 case 0x51:
209                     val = fget3b(in);
210                     llen = 3;
211                     fprintf(out, "tempo %i\n", val);
212                     fprintf(out, "# (%i us/qn) / (%i ticks/qn) = (%i us/tick)\n", val, tickres, val / tickres);
213                     break;
214                 default:
215                     fprintf(out, "meta %i ", id);
216                     for(; mlen > 0; mlen--) {
217                         fprintf(out, "%02x", fgetuc(in));
218                         len--;
219                         if(mlen > 0)
220                             fputc(' ', out);
221                     }
222                     fputc('\n', out);
223                     fprintf(stderr, "miditool: warning: unknown midi meta event %i\n", id);
224                     fskip(in, mlen);
225                     len -= mlen;
226                     llen = -1;
227                     break;
228                 }
229                 if(llen != -1) {
230                     len -= llen;
231                     if(llen < mlen) {
232                         fskip(in, mlen - llen);
233                         len -= mlen - llen;
234                         fprintf(stderr, "miditool: warning: too little data read from meta event %i (%i < %i)\n", id, llen, mlen);
235                     } else if(llen > mlen) {
236                         fprintf(stderr, "miditool: too much data read from meta event %i (%i > %i)\n", id, llen, mlen);
237                         fskip(in, len);
238                         return(1);
239                     }
240                 }
241                 break;
242             default:
243                 fprintf(stderr, "miditool: unknown midi special event %i\n", chan);
244                 fskip(in, len);
245                 return(1);
246             }
247             break;
248         }
249         if(cmd != 15)
250             fputc('\n', out);
251     }
252     if(len < 0) {
253         fprintf(stderr, "miditool: read %i bytes too much from track from track\n", -len);
254         return(1);
255     }
256     return(0);
257 }
258
259 int dumpchunk(FILE *in, FILE *out)
260 {
261     char id[4];
262     size_t len;
263     
264     if((fread(id, 1, 4, in)) == 0)
265         return(0);
266     len = fgetl(in);
267     if(!memcmp(id, "MThd", 4)) {
268         if(len != 6) {
269             fprintf(stderr, "miditool: invalid header chunk of length %zi\n", len);
270             return(1);
271         }
272         dumphead(in, out);
273     } else if(!memcmp(id, "MTrk", 4)) {
274         if(dumptrack(in, out, len))
275             return(1);
276     } else {
277         fprintf(out, "# Unknown chunk type (%.4s) of length %zi\n", id, len);
278         fskip(in, len);
279     }
280     return(0);
281 }
282
283 char *constrack(FILE *in, int *len)
284 {
285     unsigned char *bp;
286     char *buf;
287     char line[128], *p;
288     int sz;
289     int ltime, chan;
290     
291     void putv(int v, int c) {
292         if(v >= 128)
293             putv(v >> 7, 1);
294         *(bp++) = (c?0x80:0) | (v & 0x7f);
295     }
296     void putb(int v, int n) {
297         if(n > 1)
298             putb(v >> 8, n - 1);
299         *(bp++) = v & 0xff;
300     }
301     
302     buf = malloc(sz = 65536);
303     bp = (unsigned char *)buf;
304     ltime = 0;
305     while(fgets(line, sizeof(line), in) != NULL) {
306         if(((char *)bp - buf) < 32768)
307             buf = realloc(buf, sz += 65536);
308         p = strtok(line, " ");
309         if((p == NULL) || (p[0] == '#'))
310             continue;
311         if(!strcmp(p, "ev")) {
312             if((p = strtok(NULL, " ")) == NULL) {
313                 fprintf(stderr, "miditool: truncated ev line\n");
314                 goto err;
315             }
316             putv(atoi(p) - ltime, 0);
317             ltime = atoi(p);
318             if((p = strtok(NULL, " ")) == NULL) {
319                 fprintf(stderr, "miditool: truncated ev line\n");
320                 goto err;
321             }
322             chan = atoi(p);
323             if((p = strtok(NULL, " ")) == NULL) {
324                 fprintf(stderr, "miditool: truncated ev line\n");
325                 goto err;
326             }
327             if(!strcmp(p, "on")) {
328                 *(bp++) = 0x90 | chan;
329                 if((p = strtok(NULL, " ")) == NULL) {
330                     fprintf(stderr, "miditool: truncated ev on line\n");
331                     goto err;
332                 }
333                 *(bp++) = atoi(p);
334                 if((p = strtok(NULL, " ")) == NULL) {
335                     fprintf(stderr, "miditool: truncated ev on line\n");
336                     goto err;
337                 }
338                 *(bp++) = atoi(p);
339             } else if(!strcmp(p, "off")) {
340                 *(bp++) = 0x80 | chan;
341                 if((p = strtok(NULL, " ")) == NULL) {
342                     fprintf(stderr, "miditool: truncated ev on line\n");
343                     goto err;
344                 }
345                 *(bp++) = atoi(p);
346                 if((p = strtok(NULL, " ")) == NULL) {
347                     fprintf(stderr, "miditool: truncated ev on line\n");
348                     goto err;
349                 }
350                 *(bp++) = atoi(p);
351             } else {
352                 fprintf(stderr, "miditool: ignoring unknown event %s\n", p);
353             }
354         } else if(!strcmp(p, "tempo")) {
355             *(bp++) = 0xff;
356             *(bp++) = 0x51;
357             putv(3, 0);
358             if((p = strtok(NULL, " ")) == NULL) {
359                 fprintf(stderr, "miditool: tempo line without tempo\n");
360                 goto err;
361             }
362             putb(atoi(p), 3);
363         } else if(!strcmp(p, "text")) {
364         } else if(!strcmp(p, "end")) {
365             *(bp++) = 0xff;
366             *(bp++) = 0x2f;
367             putv(0, 0);
368         } else {
369             fprintf(stderr, "miditool: ignoring unknown command %s\n", p);
370         }
371     }
372     return(buf);
373     
374 err:
375     free(buf);
376     return(NULL);
377 }
378
379 int main(int argc, char **argv)
380 {
381     FILE *in, *out;
382     char *cmd;
383     
384     if(argc < 2) {
385         fprintf(stderr, "usage: miditool command (see `miditool help' for a list of commands)\n");
386         exit(1);
387     }
388     cmd = argv[1];
389     argc--; argv++;
390     if(!strcmp(cmd, "help")) {
391         printf("commands:\n");
392         printf("\tdump\tDumps a MIDI file to text format\n");
393         printf("\tconsh\tConstructs a header chunk\n");
394     } else if(!strcmp(cmd, "consh")) {
395         if(argc < 4) {
396             fprintf(stderr, "usage: miditool consh TYPE NTRACKS RES\n");
397             exit(1);
398         }
399         out = stdout;
400         fputs("MThd", out);
401         fputl(6, out);
402         fputus(atoi(argv[1]), out);
403         fputus(atoi(argv[2]), out);
404         fputus(atoi(argv[3]), out);
405     } else if(!strcmp(cmd, "dump")) {
406         in = stdin;
407         if(argc > 1) {
408             if((in = fopen(argv[1], "r")) == NULL) {
409                 perror(argv[1]);
410                 exit(1);
411             }
412         }
413         while(!feof(in)) {
414             dumpchunk(in, stdout);
415         }
416     }
417     return(0);
418 }