midifile

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