foo
[bootcard] / tools / mididump / midi.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <inttypes.h>
6 #include "midi.h"
7
8 #define USE_MMAP
9
10 #define FMT_SINGLE              0
11 #define FMT_MULTI_TRACK 1
12 #define FMT_MULTI_SEQ   2
13
14 /* meta events */
15 #define META_SEQ                0
16 #define META_TEXT               1
17 #define META_COPYRIGHT  2
18 #define META_NAME               3
19 #define META_INSTR              4
20 #define META_LYRICS             5
21 #define META_MARKER             6
22 #define META_CUE                7
23 #define META_CHANPREFIX 32
24 #define META_END_TRACK  47
25 #define META_TEMPO              81
26 #define META_SMPTE_OFFS 84
27 #define META_TMSIG              88
28 #define META_KEYSIG             89
29 #define META_SPECIFIC   127
30
31 #define CHUNK_HDR_SIZE  8
32 struct chunk_hdr {
33         char id[4];
34         uint32_t size;
35         unsigned char data[1];
36 };
37
38 struct midi_hdr {
39         uint16_t fmt;   /* 0: single, 1: multi-track, 2: multiple independent */
40         uint16_t num_tracks;
41         uint16_t tm_div;
42
43 } __attribute__ ((packed));
44
45 static void destroy_track(struct midi_track *trk);
46 static int read_track(struct midi *midi, struct chunk_hdr *chunk);
47 static long read_vardata(unsigned char **pptr);
48 static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr);
49 static int read_sysex_event(struct midi *midi, unsigned char **pptr);
50 static int ischunk(struct chunk_hdr *chunk, const char *name);
51 static struct chunk_hdr *mkchunk(void *ptr);
52 static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk);
53 static struct midi_hdr *mkmidi(void *ptr);
54 static void bigend(void *ptr, int sz);
55 static void *map_file(const char *fname, int *size);
56 static void unmap_file(void *mem, int size);
57
58 #define IS_VALID_EVTYPE(x) ((x) >= MIDI_NOTE_OFF && (x) <= MIDI_PITCH_BEND)
59
60 /* XXX the event arity table must match the MIDI_* defines in midi.h */
61 static int ev_arity[] = {
62         0, 0, 0, 0, 0, 0, 0, 0,
63         2, /* note off (note, velocity)*/
64         2, /* note on (note, velocity)*/
65         2, /* note aftertouch (note, aftertouch value) */
66         2, /* controller (controller number, value) */
67         1, /* prog change (prog number) */
68         1, /* channel aftertouch (aftertouch value) */
69         2  /* pitch bend (pitch LSB, pitch MSB) */
70 };
71
72
73 struct midi *midi_load(const char *fname)
74 {
75         struct midi *midi;
76         char *mem;
77         int size;
78         struct chunk_hdr *chunk;
79         struct midi_hdr *hdr;
80
81         if(!(mem = map_file(fname, &size))) {
82                 return 0;
83         }
84         chunk = mkchunk(mem);
85
86         if(!ischunk(chunk, "MThd") || chunk->size != 6) {
87                 fprintf(stderr, "invalid or corrupted midi file: %s\n", fname);
88                 goto err;
89         }
90         hdr = mkmidi(chunk->data);
91
92         if(!(midi = malloc(sizeof *midi))) {
93                 perror("failed to allocate memory");
94                 goto err;
95         }
96
97         if((hdr->tm_div & 0x8000) == 0) {
98                 /* division is in pulses / quarter note */
99                 midi->ppqn = hdr->tm_div;
100                 midi->fps = midi->ticks_per_frame = -1;
101         } else {
102                 /* division in frames / sec */
103                 midi->fps = (hdr->tm_div & 0x7f00) >> 8;
104                 midi->ticks_per_frame = hdr->tm_div & 0xff;
105                 midi->ppqn = -1;
106         }
107
108         if(!(midi->tracks = malloc(hdr->num_tracks * sizeof *midi->tracks))) {
109                 perror("failed to allocate memory");
110                 goto err;
111         }
112         midi->num_tracks = 0;
113
114         while((chunk = skip_chunk(chunk)) && ((char*)chunk < mem + size)) {
115                 if(ischunk(chunk, "MTrk")) {
116                         if(read_track(midi, chunk) == -1) {
117                                 fprintf(stderr, "failed to read track\n");
118                         }
119                 }
120         }
121
122         unmap_file(mem, size);
123         return midi;
124
125 err:
126         unmap_file(mem, size);
127         midi_free(midi);
128         return 0;
129 }
130
131 void midi_free(struct midi *midi)
132 {
133         int i;
134
135         if(!midi) return;
136
137         for(i=0; i<midi->num_tracks; i++) {
138                 destroy_track(midi->tracks + i);
139         }
140
141         free(midi->tracks);
142         free(midi);
143 }
144
145 int midi_num_tracks(struct midi *midi)
146 {
147         return midi->num_tracks;
148 }
149
150 struct midi_track *midi_track(struct midi *midi, int idx)
151 {
152         if(idx < 0 || idx >= midi->num_tracks) {
153                 return 0;
154         }
155         return midi->tracks + idx;
156 }
157
158 static void destroy_track(struct midi_track *trk)
159 {
160         free(trk->name);
161         while(trk->head) {
162                 void *tmp = trk->head;
163                 trk->head = trk->head->next;
164                 free(tmp);
165         }
166 }
167
168 static int read_track(struct midi *midi, struct chunk_hdr *chunk)
169 {
170         unsigned char *ptr;
171         struct midi_track trk = {0, 0, 0, 0};
172         unsigned char prev_stat = 0;
173         int type;
174         struct midi_event *ev;
175
176         if(!ischunk(chunk, "MTrk")) {
177                 return -1;
178         }
179
180         ptr = chunk->data;
181         while(ptr < chunk->data + chunk->size) {
182                 long dt;
183                 unsigned char stat;
184
185                 dt = read_vardata(&ptr);
186                 stat = *ptr++;
187
188                 if(stat == 0xff) {
189                         read_meta_event(midi, &trk, &ptr);
190                 } else if(stat == 0xf0) {
191                         read_sysex_event(midi, &ptr);
192                 } else {
193                         if(!(stat & 0x80)) {
194                                 /* not a status byte, assume running status */
195                                 stat = prev_stat;
196                                 ptr--;
197                         }
198                         type = (stat >> 4) & 0xf;
199
200                         if(!IS_VALID_EVTYPE(type) || !(ev = malloc(sizeof *ev))) {
201                                 /* unkwown message, skip all data bytes */
202                                 while(ptr < chunk->data + chunk->size && !(*ptr & 0x80)) {
203                                         ptr++;
204                                 }
205                                 continue;
206                         }
207
208                         if(trk.head) {
209                                 trk.tail->next = ev;
210                         } else {
211                                 trk.head = ev;
212                         }
213                         trk.tail = ev;
214                         ev->next = 0;
215                         trk.num_ev++;
216
217                         ev->dt = dt;
218                         ev->type = type;
219                         ev->channel = stat & 0xf;
220
221                         ev->arg[0] = *ptr++;
222                         if(ev_arity[ev->type] > 1) {
223                                 ev->arg[1] = *ptr++;
224                         }
225
226                         if(ev->type == MIDI_NOTE_ON && ev->arg[1] == 0) {
227                                 ev->type = MIDI_NOTE_OFF;
228                         }
229
230                         prev_stat = stat;
231                 }
232         }
233
234         /* if we did actually add any events ... */
235         if(trk.num_ev) {
236                 midi->tracks[midi->num_tracks++] = trk;
237                 /*printf("loaded track with %d events\n", trk.num_ev);*/
238         }
239         return 0;
240 }
241
242 static long read_vardata(unsigned char **pptr)
243 {
244         int i;
245         long res = 0;
246         unsigned char *ptr = *pptr;
247
248         for(i=0; i<4; i++) {
249                 res |= (long)(*ptr & 0x7f) << (i * 8);
250
251                 /* if first bit is not set we're done */
252                 if((*ptr++ & 0x80) == 0)
253                         break;
254         }
255         *pptr = ptr;
256         return res;
257 }
258
259 static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr)
260 {
261         unsigned char *ptr = *pptr;
262         unsigned char type;
263         long size;
264
265         type = *ptr++;
266         size = read_vardata(&ptr);
267
268         switch(type) {
269         case META_NAME:
270                 free(trk->name);
271                 trk->name = malloc(size + 1);
272                 memcpy(trk->name, ptr, size);
273                 trk->name[size] = 0;
274                 break;
275
276         case META_TEMPO:
277                 /* TODO add a tempo change event to the midi struct */
278                 break;
279
280         default:
281                 break;
282         }
283         *pptr = ptr + size;
284         return 0;
285 }
286
287 /* ignore sysex events */
288 static int read_sysex_event(struct midi *midi, unsigned char **pptr)
289 {
290         long size = read_vardata(pptr);
291         *pptr += size;
292         return 0;
293 }
294
295 static int ischunk(struct chunk_hdr *chunk, const char *name)
296 {
297         return memcmp(chunk->id, name, 4) == 0;
298 }
299
300 static struct chunk_hdr *mkchunk(void *ptr)
301 {
302         struct chunk_hdr *chdr = ptr;
303         bigend(&chdr->size, sizeof chdr->size);
304         return chdr;
305 }
306
307 static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk)
308 {
309         return mkchunk((char*)chunk + CHUNK_HDR_SIZE + chunk->size);
310 }
311
312 static struct midi_hdr *mkmidi(void *ptr)
313 {
314         struct midi_hdr *midi = ptr;
315
316         bigend(&midi->fmt, sizeof midi->fmt);
317         bigend(&midi->num_tracks, sizeof midi->num_tracks);
318         bigend(&midi->tm_div, sizeof midi->tm_div);
319         return midi;
320 }
321
322 static void bigend(void *ptr, int sz)
323 {
324         static unsigned char test[] = {0x12, 0x34};
325         uint32_t val32;
326         uint16_t val16;
327
328         if(sz < 2 || *(uint16_t*)test == 0x1234) {
329                 return;
330         }
331
332         switch(sz) {
333         case 4:
334                 val32 = *(uint32_t*)ptr;
335                 *(uint32_t*)ptr = (val32 << 24) | (val32 >> 24) | ((val32 & 0xff00) << 8) |
336                         ((val32 & 0xff0000) >> 8);
337                 break;
338
339         case 2:
340                 val16 = *(uint16_t*)ptr;
341                 *(uint16_t*)ptr = (val16 >> 8) | (val16 << 8);
342                 break;
343
344         case 1:
345         default:
346                 break;
347         }
348 }
349
350 #if defined(__unix__) && defined(USE_MMAP)
351 #include <unistd.h>
352 #include <fcntl.h>
353 #include <sys/mman.h>
354 #include <sys/stat.h>
355
356 static void *map_file(const char *fname, int *size)
357 {
358         int fd;
359         struct stat st;
360         void *mem;
361
362         if((fd = open(fname, O_RDONLY)) == -1) {
363                 fprintf(stderr, "failed to open midi file: %s: %s\n", fname, strerror(errno));
364                 return 0;
365         }
366         fstat(fd, &st);
367
368         if((mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void*)-1) {
369                 fprintf(stderr, "failed to map midi file: %s: %s\n", fname, strerror(errno));
370                 close(fd);
371                 return 0;
372         }
373         close(fd);
374
375         *size = st.st_size;
376         return mem;
377 }
378
379 static void unmap_file(void *mem, int size)
380 {
381         munmap(mem, size);
382 }
383 #else
384 static void *map_file(const char *fname, int *size)
385 {
386         FILE *fp;
387         long sz;
388         void *buf;
389
390         if(!(fp = fopen(fname, "rb"))) {
391                 fprintf(stderr, "failed to open midi file: %s: %s\n", fname, strerror(errno));
392                 return 0;
393         }
394         fseek(fp, 0, SEEK_END);
395         sz = ftell(fp);
396         rewind(fp);
397
398         if(!(buf = malloc(sz))) {
399                 fprintf(stderr, "failed to allocate space for %s in memory (%ld bytes)\n", fname, sz);
400                 fclose(fp);
401                 return 0;
402         }
403         if(fread(buf, 1, sz, fp) != sz) {
404                 fprintf(stderr, "failed to load midi file: %s: %s\n", fname, strerror(errno));
405                 free(buf);
406                 fclose(fp);
407                 return 0;
408         }
409         fclose(fp);
410
411         *size = sz;
412         return buf;
413 }
414
415 static void unmap_file(void *mem, int size)
416 {
417         free(mem);
418 }
419 #endif