--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include "midi.h"
+
+#define FMT_SINGLE 0
+#define FMT_MULTI_TRACK 1
+#define FMT_MULTI_SEQ 2
+
+/* meta events */
+#define META_SEQ 0
+#define META_TEXT 1
+#define META_COPYRIGHT 2
+#define META_NAME 3
+#define META_INSTR 4
+#define META_LYRICS 5
+#define META_MARKER 6
+#define META_CUE 7
+#define META_CHANPREFIX 32
+#define META_END_TRACK 47
+#define META_TEMPO 81
+#define META_SMPTE_OFFS 84
+#define META_TMSIG 88
+#define META_KEYSIG 89
+#define META_SPECIFIC 127
+
+struct midi {
+ int bpm;
+
+ int num_tracks;
+ struct midi_track *tracks;
+};
+
+struct midi_track {
+ char *name;
+ struct midi_event *head, *tail;
+ int num_ev;
+};
+
+struct midi_event {
+ long dt;
+ int type;
+ int channel;
+ int arg[2];
+
+ struct midi_event *next;
+};
+
+#define CHUNK_HDR_SIZE 8
+struct chunk_hdr {
+ char id[4];
+ uint32_t size;
+ unsigned char data[1];
+};
+
+struct midi_hdr {
+ uint16_t fmt; /* 0: single, 1: multi-track, 2: multiple independent */
+ uint16_t num_tracks;
+ uint16_t tm_div;
+
+} __attribute__ ((packed));
+
+static void destroy_track(struct midi_track *trk);
+static int read_track(struct midi *midi, struct chunk_hdr *chunk);
+static long read_vardata(unsigned char **pptr);
+static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr);
+static int read_sysex_event(struct midi *midi, unsigned char **pptr);
+static int ischunk(struct chunk_hdr *chunk, const char *name);
+static struct chunk_hdr *mkchunk(void *ptr);
+static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk);
+static struct midi_hdr *mkmidi(void *ptr);
+static void bigend(void *ptr, int sz);
+static void *map_file(const char *fname, int *size);
+static void unmap_file(void *mem, int size);
+
+#define IS_VALID_EVTYPE(x) ((x) >= MIDI_NOTE_OFF && (x) <= MIDI_PITCH_BEND)
+
+/* XXX the event arity table must match the MIDI_* defines in midi.h */
+static int ev_arity[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 2, /* note off (note, velocity)*/
+ 2, /* note on (note, velocity)*/
+ 2, /* note aftertouch (note, aftertouch value) */
+ 2, /* controller (controller number, value) */
+ 1, /* prog change (prog number) */
+ 1, /* channel aftertouch (aftertouch value) */
+ 2 /* pitch bend (pitch LSB, pitch MSB) */
+};
+
+
+struct midi *load_midi(const char *fname)
+{
+ struct midi *midi;
+ char *mem;
+ int size;
+ struct chunk_hdr *chunk;
+ struct midi_hdr *hdr;
+
+ if(!(mem = map_file(fname, &size))) {
+ return 0;
+ }
+ chunk = mkchunk(mem);
+
+ if(!ischunk(chunk, "MThd") || chunk->size != 6) {
+ fprintf(stderr, "invalid or corrupted midi file: %s\n", fname);
+ goto end;
+ }
+ hdr = mkmidi(chunk->data);
+
+ printf("format: %d\n", (int)hdr->fmt);
+ printf("tracks: %d\n", (int)hdr->num_tracks);
+
+ if((hdr->tm_div & 0x8000) == 0) {
+ /* division is in pulses / quarter note */
+ printf("time division: %d ppqn\n", (int)hdr->tm_div);
+ } else {
+ /* division in frames / sec */
+ int fps = (hdr->tm_div & 0x7f00) >> 8;
+ int ticks_per_frame = hdr->tm_div & 0xff;
+ printf("time division: %d fps, %d ticks/frame\n", fps, ticks_per_frame);
+ }
+
+ if(!(midi = malloc(sizeof *midi))) {
+ perror("failed to allocate memory");
+ goto end;
+ }
+ if(!(midi->tracks = malloc(hdr->num_tracks * sizeof *midi->tracks))) {
+ perror("failed to allocate memory");
+ goto end;
+ }
+ midi->num_tracks = 0;
+
+ while((chunk = skip_chunk(chunk)) && ((char*)chunk < mem + size)) {
+ if(ischunk(chunk, "MTrk")) {
+ if(read_track(midi, chunk) == -1) {
+ fprintf(stderr, "failed to read track\n");
+ }
+ } else {
+ printf("ignoring chunk: %c%c%c%c\n", chunk->id[0], chunk->id[1], chunk->id[2], chunk->id[3]);
+ }
+ }
+
+end:
+ unmap_file(mem, size);
+ if(midi) {
+ free_midi(midi);
+ midi = 0;
+ }
+ return midi;
+}
+
+void free_midi(struct midi *midi)
+{
+ int i;
+
+ if(!midi) return;
+
+ for(i=0; i<midi->num_tracks; i++) {
+ destroy_track(midi->tracks + i);
+ }
+
+ free(midi->tracks); /* TODO free tracks properly */
+ free(midi);
+}
+
+static void destroy_track(struct midi_track *trk)
+{
+ free(trk->name);
+ while(trk->head) {
+ void *tmp = trk->head;
+ trk->head = trk->head->next;
+ free(tmp);
+ }
+}
+
+static int read_track(struct midi *midi, struct chunk_hdr *chunk)
+{
+ unsigned char *ptr;
+ struct midi_track trk = {0, 0, 0, 0};
+ unsigned char prev_stat = 0;
+
+ if(!ischunk(chunk, "MTrk")) {
+ return -1;
+ }
+
+ ptr = chunk->data;
+ while(ptr < chunk->data + chunk->size) {
+ long dt;
+ unsigned char stat;
+
+ /* TODO also convert dt to some standard unit */
+ dt = read_vardata(&ptr);
+ stat = *ptr++;
+
+ if(stat == 0xff) {
+ read_meta_event(midi, &trk, &ptr);
+ } else if(stat == 0xf0) {
+ read_sysex_event(midi, &ptr);
+ } else {
+ struct midi_event *ev = malloc(sizeof *ev);
+
+ if(trk.head) {
+ trk.tail->next = ev;
+ } else {
+ trk.head = ev;
+ }
+ trk.tail = ev;
+ ev->next = 0;
+ trk.num_ev++;
+
+ if(!(stat & 0x80)) {
+ /* not a status byte, assume running status */
+ stat = prev_stat;
+ ptr--;
+ }
+
+ ev->dt = dt;
+ ev->type = (stat >> 4) & 0xf;
+ if(!IS_VALID_EVTYPE(ev->type)) {
+ fprintf(stderr, "warning, skipping track with unknown event %d\n", ev->type);
+ return -1;
+ }
+ ev->channel = stat & 0xf;
+
+ ev->arg[0] = *ptr++;
+ if(ev_arity[ev->type] > 1) {
+ ev->arg[1] = *ptr++;
+ }
+
+ prev_stat = stat;
+ }
+ }
+
+ /* if we did actually add any events ... */
+ if(trk.num_ev) {
+ midi->tracks[midi->num_tracks++] = trk;
+ printf("loaded track with %d events\n", trk.num_ev);
+ }
+ return 0;
+}
+
+static long read_vardata(unsigned char **pptr)
+{
+ int i;
+ long res = 0;
+ unsigned char *ptr = *pptr;
+
+ for(i=0; i<4; i++) {
+ res |= (long)(*ptr & 0x7f) << (i * 8);
+
+ /* if first bit is not set we're done */
+ if((*ptr++ & 0x80) == 0)
+ break;
+ }
+ *pptr = ptr;
+ return res;
+}
+
+static int read_meta_event(struct midi *midi, struct midi_track *trk, unsigned char **pptr)
+{
+ unsigned char *ptr = *pptr;
+ unsigned char type;
+ long size;
+
+ type = *ptr++;
+ size = read_vardata(&ptr);
+
+ switch(type) {
+ case META_NAME:
+ free(trk->name);
+ trk->name = malloc(size + 1);
+ memcpy(trk->name, ptr, size);
+ trk->name[size] = 0;
+ break;
+
+ case META_TEMPO:
+ /* TODO add a tempo change event to the midi struct */
+ break;
+
+ default:
+ break;
+ }
+ *pptr = ptr + size;
+ return 0;
+}
+
+/* ignore sysex events */
+static int read_sysex_event(struct midi *midi, unsigned char **pptr)
+{
+ long size = read_vardata(pptr);
+ *pptr += size;
+ return 0;
+}
+
+static int ischunk(struct chunk_hdr *chunk, const char *name)
+{
+ return memcmp(chunk->id, name, 4) == 0;
+}
+
+static struct chunk_hdr *mkchunk(void *ptr)
+{
+ struct chunk_hdr *chdr = ptr;
+ bigend(&chdr->size, sizeof chdr->size);
+ return chdr;
+}
+
+static struct chunk_hdr *skip_chunk(struct chunk_hdr *chunk)
+{
+ return mkchunk((char*)chunk + CHUNK_HDR_SIZE + chunk->size);
+}
+
+static struct midi_hdr *mkmidi(void *ptr)
+{
+ struct midi_hdr *midi = ptr;
+
+ bigend(&midi->fmt, sizeof midi->fmt);
+ bigend(&midi->num_tracks, sizeof midi->num_tracks);
+ bigend(&midi->tm_div, sizeof midi->tm_div);
+ return midi;
+}
+
+static void bigend(void *ptr, int sz)
+{
+ switch(sz) {
+ case 4:
+ *((uint32_t*)ptr) = ntohl(*(uint32_t*)ptr);
+ break;
+
+ case 2:
+ *(uint16_t*)ptr = ntohs(*(uint16_t*)ptr);
+ break;
+
+ case 1:
+ default:
+ break;
+ }
+}
+
+static void *map_file(const char *fname, int *size)
+{
+ int fd;
+ struct stat st;
+ void *mem;
+
+ if((fd = open(fname, O_RDONLY)) == -1) {
+ fprintf(stderr, "failed to open midi file: %s: %s\n", fname, strerror(errno));
+ return 0;
+ }
+ fstat(fd, &st);
+
+ if((mem = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) == (void*)-1) {
+ fprintf(stderr, "failed to map midi file: %s: %s\n", fname, strerror(errno));
+ close(fd);
+ return 0;
+ }
+ close(fd);
+
+ *size = st.st_size;
+ return mem;
+}
+
+static void unmap_file(void *mem, int size)
+{
+ munmap(mem, size);
+}