acmecert: Initial commit.
[utils.git] / miditool.c
CommitLineData
9f167cc9 1#include <stdlib.h>
2#include <stdio.h>
3#include <unistd.h>
4#include <string.h>
5#include <stdint.h>
6
7int trackno = 0;
8int tickres;
9
10unsigned char fgetuc(FILE *in)
11{
12 return((unsigned char)fgetc(in));
13}
14
15int fgetl(FILE *in)
16{
17 return((fgetuc(in) << 24) |
18 (fgetuc(in) << 16) |
19 (fgetuc(in) << 8) |
20 fgetuc(in));
21}
22
23int fgetus(FILE *in)
24{
25 return((fgetuc(in) << 8) | fgetuc(in));
26}
27
28int fget3b(FILE *in)
29{
30 return((fgetuc(in) << 16) | (fgetuc(in) << 8) | fgetuc(in));
31}
32
33int 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
49void 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
57void 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
65void 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
74void 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
95int 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
259int 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
283char *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
374err:
375 free(buf);
376 return(NULL);
377}
378
379int 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}