midifile
changeset 0:3658e56b3a8a
initial commit
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Thu, 26 Jan 2012 00:31:39 +0200 |
parents | |
children | abfe7b19079a |
files | .hgignore Makefile src/midi.c src/midi.h test.c |
diffstat | 5 files changed, 432 insertions(+), 0 deletions(-) [+] |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/.hgignore Thu Jan 26 00:31:39 2012 +0200 1.3 @@ -0,0 +1,4 @@ 1.4 +\.o$ 1.5 +\.d$ 1.6 +\.swp$ 1.7 +^test$
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/Makefile Thu Jan 26 00:31:39 2012 +0200 2.3 @@ -0,0 +1,18 @@ 2.4 +src = $(wildcard src/*.c) test.c 2.5 +obj = $(src:.c=.o) 2.6 +dep = $(obj:.o=.d) 2.7 +bin = test 2.8 + 2.9 +CFLAGS = -pedantic -Wall -g -Isrc 2.10 + 2.11 +$(bin): $(obj) 2.12 + $(CC) -o $@ $(obj) $(LDFLAGS) 2.13 + 2.14 +-include $(dep) 2.15 + 2.16 +%.d: %.c 2.17 + @$(CPP) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@ 2.18 + 2.19 +.PHONY: clean 2.20 +clean: 2.21 + rm -f $(obj) $(bin)
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/src/midi.c Thu Jan 26 00:31:39 2012 +0200 3.3 @@ -0,0 +1,362 @@ 3.4 +#include <stdio.h> 3.5 +#include <stdlib.h> 3.6 +#include <string.h> 3.7 +#include <errno.h> 3.8 +#include <inttypes.h> 3.9 +#include <unistd.h> 3.10 +#include <fcntl.h> 3.11 +#include <sys/mman.h> 3.12 +#include <sys/stat.h> 3.13 +#include <arpa/inet.h> 3.14 +#include "midi.h" 3.15 + 3.16 +#define FMT_SINGLE 0 3.17 +#define FMT_MULTI_TRACK 1 3.18 +#define FMT_MULTI_SEQ 2 3.19 + 3.20 +/* meta events */ 3.21 +#define META_SEQ 0 3.22 +#define META_TEXT 1 3.23 +#define META_COPYRIGHT 2 3.24 +#define META_NAME 3 3.25 +#define META_INSTR 4 3.26 +#define META_LYRICS 5 3.27 +#define META_MARKER 6 3.28 +#define META_CUE 7 3.29 +#define META_CHANPREFIX 32 3.30 +#define META_END_TRACK 47 3.31 +#define META_TEMPO 81 3.32 +#define META_SMPTE_OFFS 84 3.33 +#define META_TMSIG 88 3.34 +#define META_KEYSIG 89 3.35 +#define META_SPECIFIC 127 3.36 + 3.37 +struct midi { 3.38 + int bpm; 3.39 + 3.40 + int num_tracks; 3.41 + struct midi_track *tracks; 3.42 +}; 3.43 + 3.44 +struct midi_track { 3.45 + char *name; 3.46 + struct midi_event *head, *tail; 3.47 + int num_ev; 3.48 +}; 3.49 + 3.50 +struct midi_event { 3.51 + long dt; 3.52 + int type; 3.53 + int channel; 3.54 + int arg[2]; 3.55 + 3.56 + struct midi_event *next; 3.57 +}; 3.58 + 3.59 +#define CHUNK_HDR_SIZE 8 3.60 +struct chunk_hdr { 3.61 + char id[4]; 3.62 + uint32_t size; 3.63 + unsigned char data[1]; 3.64 +}; 3.65 + 3.66 +struct midi_hdr { 3.67 + uint16_t fmt; /* 0: single, 1: multi-track, 2: multiple independent */ 3.68 + uint16_t num_tracks; 3.69 + uint16_t tm_div; 3.70 + 3.71 +} __attribute__ ((packed)); 3.72 + 3.73 +static void destroy_track(struct midi_track *trk); 3.74 +static int read_track(struct midi *midi, struct chunk_hdr *chunk); 3.75 +static long read_vardata(unsigned char **pptr); 3.76 +static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr); 3.77 +static int read_sysex_event(struct midi *midi, unsigned char **pptr); 3.78 +static int ischunk(struct chunk_hdr *chunk, const char *name); 3.79 +static struct chunk_hdr *mkchunk(void *ptr); 3.80 +static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk); 3.81 +static struct midi_hdr *mkmidi(void *ptr); 3.82 +static void bigend(void *ptr, int sz); 3.83 +static void *map_file(const char *fname, int *size); 3.84 +static void unmap_file(void *mem, int size); 3.85 + 3.86 +#define IS_VALID_EVTYPE(x) ((x) >= MIDI_NOTE_OFF && (x) <= MIDI_PITCH_BEND) 3.87 + 3.88 +/* XXX the event arity table must match the MIDI_* defines in midi.h */ 3.89 +static int ev_arity[] = { 3.90 + 0, 0, 0, 0, 0, 0, 0, 0, 3.91 + 2, /* note off (note, velocity)*/ 3.92 + 2, /* note on (note, velocity)*/ 3.93 + 2, /* note aftertouch (note, aftertouch value) */ 3.94 + 2, /* controller (controller number, value) */ 3.95 + 1, /* prog change (prog number) */ 3.96 + 1, /* channel aftertouch (aftertouch value) */ 3.97 + 2 /* pitch bend (pitch LSB, pitch MSB) */ 3.98 +}; 3.99 + 3.100 + 3.101 +struct midi *load_midi(const char *fname) 3.102 +{ 3.103 + struct midi *midi; 3.104 + char *mem; 3.105 + int size; 3.106 + struct chunk_hdr *chunk; 3.107 + struct midi_hdr *hdr; 3.108 + 3.109 + if(!(mem = map_file(fname, &size))) { 3.110 + return 0; 3.111 + } 3.112 + chunk = mkchunk(mem); 3.113 + 3.114 + if(!ischunk(chunk, "MThd") || chunk->size != 6) { 3.115 + fprintf(stderr, "invalid or corrupted midi file: %s\n", fname); 3.116 + goto end; 3.117 + } 3.118 + hdr = mkmidi(chunk->data); 3.119 + 3.120 + printf("format: %d\n", (int)hdr->fmt); 3.121 + printf("tracks: %d\n", (int)hdr->num_tracks); 3.122 + 3.123 + if((hdr->tm_div & 0x8000) == 0) { 3.124 + /* division is in pulses / quarter note */ 3.125 + printf("time division: %d ppqn\n", (int)hdr->tm_div); 3.126 + } else { 3.127 + /* division in frames / sec */ 3.128 + int fps = (hdr->tm_div & 0x7f00) >> 8; 3.129 + int ticks_per_frame = hdr->tm_div & 0xff; 3.130 + printf("time division: %d fps, %d ticks/frame\n", fps, ticks_per_frame); 3.131 + } 3.132 + 3.133 + if(!(midi = malloc(sizeof *midi))) { 3.134 + perror("failed to allocate memory"); 3.135 + goto end; 3.136 + } 3.137 + if(!(midi->tracks = malloc(hdr->num_tracks * sizeof *midi->tracks))) { 3.138 + perror("failed to allocate memory"); 3.139 + goto end; 3.140 + } 3.141 + midi->num_tracks = 0; 3.142 + 3.143 + while((chunk = skip_chunk(chunk))) { 3.144 + if(ischunk(chunk, "MTrk")) { 3.145 + if(read_track(midi, chunk) == -1) { 3.146 + fprintf(stderr, "failed to read track\n"); 3.147 + } 3.148 + } else { 3.149 + printf("ignoring chunk: %c%c%c%c\n", chunk->id[0], chunk->id[1], chunk->id[2], chunk->id[3]); 3.150 + } 3.151 + } 3.152 + 3.153 +end: 3.154 + unmap_file(mem, size); 3.155 + if(midi) { 3.156 + free_midi(midi); 3.157 + midi = 0; 3.158 + } 3.159 + return midi; 3.160 +} 3.161 + 3.162 +void free_midi(struct midi *midi) 3.163 +{ 3.164 + int i; 3.165 + 3.166 + if(!midi) return; 3.167 + 3.168 + for(i=0; i<midi->num_tracks; i++) { 3.169 + destroy_track(midi->tracks + i); 3.170 + } 3.171 + 3.172 + free(midi->tracks); /* TODO free tracks properly */ 3.173 + free(midi); 3.174 +} 3.175 + 3.176 +static void destroy_track(struct midi_track *trk) 3.177 +{ 3.178 + free(trk->name); 3.179 + while(trk->head) { 3.180 + void *tmp = trk->head; 3.181 + trk->head = trk->head->next; 3.182 + free(tmp); 3.183 + } 3.184 +} 3.185 + 3.186 +static int read_track(struct midi *midi, struct chunk_hdr *chunk) 3.187 +{ 3.188 + unsigned char *ptr; 3.189 + struct midi_track trk = {0, 0, 0, 0}; 3.190 + 3.191 + if(!ischunk(chunk, "MTrk")) { 3.192 + return -1; 3.193 + } 3.194 + 3.195 + ptr = chunk->data; 3.196 + while(ptr < chunk->data + chunk->size) { 3.197 + long dt; 3.198 + unsigned char stat; 3.199 + 3.200 + /* TODO also convert dt to some standard unit */ 3.201 + dt = read_vardata(&ptr); 3.202 + stat = *ptr++; 3.203 + 3.204 + if(stat == 0xff) { 3.205 + read_meta_event(midi, &trk, &ptr); 3.206 + } else if(stat == 0xf0) { 3.207 + read_sysex_event(midi, &ptr); 3.208 + } else { 3.209 + struct midi_event *ev = malloc(sizeof *ev); 3.210 + 3.211 + if(trk.head) { 3.212 + trk.tail->next = ev; 3.213 + } else { 3.214 + trk.head = ev; 3.215 + } 3.216 + trk.tail = ev; 3.217 + ev->next = 0; 3.218 + trk.num_ev++; 3.219 + 3.220 + ev->dt = dt; 3.221 + ev->type = (stat >> 4) & 0xf; 3.222 + if(!IS_VALID_EVTYPE(ev->type)) { 3.223 + fprintf(stderr, "warning, skipping track with unknown event %d\n", ev->type); 3.224 + return -1; 3.225 + } 3.226 + ev->channel = stat & 0xf; 3.227 + 3.228 + ev->arg[0] = *ptr++; 3.229 + if(ev_arity[ev->type] > 1) { 3.230 + ev->arg[1] = *ptr++; 3.231 + } 3.232 + } 3.233 + } 3.234 + 3.235 + /* if we did actually add any events ... */ 3.236 + if(trk.num_ev) { 3.237 + midi->tracks[midi->num_tracks++] = trk; 3.238 + } 3.239 + return 0; 3.240 +} 3.241 + 3.242 +static long read_vardata(unsigned char **pptr) 3.243 +{ 3.244 + int i; 3.245 + long res = 0; 3.246 + unsigned char *ptr = *pptr; 3.247 + 3.248 + for(i=0; i<4; i++) { 3.249 + res |= (long)(*ptr & 0x7f) << (i * 8); 3.250 + 3.251 + /* if first bit is not set we're done */ 3.252 + if((*ptr++ & 0x80) == 0) 3.253 + break; 3.254 + } 3.255 + *pptr = ptr; 3.256 + return res; 3.257 +} 3.258 + 3.259 +static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr) 3.260 +{ 3.261 + unsigned char *ptr = *pptr; 3.262 + unsigned char type; 3.263 + long size; 3.264 + 3.265 + type = *ptr++; 3.266 + size = read_vardata(&ptr); 3.267 + 3.268 + switch(type) { 3.269 + case META_NAME: 3.270 + free(trk->name); 3.271 + trk->name = malloc(size + 1); 3.272 + memcpy(trk->name, ptr, size); 3.273 + trk->name[size] = 0; 3.274 + break; 3.275 + 3.276 + case META_TEMPO: 3.277 + /* TODO add a tempo change event to the midi struct */ 3.278 + break; 3.279 + 3.280 + default: 3.281 + break; 3.282 + } 3.283 + *pptr = ptr + size; 3.284 + return 0; 3.285 +} 3.286 + 3.287 +/* ignore sysex events */ 3.288 +static int read_sysex_event(struct midi *midi, unsigned char **pptr) 3.289 +{ 3.290 + long size = read_vardata(pptr); 3.291 + *pptr += size; 3.292 + return 0; 3.293 +} 3.294 + 3.295 +static int ischunk(struct chunk_hdr *chunk, const char *name) 3.296 +{ 3.297 + return memcmp(chunk->id, name, 4) == 0; 3.298 +} 3.299 + 3.300 +static struct chunk_hdr *mkchunk(void *ptr) 3.301 +{ 3.302 + struct chunk_hdr *chdr = ptr; 3.303 + bigend(&chdr->size, sizeof chdr->size); 3.304 + return chdr; 3.305 +} 3.306 + 3.307 +static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk) 3.308 +{ 3.309 + return mkchunk((char*)chunk + CHUNK_HDR_SIZE + chunk->size); 3.310 +} 3.311 + 3.312 +static struct midi_hdr *mkmidi(void *ptr) 3.313 +{ 3.314 + struct midi_hdr *midi = ptr; 3.315 + 3.316 + bigend(&midi->fmt, sizeof midi->fmt); 3.317 + bigend(&midi->num_tracks, sizeof midi->num_tracks); 3.318 + bigend(&midi->tm_div, sizeof midi->tm_div); 3.319 + return midi; 3.320 +} 3.321 + 3.322 +static void bigend(void *ptr, int sz) 3.323 +{ 3.324 + switch(sz) { 3.325 + case 4: 3.326 + *((uint32_t*)ptr) = ntohl(*(uint32_t*)ptr); 3.327 + break; 3.328 + 3.329 + case 2: 3.330 + *(uint16_t*)ptr = ntohs(*(uint16_t*)ptr); 3.331 + break; 3.332 + 3.333 + case 1: 3.334 + default: 3.335 + break; 3.336 + } 3.337 +} 3.338 + 3.339 +static void *map_file(const char *fname, int *size) 3.340 +{ 3.341 + int fd; 3.342 + struct stat st; 3.343 + void *mem; 3.344 + 3.345 + if((fd = open(fname, O_RDONLY)) == -1) { 3.346 + fprintf(stderr, "failed to open midi file: %s: %s\n", fname, strerror(errno)); 3.347 + return 0; 3.348 + } 3.349 + fstat(fd, &st); 3.350 + 3.351 + if((mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void*)-1) { 3.352 + fprintf(stderr, "failed to map midi file: %s: %s\n", fname, strerror(errno)); 3.353 + close(fd); 3.354 + return 0; 3.355 + } 3.356 + close(fd); 3.357 + 3.358 + *size = st.st_size; 3.359 + return mem; 3.360 +} 3.361 + 3.362 +static void unmap_file(void *mem, int size) 3.363 +{ 3.364 + munmap(mem, size); 3.365 +}
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/src/midi.h Thu Jan 26 00:31:39 2012 +0200 4.3 @@ -0,0 +1,29 @@ 4.4 +#ifndef MIDI_H_ 4.5 +#define MIDI_H_ 4.6 + 4.7 +#include <stdio.h> 4.8 + 4.9 +struct midi; 4.10 +struct midi_track; 4.11 +struct midi_event; 4.12 + 4.13 +#define MIDI_NOTE_OFF 8 4.14 +#define MIDI_NOTE_ON 9 4.15 +#define MIDI_NOTE_AFTERTOUCH 10 4.16 +#define MIDI_CONTROLLER 11 4.17 +#define MIDI_PROG_CHANGE 12 4.18 +#define MIDI_CHAN_AFTERTOUCH 13 4.19 +#define MIDI_PITCH_BEND 14 4.20 + 4.21 +#ifdef __cplusplus 4.22 +extern "C" { 4.23 +#endif 4.24 + 4.25 +struct midi *load_midi(const char *fname); 4.26 +void free_midi(struct midi *midi); 4.27 + 4.28 +#ifdef __cplusplus 4.29 +} 4.30 +#endif 4.31 + 4.32 +#endif /* MIDI_H_ */
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/test.c Thu Jan 26 00:31:39 2012 +0200 5.3 @@ -0,0 +1,19 @@ 5.4 +#include <stdio.h> 5.5 +#include "midi.h" 5.6 + 5.7 +int main(int argc, char **argv) 5.8 +{ 5.9 + struct midi *midi; 5.10 + 5.11 + if(!argv[1]) { 5.12 + fprintf(stderr, "pass the path to a midi file\n"); 5.13 + return 1; 5.14 + } 5.15 + 5.16 + if(!(midi = load_midi(argv[1]))) { 5.17 + return 1; 5.18 + } 5.19 + 5.20 + free_midi(midi); 5.21 + return 0; 5.22 +}