midifile
diff 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 diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/midi.c Thu Jan 26 00:31:39 2012 +0200 1.3 @@ -0,0 +1,362 @@ 1.4 +#include <stdio.h> 1.5 +#include <stdlib.h> 1.6 +#include <string.h> 1.7 +#include <errno.h> 1.8 +#include <inttypes.h> 1.9 +#include <unistd.h> 1.10 +#include <fcntl.h> 1.11 +#include <sys/mman.h> 1.12 +#include <sys/stat.h> 1.13 +#include <arpa/inet.h> 1.14 +#include "midi.h" 1.15 + 1.16 +#define FMT_SINGLE 0 1.17 +#define FMT_MULTI_TRACK 1 1.18 +#define FMT_MULTI_SEQ 2 1.19 + 1.20 +/* meta events */ 1.21 +#define META_SEQ 0 1.22 +#define META_TEXT 1 1.23 +#define META_COPYRIGHT 2 1.24 +#define META_NAME 3 1.25 +#define META_INSTR 4 1.26 +#define META_LYRICS 5 1.27 +#define META_MARKER 6 1.28 +#define META_CUE 7 1.29 +#define META_CHANPREFIX 32 1.30 +#define META_END_TRACK 47 1.31 +#define META_TEMPO 81 1.32 +#define META_SMPTE_OFFS 84 1.33 +#define META_TMSIG 88 1.34 +#define META_KEYSIG 89 1.35 +#define META_SPECIFIC 127 1.36 + 1.37 +struct midi { 1.38 + int bpm; 1.39 + 1.40 + int num_tracks; 1.41 + struct midi_track *tracks; 1.42 +}; 1.43 + 1.44 +struct midi_track { 1.45 + char *name; 1.46 + struct midi_event *head, *tail; 1.47 + int num_ev; 1.48 +}; 1.49 + 1.50 +struct midi_event { 1.51 + long dt; 1.52 + int type; 1.53 + int channel; 1.54 + int arg[2]; 1.55 + 1.56 + struct midi_event *next; 1.57 +}; 1.58 + 1.59 +#define CHUNK_HDR_SIZE 8 1.60 +struct chunk_hdr { 1.61 + char id[4]; 1.62 + uint32_t size; 1.63 + unsigned char data[1]; 1.64 +}; 1.65 + 1.66 +struct midi_hdr { 1.67 + uint16_t fmt; /* 0: single, 1: multi-track, 2: multiple independent */ 1.68 + uint16_t num_tracks; 1.69 + uint16_t tm_div; 1.70 + 1.71 +} __attribute__ ((packed)); 1.72 + 1.73 +static void destroy_track(struct midi_track *trk); 1.74 +static int read_track(struct midi *midi, struct chunk_hdr *chunk); 1.75 +static long read_vardata(unsigned char **pptr); 1.76 +static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr); 1.77 +static int read_sysex_event(struct midi *midi, unsigned char **pptr); 1.78 +static int ischunk(struct chunk_hdr *chunk, const char *name); 1.79 +static struct chunk_hdr *mkchunk(void *ptr); 1.80 +static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk); 1.81 +static struct midi_hdr *mkmidi(void *ptr); 1.82 +static void bigend(void *ptr, int sz); 1.83 +static void *map_file(const char *fname, int *size); 1.84 +static void unmap_file(void *mem, int size); 1.85 + 1.86 +#define IS_VALID_EVTYPE(x) ((x) >= MIDI_NOTE_OFF && (x) <= MIDI_PITCH_BEND) 1.87 + 1.88 +/* XXX the event arity table must match the MIDI_* defines in midi.h */ 1.89 +static int ev_arity[] = { 1.90 + 0, 0, 0, 0, 0, 0, 0, 0, 1.91 + 2, /* note off (note, velocity)*/ 1.92 + 2, /* note on (note, velocity)*/ 1.93 + 2, /* note aftertouch (note, aftertouch value) */ 1.94 + 2, /* controller (controller number, value) */ 1.95 + 1, /* prog change (prog number) */ 1.96 + 1, /* channel aftertouch (aftertouch value) */ 1.97 + 2 /* pitch bend (pitch LSB, pitch MSB) */ 1.98 +}; 1.99 + 1.100 + 1.101 +struct midi *load_midi(const char *fname) 1.102 +{ 1.103 + struct midi *midi; 1.104 + char *mem; 1.105 + int size; 1.106 + struct chunk_hdr *chunk; 1.107 + struct midi_hdr *hdr; 1.108 + 1.109 + if(!(mem = map_file(fname, &size))) { 1.110 + return 0; 1.111 + } 1.112 + chunk = mkchunk(mem); 1.113 + 1.114 + if(!ischunk(chunk, "MThd") || chunk->size != 6) { 1.115 + fprintf(stderr, "invalid or corrupted midi file: %s\n", fname); 1.116 + goto end; 1.117 + } 1.118 + hdr = mkmidi(chunk->data); 1.119 + 1.120 + printf("format: %d\n", (int)hdr->fmt); 1.121 + printf("tracks: %d\n", (int)hdr->num_tracks); 1.122 + 1.123 + if((hdr->tm_div & 0x8000) == 0) { 1.124 + /* division is in pulses / quarter note */ 1.125 + printf("time division: %d ppqn\n", (int)hdr->tm_div); 1.126 + } else { 1.127 + /* division in frames / sec */ 1.128 + int fps = (hdr->tm_div & 0x7f00) >> 8; 1.129 + int ticks_per_frame = hdr->tm_div & 0xff; 1.130 + printf("time division: %d fps, %d ticks/frame\n", fps, ticks_per_frame); 1.131 + } 1.132 + 1.133 + if(!(midi = malloc(sizeof *midi))) { 1.134 + perror("failed to allocate memory"); 1.135 + goto end; 1.136 + } 1.137 + if(!(midi->tracks = malloc(hdr->num_tracks * sizeof *midi->tracks))) { 1.138 + perror("failed to allocate memory"); 1.139 + goto end; 1.140 + } 1.141 + midi->num_tracks = 0; 1.142 + 1.143 + while((chunk = skip_chunk(chunk))) { 1.144 + if(ischunk(chunk, "MTrk")) { 1.145 + if(read_track(midi, chunk) == -1) { 1.146 + fprintf(stderr, "failed to read track\n"); 1.147 + } 1.148 + } else { 1.149 + printf("ignoring chunk: %c%c%c%c\n", chunk->id[0], chunk->id[1], chunk->id[2], chunk->id[3]); 1.150 + } 1.151 + } 1.152 + 1.153 +end: 1.154 + unmap_file(mem, size); 1.155 + if(midi) { 1.156 + free_midi(midi); 1.157 + midi = 0; 1.158 + } 1.159 + return midi; 1.160 +} 1.161 + 1.162 +void free_midi(struct midi *midi) 1.163 +{ 1.164 + int i; 1.165 + 1.166 + if(!midi) return; 1.167 + 1.168 + for(i=0; i<midi->num_tracks; i++) { 1.169 + destroy_track(midi->tracks + i); 1.170 + } 1.171 + 1.172 + free(midi->tracks); /* TODO free tracks properly */ 1.173 + free(midi); 1.174 +} 1.175 + 1.176 +static void destroy_track(struct midi_track *trk) 1.177 +{ 1.178 + free(trk->name); 1.179 + while(trk->head) { 1.180 + void *tmp = trk->head; 1.181 + trk->head = trk->head->next; 1.182 + free(tmp); 1.183 + } 1.184 +} 1.185 + 1.186 +static int read_track(struct midi *midi, struct chunk_hdr *chunk) 1.187 +{ 1.188 + unsigned char *ptr; 1.189 + struct midi_track trk = {0, 0, 0, 0}; 1.190 + 1.191 + if(!ischunk(chunk, "MTrk")) { 1.192 + return -1; 1.193 + } 1.194 + 1.195 + ptr = chunk->data; 1.196 + while(ptr < chunk->data + chunk->size) { 1.197 + long dt; 1.198 + unsigned char stat; 1.199 + 1.200 + /* TODO also convert dt to some standard unit */ 1.201 + dt = read_vardata(&ptr); 1.202 + stat = *ptr++; 1.203 + 1.204 + if(stat == 0xff) { 1.205 + read_meta_event(midi, &trk, &ptr); 1.206 + } else if(stat == 0xf0) { 1.207 + read_sysex_event(midi, &ptr); 1.208 + } else { 1.209 + struct midi_event *ev = malloc(sizeof *ev); 1.210 + 1.211 + if(trk.head) { 1.212 + trk.tail->next = ev; 1.213 + } else { 1.214 + trk.head = ev; 1.215 + } 1.216 + trk.tail = ev; 1.217 + ev->next = 0; 1.218 + trk.num_ev++; 1.219 + 1.220 + ev->dt = dt; 1.221 + ev->type = (stat >> 4) & 0xf; 1.222 + if(!IS_VALID_EVTYPE(ev->type)) { 1.223 + fprintf(stderr, "warning, skipping track with unknown event %d\n", ev->type); 1.224 + return -1; 1.225 + } 1.226 + ev->channel = stat & 0xf; 1.227 + 1.228 + ev->arg[0] = *ptr++; 1.229 + if(ev_arity[ev->type] > 1) { 1.230 + ev->arg[1] = *ptr++; 1.231 + } 1.232 + } 1.233 + } 1.234 + 1.235 + /* if we did actually add any events ... */ 1.236 + if(trk.num_ev) { 1.237 + midi->tracks[midi->num_tracks++] = trk; 1.238 + } 1.239 + return 0; 1.240 +} 1.241 + 1.242 +static long read_vardata(unsigned char **pptr) 1.243 +{ 1.244 + int i; 1.245 + long res = 0; 1.246 + unsigned char *ptr = *pptr; 1.247 + 1.248 + for(i=0; i<4; i++) { 1.249 + res |= (long)(*ptr & 0x7f) << (i * 8); 1.250 + 1.251 + /* if first bit is not set we're done */ 1.252 + if((*ptr++ & 0x80) == 0) 1.253 + break; 1.254 + } 1.255 + *pptr = ptr; 1.256 + return res; 1.257 +} 1.258 + 1.259 +static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr) 1.260 +{ 1.261 + unsigned char *ptr = *pptr; 1.262 + unsigned char type; 1.263 + long size; 1.264 + 1.265 + type = *ptr++; 1.266 + size = read_vardata(&ptr); 1.267 + 1.268 + switch(type) { 1.269 + case META_NAME: 1.270 + free(trk->name); 1.271 + trk->name = malloc(size + 1); 1.272 + memcpy(trk->name, ptr, size); 1.273 + trk->name[size] = 0; 1.274 + break; 1.275 + 1.276 + case META_TEMPO: 1.277 + /* TODO add a tempo change event to the midi struct */ 1.278 + break; 1.279 + 1.280 + default: 1.281 + break; 1.282 + } 1.283 + *pptr = ptr + size; 1.284 + return 0; 1.285 +} 1.286 + 1.287 +/* ignore sysex events */ 1.288 +static int read_sysex_event(struct midi *midi, unsigned char **pptr) 1.289 +{ 1.290 + long size = read_vardata(pptr); 1.291 + *pptr += size; 1.292 + return 0; 1.293 +} 1.294 + 1.295 +static int ischunk(struct chunk_hdr *chunk, const char *name) 1.296 +{ 1.297 + return memcmp(chunk->id, name, 4) == 0; 1.298 +} 1.299 + 1.300 +static struct chunk_hdr *mkchunk(void *ptr) 1.301 +{ 1.302 + struct chunk_hdr *chdr = ptr; 1.303 + bigend(&chdr->size, sizeof chdr->size); 1.304 + return chdr; 1.305 +} 1.306 + 1.307 +static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk) 1.308 +{ 1.309 + return mkchunk((char*)chunk + CHUNK_HDR_SIZE + chunk->size); 1.310 +} 1.311 + 1.312 +static struct midi_hdr *mkmidi(void *ptr) 1.313 +{ 1.314 + struct midi_hdr *midi = ptr; 1.315 + 1.316 + bigend(&midi->fmt, sizeof midi->fmt); 1.317 + bigend(&midi->num_tracks, sizeof midi->num_tracks); 1.318 + bigend(&midi->tm_div, sizeof midi->tm_div); 1.319 + return midi; 1.320 +} 1.321 + 1.322 +static void bigend(void *ptr, int sz) 1.323 +{ 1.324 + switch(sz) { 1.325 + case 4: 1.326 + *((uint32_t*)ptr) = ntohl(*(uint32_t*)ptr); 1.327 + break; 1.328 + 1.329 + case 2: 1.330 + *(uint16_t*)ptr = ntohs(*(uint16_t*)ptr); 1.331 + break; 1.332 + 1.333 + case 1: 1.334 + default: 1.335 + break; 1.336 + } 1.337 +} 1.338 + 1.339 +static void *map_file(const char *fname, int *size) 1.340 +{ 1.341 + int fd; 1.342 + struct stat st; 1.343 + void *mem; 1.344 + 1.345 + if((fd = open(fname, O_RDONLY)) == -1) { 1.346 + fprintf(stderr, "failed to open midi file: %s: %s\n", fname, strerror(errno)); 1.347 + return 0; 1.348 + } 1.349 + fstat(fd, &st); 1.350 + 1.351 + if((mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void*)-1) { 1.352 + fprintf(stderr, "failed to map midi file: %s: %s\n", fname, strerror(errno)); 1.353 + close(fd); 1.354 + return 0; 1.355 + } 1.356 + close(fd); 1.357 + 1.358 + *size = st.st_size; 1.359 + return mem; 1.360 +} 1.361 + 1.362 +static void unmap_file(void *mem, int size) 1.363 +{ 1.364 + munmap(mem, size); 1.365 +}