From 613c5581edee6684543d853d8e88045fb15410f5 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Fri, 17 Jun 2022 02:33:47 +0300 Subject: [PATCH] initial commit --- .gitignore | 4 + Makefile | 19 ++++ src/midi.c | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/midi.h | 29 +++++ test.c | 19 ++++ 5 files changed, 443 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 src/midi.c create mode 100644 src/midi.h create mode 100644 test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0932ecf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.d +*.swp +test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fe5af39 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +src = $(wildcard src/*.c) test.c +obj = $(src:.c=.o) +dep = $(obj:.o=.d) +bin = test + +CFLAGS = -pedantic -Wall -g -Isrc -MMD + +$(bin): $(obj) + $(CC) -o $@ $(obj) $(LDFLAGS) + +-include $(dep) + +.PHONY: clean +clean: + rm -f $(obj) $(bin) + +.PHONY: cleandep +cleandep: + rm -f $(dep) diff --git a/src/midi.c b/src/midi.c new file mode 100644 index 0000000..60613d6 --- /dev/null +++ b/src/midi.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; inum_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); +} diff --git a/src/midi.h b/src/midi.h new file mode 100644 index 0000000..4ad08a4 --- /dev/null +++ b/src/midi.h @@ -0,0 +1,29 @@ +#ifndef MIDI_H_ +#define MIDI_H_ + +#include + +struct midi; +struct midi_track; +struct midi_event; + +#define MIDI_NOTE_OFF 8 +#define MIDI_NOTE_ON 9 +#define MIDI_NOTE_AFTERTOUCH 10 +#define MIDI_CONTROLLER 11 +#define MIDI_PROG_CHANGE 12 +#define MIDI_CHAN_AFTERTOUCH 13 +#define MIDI_PITCH_BEND 14 + +#ifdef __cplusplus +extern "C" { +#endif + +struct midi *load_midi(const char *fname); +void free_midi(struct midi *midi); + +#ifdef __cplusplus +} +#endif + +#endif /* MIDI_H_ */ diff --git a/test.c b/test.c new file mode 100644 index 0000000..c9d4e8b --- /dev/null +++ b/test.c @@ -0,0 +1,19 @@ +#include +#include "midi.h" + +int main(int argc, char **argv) +{ + struct midi *midi; + + if(!argv[1]) { + fprintf(stderr, "pass the path to a midi file\n"); + return 1; + } + + if(!(midi = load_midi(argv[1]))) { + return 1; + } + + free_midi(midi); + return 0; +} -- 1.7.10.4