midifile

view src/midi.c @ 0:3658e56b3a8a

initial commit
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 26 Jan 2012 00:31:39 +0200
parents
children abfe7b19079a
line source
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"
13 #define FMT_SINGLE 0
14 #define FMT_MULTI_TRACK 1
15 #define FMT_MULTI_SEQ 2
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
34 struct midi {
35 int bpm;
37 int num_tracks;
38 struct midi_track *tracks;
39 };
41 struct midi_track {
42 char *name;
43 struct midi_event *head, *tail;
44 int num_ev;
45 };
47 struct midi_event {
48 long dt;
49 int type;
50 int channel;
51 int arg[2];
53 struct midi_event *next;
54 };
56 #define CHUNK_HDR_SIZE 8
57 struct chunk_hdr {
58 char id[4];
59 uint32_t size;
60 unsigned char data[1];
61 };
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;
68 } __attribute__ ((packed));
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);
83 #define IS_VALID_EVTYPE(x) ((x) >= MIDI_NOTE_OFF && (x) <= MIDI_PITCH_BEND)
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 };
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;
106 if(!(mem = map_file(fname, &size))) {
107 return 0;
108 }
109 chunk = mkchunk(mem);
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);
117 printf("format: %d\n", (int)hdr->fmt);
118 printf("tracks: %d\n", (int)hdr->num_tracks);
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 }
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;
140 while((chunk = skip_chunk(chunk))) {
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 }
150 end:
151 unmap_file(mem, size);
152 if(midi) {
153 free_midi(midi);
154 midi = 0;
155 }
156 return midi;
157 }
159 void free_midi(struct midi *midi)
160 {
161 int i;
163 if(!midi) return;
165 for(i=0; i<midi->num_tracks; i++) {
166 destroy_track(midi->tracks + i);
167 }
169 free(midi->tracks); /* TODO free tracks properly */
170 free(midi);
171 }
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 }
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};
188 if(!ischunk(chunk, "MTrk")) {
189 return -1;
190 }
192 ptr = chunk->data;
193 while(ptr < chunk->data + chunk->size) {
194 long dt;
195 unsigned char stat;
197 /* TODO also convert dt to some standard unit */
198 dt = read_vardata(&ptr);
199 stat = *ptr++;
201 if(stat == 0xff) {
202 read_meta_event(midi, &trk, &ptr);
203 } else if(stat == 0xf0) {
204 read_sysex_event(midi, &ptr);
205 } else {
206 struct midi_event *ev = malloc(sizeof *ev);
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++;
217 ev->dt = dt;
218 ev->type = (stat >> 4) & 0xf;
219 if(!IS_VALID_EVTYPE(ev->type)) {
220 fprintf(stderr, "warning, skipping track with unknown event %d\n", ev->type);
221 return -1;
222 }
223 ev->channel = stat & 0xf;
225 ev->arg[0] = *ptr++;
226 if(ev_arity[ev->type] > 1) {
227 ev->arg[1] = *ptr++;
228 }
229 }
230 }
232 /* if we did actually add any events ... */
233 if(trk.num_ev) {
234 midi->tracks[midi->num_tracks++] = trk;
235 }
236 return 0;
237 }
239 static long read_vardata(unsigned char **pptr)
240 {
241 int i;
242 long res = 0;
243 unsigned char *ptr = *pptr;
245 for(i=0; i<4; i++) {
246 res |= (long)(*ptr & 0x7f) << (i * 8);
248 /* if first bit is not set we're done */
249 if((*ptr++ & 0x80) == 0)
250 break;
251 }
252 *pptr = ptr;
253 return res;
254 }
256 static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr)
257 {
258 unsigned char *ptr = *pptr;
259 unsigned char type;
260 long size;
262 type = *ptr++;
263 size = read_vardata(&ptr);
265 switch(type) {
266 case META_NAME:
267 free(trk->name);
268 trk->name = malloc(size + 1);
269 memcpy(trk->name, ptr, size);
270 trk->name[size] = 0;
271 break;
273 case META_TEMPO:
274 /* TODO add a tempo change event to the midi struct */
275 break;
277 default:
278 break;
279 }
280 *pptr = ptr + size;
281 return 0;
282 }
284 /* ignore sysex events */
285 static int read_sysex_event(struct midi *midi, unsigned char **pptr)
286 {
287 long size = read_vardata(pptr);
288 *pptr += size;
289 return 0;
290 }
292 static int ischunk(struct chunk_hdr *chunk, const char *name)
293 {
294 return memcmp(chunk->id, name, 4) == 0;
295 }
297 static struct chunk_hdr *mkchunk(void *ptr)
298 {
299 struct chunk_hdr *chdr = ptr;
300 bigend(&chdr->size, sizeof chdr->size);
301 return chdr;
302 }
304 static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk)
305 {
306 return mkchunk((char*)chunk + CHUNK_HDR_SIZE + chunk->size);
307 }
309 static struct midi_hdr *mkmidi(void *ptr)
310 {
311 struct midi_hdr *midi = ptr;
313 bigend(&midi->fmt, sizeof midi->fmt);
314 bigend(&midi->num_tracks, sizeof midi->num_tracks);
315 bigend(&midi->tm_div, sizeof midi->tm_div);
316 return midi;
317 }
319 static void bigend(void *ptr, int sz)
320 {
321 switch(sz) {
322 case 4:
323 *((uint32_t*)ptr) = ntohl(*(uint32_t*)ptr);
324 break;
326 case 2:
327 *(uint16_t*)ptr = ntohs(*(uint16_t*)ptr);
328 break;
330 case 1:
331 default:
332 break;
333 }
334 }
336 static void *map_file(const char *fname, int *size)
337 {
338 int fd;
339 struct stat st;
340 void *mem;
342 if((fd = open(fname, O_RDONLY)) == -1) {
343 fprintf(stderr, "failed to open midi file: %s: %s\n", fname, strerror(errno));
344 return 0;
345 }
346 fstat(fd, &st);
348 if((mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void*)-1) {
349 fprintf(stderr, "failed to map midi file: %s: %s\n", fname, strerror(errno));
350 close(fd);
351 return 0;
352 }
353 close(fd);
355 *size = st.st_size;
356 return mem;
357 }
359 static void unmap_file(void *mem, int size)
360 {
361 munmap(mem, size);
362 }