initial commit
authorJohn Tsiombikas <nuclear@member.fsf.org>
Thu, 16 Jun 2022 23:33:47 +0000 (02:33 +0300)
committerJohn Tsiombikas <nuclear@member.fsf.org>
Thu, 16 Jun 2022 23:33:47 +0000 (02:33 +0300)
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
src/midi.c [new file with mode: 0644]
src/midi.h [new file with mode: 0644]
test.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0932ecf
--- /dev/null
@@ -0,0 +1,4 @@
+*.o
+*.d
+*.swp
+test
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..60613d6
--- /dev/null
@@ -0,0 +1,372 @@
+#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);
+}
diff --git a/src/midi.h b/src/midi.h
new file mode 100644 (file)
index 0000000..4ad08a4
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef MIDI_H_
+#define MIDI_H_
+
+#include <stdio.h>
+
+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 (file)
index 0000000..c9d4e8b
--- /dev/null
+++ b/test.c
@@ -0,0 +1,19 @@
+#include <stdio.h>
+#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;
+}