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