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