From 553a02cc25eb2c9646348ce8fec94c491d777bde Mon Sep 17 00:00:00 2001 From: Lauchmelder Date: Sat, 9 Apr 2022 19:08:28 +0200 Subject: [PATCH] minor changes --- src/main.c | 76 ++- src/midi_interface.c | 5 + src/midi_interface.h | 1 + src/midi_parser.c | 1074 ++++++++++++++++++++++++++++++++++++++++++ src/midi_parser.h | 91 ++++ 5 files changed, 1239 insertions(+), 8 deletions(-) create mode 100644 src/midi_parser.c create mode 100644 src/midi_parser.h diff --git a/src/main.c b/src/main.c index 1171032..1ae0c82 100644 --- a/src/main.c +++ b/src/main.c @@ -9,13 +9,11 @@ #include #include "midi_interface.h" +#include "midi_parser.h" -typedef struct Note -{ - unsigned char channel; - unsigned char pitch; - unsigned char velocity; -} Note; +uint32_t time_per_quarter; +uint32_t ticks_per_quarter; +MidiInterface* interface; typedef struct Offset { @@ -53,16 +51,37 @@ static const Offset pattern[] = { static int _Atomic stop = ATOMIC_VAR_INIT(0); static int _Atomic step = ATOMIC_VAR_INIT(0); static Note* _Atomic note = ATOMIC_VAR_INIT(NULL); +static int _Atomic playing = ATOMIC_VAR_INIT(0); -void *arpeggio_loop(void* data); +void* arpeggio_loop(void* data); +void* play_track(void* data); int main(int argc, char** argv) { - MidiInterface* interface; + MidiParser* parser; + parser = parseMidi("out/pushing_onwards.mid", false, true); + if(!parser) + { + fprintf(stderr, "Failed to read MIDI file\n"); + exit(-1); + } + + time_per_quarter = ((uint32_t*)(parser->tracks[0].events[2].infos))[0]; + ticks_per_quarter = parser->ticks; + int result = open_midi_device(&interface, NULL); if(result < 0) exit(result); + pthread_t* threads = (pthread_t*)malloc(parser->nbOfTracks * sizeof(pthread_t)); + for(int i = 0; i < parser->nbOfTracks; i++) + { + pthread_create(threads + i, NULL, play_track, parser->tracks + i); + } + + playing = 1; + + /* int key_down = 0; Note current_note; @@ -107,7 +126,15 @@ int main(int argc, char** argv) pthread_join(arpeggio_thread, NULL); free_message(message); + */ + for(int i = 0; i < parser->nbOfTracks; i++) + { + pthread_join(threads[i], NULL); + } + free(threads); close_midi_device(interface); + + return 0; } @@ -140,4 +167,37 @@ void *arpeggio_loop(void* data) } free_message(message); +} + +void* play_track(void* data) +{ + Track* track = (Track*)data; + Message* message; + create_message(&message); + message->channel = 0; + message->length = 2; + + int current_event = 0; + + while(current_event < track->nbOfEvents) + { + if(!playing) + continue; + + Event event = track->events[current_event]; + usleep(event.timeToAppear * time_per_quarter / ticks_per_quarter); + + if(event.type == MidiNotePressed || event.type == MidiNoteReleased) + { + message->type = (event.type == MidiNotePressed) ? NOTE_ON : NOTE_OFF; + message->data = event.infos + 1; + + // printf("Sending %d %d\n", message->data[0], message->data[1]); + int result = write_midi_device(interface, message); + if(result < 0) + fprintf(stderr, "Write err: %s\n", midi_strerror(result)); + } + + current_event++; + } } \ No newline at end of file diff --git a/src/midi_interface.c b/src/midi_interface.c index 4bb6555..d679e7f 100644 --- a/src/midi_interface.c +++ b/src/midi_interface.c @@ -94,6 +94,11 @@ int write_midi_device(MidiInterface* interface, const Message* buffer) return MIDI_WRITE_ERROR; } +int write_midi_device_raw(MidiInterface* interface, const char* buffer) +{ + // +} + int try_open_device(const char* device) { int fd = open(device, O_RDWR, 0); diff --git a/src/midi_interface.h b/src/midi_interface.h index 2775fa1..dd1ef46 100644 --- a/src/midi_interface.h +++ b/src/midi_interface.h @@ -17,6 +17,7 @@ int close_midi_device(MidiInterface* interface); int read_midi_device(MidiInterface* interface, Message* buffer); int write_midi_device(MidiInterface* interface, const Message* buffer); +int write_midi_device_raw(MidiInterface* interface, const char* buffer); #endif \ No newline at end of file diff --git a/src/midi_parser.c b/src/midi_parser.c new file mode 100644 index 0000000..f8fbc8a --- /dev/null +++ b/src/midi_parser.c @@ -0,0 +1,1074 @@ +/** + * https://github.com/Gegel85/midi_parser + */ + +#include +#include +#include +#include +#include +#include +#include "midi_parser.h" + +bool addNode(NoteList *list, Note *data) +{ + if (!data) + return (false); + for (; list->note && list->next; list = list->next); + if (list->note) { + list->next = malloc(sizeof(*list->next)); + if (!list->next) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(*list->next)); + return (false); + } + list->next->prev = list; + list->next->next = NULL; + list = list->next; + } + list->note = data; + return (true); +} + +void deleteNode(NoteList *node) +{ + if (node->prev) { + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + free(node); + } else if (node->next) { + node->note = node->next->note; + node = node->next; + node->prev->next = node->next; + if (node->next) + node->next->prev = node->prev; + free(node); + } else + node->note = NULL; +} + +char *getNoteString(char note) +{ + static char buffer[5]; + int nbr; + + memset(buffer, 0, sizeof(buffer)); + switch (note % 12) { + case 0: + buffer[0] = 'C'; + nbr = 1; + break; + case 1: + buffer[0] = 'C'; + buffer[1] = '#'; + nbr = 2; + break; + case 2: + buffer[0] = 'D'; + nbr = 1; + break; + case 3: + buffer[0] = 'D'; + buffer[1] = '#'; + nbr = 2; + break; + case 4: + buffer[0] = 'E'; + nbr = 1; + break; + case 5: + buffer[0] = 'F'; + nbr = 1; + break; + case 6: + buffer[0] = 'F'; + buffer[1] = '#'; + nbr = 2; + break; + case 7: + buffer[0] = 'G'; + nbr = 1; + break; + case 8: + buffer[0] = 'G'; + buffer[1] = '#'; + nbr = 2; + break; + case 9: + buffer[0] = 'A'; + nbr = 1; + break; + case 10: + buffer[0] = 'A'; + buffer[1] = '#'; + nbr = 2; + break; + case 11: + buffer[0] = 'B'; + nbr = 1; + break; + } + sprintf(buffer + nbr, "%i", note / 12 - 1); + return (buffer); +} + +void deleteTrack(Track *track) +{ + free(track->copyright); + free(track->name); + free(track->instrumentName); + for (int i = 0; i < track->nbOfEvents && track->events; i++) + free(track->events[i].infos); + free(track->notes); + free(track->events); +} + +void deleteMidiParserStruct(MidiParser *result) +{ + for (int i = 0; i < result->nbOfTracks; i++) + deleteTrack(&result->tracks[i]); + free(result->tracks); +} + +int readVarLenInt(int fd, int *pos) +{ + char buff = 0; + int result = 0; + int count = 0; + + do { + if (count++ == 4 || read(fd, &buff, 1) <= 0) { + *pos = -1; + return (0); + } + result = (result << 7) + (buff & 0x7F); + (*pos)++; + } while (buff & 0x80); + return (result); +} + +char *readString(int fd, int len) +{ + char *buffer = malloc(len + 1); + + if (!buffer) { + printf("Error: Couldn't alloc %iB\n", len + 1); + return (NULL); + } else if (read(fd, buffer, len) != len) { + free(buffer); + return (NULL); + } + buffer[len] = 0; + return (buffer); +} + +unsigned char readSingleByte(int fd, int *i) +{ + char byte; + + if (read(fd, &byte, 1) == 1) { + (*i)++; + return (byte); + } + *i = -1; + return (0); +} + +void showChunk(unsigned char *buffer, int pos, int len, int posInFile) +{ + int realPos = pos - 1; + + posInFile = pos > 15 ? posInFile - 15 : posInFile - pos; + pos = pos > 15 ? pos - 15 : 0; + for (int j = 0; j < 10 - (posInFile % 10); j++) + printf(" "); + for (int i = 10 - posInFile % 10; i < 40 && pos + i < len; i += 10) { + for (int j = printf("%i", posInFile + i); j < 50; j++) + printf(" "); + } + printf("\n"); + for (int i = 0; i < 40 && pos + i < len; i++) + printf(i + pos == realPos ? " V " : ((i + posInFile) % 10 ? " ' " : " | ")); + printf("\n"); + for (int i = 0; i < 40 && pos + i < len; i++) + for (int j = printf(buffer[i + pos] ? " %#x" : " 0x0", buffer[i + pos]); j < 5; j++) + printf(" "); + printf("\n"); +} + +bool parseMidiTrack(unsigned char *buffer, int buffLen, Track *track, bool outputDebug, MidiParser *result, int posInFile, bool createNoteArray) +{ + void *buff; + unsigned char byte; + unsigned char statusByte; + unsigned int deltaTime = 0; + unsigned long totalTime = 0; + unsigned int len; + NoteList list = {NULL, NULL, NULL}; + NoteList *node; + Note *noteBuffer; + Event *currentEvent = track->events; + int i = 0; + int currentNote = 0; + int currentEventId = 0; + + for (; i != -1 && i < buffLen; ) { + ++track->nbOfEvents; + for (deltaTime = buffer[i] & 0x7F; buffer[i++] & 0x80; deltaTime = (deltaTime << 7) + (buffer[i] & 0x7F)); + byte = buffer[i]; + + if (byte & 0x80) { + statusByte = byte; + i++; + } + if (outputDebug) + printf("After % 8i ticks: ", deltaTime); + if (statusByte == 0xFF) { + switch (buffer[i++]) { + case 0x00: + if (buffer[i++] != 0x02) { + printf("Error: Invalid byte found (%#x found but expected 0x02)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (totalTime > 0) { + printf("Error: Cannot add sequence number after non-zero delta times\n"); + return (false); + } + i += 2; + break; + case 0x01: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + break; + case 0x02: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + track->nbOfEvents--; + break; + case 0x03: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + track->nbOfEvents--; + break; + case 0x04: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + track->nbOfEvents--; + break; + case 0x05: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + break; + case 0x06: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + break; + case 0x07: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + break; + case 0x20: + if (buffer[i++] != 0x01) { + printf("Error: Invalid byte found (%#x found but expected 0x01)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + i++; + break; + case 0x21: + if (buffer[i++] != 0x01) { + printf("Error: Invalid byte found (%#x found but expected 0x01)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + i++; + break; + case 0X2F: + if (buffer[i++] != 0x00) { + printf("Error: Invalid byte found (%#x found but expected 0x00)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (i != buffLen) { + printf("Error: Found end of track %s the last index (Found at index %i but expected %i)\n", i > buffLen ? "after" : "before", i, buffLen); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + i = -1; + break; + case 0x51: + if (buffer[i++] != 0x03) { + printf("Error: Invalid byte found (%#x found but expected 0x03)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + i += 3; + break; + case 0x54: + if (buffer[i++] != 0x05) { + printf("Error: Invalid byte found (%#x found but expected 0x05)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (totalTime > 0) { + printf("Error: Cannot add SMTPE Offset after non-zero delta times\n"); + return (false); + } + i += 5; + break; + case 0x58: + if (buffer[i++] != 0x04) { + printf("Error: Invalid byte found (%#x found but expected 0x04)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + i += 4; + break; + case 0x59: + if (buffer[i++] != 0x02) { + printf("Error: Invalid byte found (%#x found but expected 0x02)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + i += 2; + break; + case 0x7F: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + break; + default: + if (outputDebug) + printf("Error: Invalid meta event type (%#x)\n", buffer[i - 1]); + return (false); + } + } else if (statusByte >= 0x80 && statusByte < 0x90) { + if (buffer[i++] > 127) { + printf("Error: Note out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (buffer[i++] > 127) { + printf("Error: Velocity out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + + } else if (statusByte >= 0x90 && statusByte < 0xA0) { + if (buffer[i++] > 127) { + printf("Error: Note out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (buffer[i++] > 127) { + printf("Error: Velocity out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + + result->nbOfNotes++; + track->nbOfNotes++; + } else if (statusByte >= 0xA0 && statusByte < 0xB0) { + if (buffer[i++] > 127) { + printf("Error: Note out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (buffer[i++] > 127) { + printf("Error: Velocity out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + } else if (statusByte >= 0xB0 && statusByte < 0xC0) { + if (buffer[i++] > 127) { + printf("Error: Controller out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (buffer[i++] > 127) { + printf("Error: Value out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + } else if (statusByte >= 0xC0 && statusByte < 0xD0) { + if (buffer[i++] > 127) { + printf("Error: Program out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + } else if (statusByte >= 0xD0 && statusByte < 0xE0) { + if (buffer[i++] > 127) { + printf("Error: Pressure out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + } else if (statusByte >= 0xE0 && statusByte < 0xF0) { + if (buffer[i++] > 127) { + printf("Error: Lsb out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } else if (buffer[i++] > 127) { + printf("Error: Msb out of range (%i out of range 0-127)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + return (false); + } + } else if (statusByte == 0xF0 || statusByte == 0xF7) { + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + } else { + printf("Warning: Unsupported event (status byte: %#x, delta time: %u) (At pos %i)\n", statusByte, deltaTime, i + posInFile); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + i++; + } + if (outputDebug) + printf(" New position: %i\n", i + posInFile); + totalTime += deltaTime; + } + if (outputDebug) + printf("Begin of the track: %i\n", posInFile); + track->events = malloc(sizeof(*track->events) * track->nbOfEvents); + if (!track->events) { + printf("Error: Cannot alloc %iB\n", (int)(sizeof(*track->events) * track->nbOfEvents)); + return (false); + } + memset(track->events, 0, sizeof(*track->events) * track->nbOfEvents); + if (createNoteArray) { + track->notes = malloc(sizeof(*track->notes) * track->nbOfNotes); + if (!track->notes) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(*track->notes) * track->nbOfNotes); + return (false); + } + memset(track->notes, 0, sizeof(*track->notes) * track->nbOfNotes); + } + totalTime = 0; + for (i = 0; i < buffLen; ) { + for (deltaTime = buffer[i] & 0x7F; buffer[i++] & 0x80; deltaTime = (deltaTime << 7) + (buffer[i] & 0x7F)); + for (node = &list; node; node = node->next) + if (node->note) + node->note->duration += deltaTime; + byte = buffer[i]; + + if (byte & 0x80) { + statusByte = byte; + i++; + } + if (outputDebug) + printf("After % 8i ticks: ", deltaTime); + if (statusByte == 0xFF) { + switch (buffer[i++]) { + case 0x00: + i++; + buff = malloc(sizeof(int)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(int)); + while (list.note) + deleteNode(&list); + return (false); + } + *(int *)buff = (buffer[i++] << 8) + buffer[i++]; + if (outputDebug) + printf("Found sequence number: %i", *(int *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiSequenceNumber; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0x01: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("Text event: '%s'", (char *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiTextEvent; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + i += len; + break; + case 0x02: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("Copyrights to: '%s'", (char *)buff); + free(track->copyright); + track->copyright = buff; + i += len; + break; + case 0x03: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("Track name: '%s'", (char *)buff); + free(track->name); + track->name = buff; + i += len; + break; + case 0x04: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("Instrument name: '%s'", (char *)buff); + free(track->instrumentName); + track->instrumentName = buff; + i += len; + break; + case 0x05: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("Lyric event: '%s'", (char *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNewLyric; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + i += len; + break; + case 0x06: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("Marker: '%s'", (char *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNewMarker; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + i += len; + break; + case 0x07: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) + printf("New cue point: '%s'", (char *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNewCuePoint; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + i += len; + break; + case 0x20: + i++; + buff = malloc(sizeof(char)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char)); + while (list.note) + deleteNode(&list); + return (false); + } + *(char *)buff = buffer[i++]; + if (outputDebug) + printf("New channel prefix: %i", *(int *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNewChannelPrefix; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0x21: + i++; + buff = malloc(sizeof(char)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char)); + while (list.note) + deleteNode(&list); + return (false); + } + *(char *)buff = buffer[i++]; + if (outputDebug) + printf("Midi port changed: %i", *(int *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiPortChange; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0X2F: + if (buffer[i++] != 0x00) { + printf("Error: Invalid byte found (%#x found but expected 0x00)\n", buffer[i - 1]); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + while (list.note) + deleteNode(&list); + return (false); + } else if (i != buffLen) { + printf("Error: Found end of track %s the last index (Found at index %i but expected %i)\n", i > buffLen ? "after" : "before", i, buffLen); + if (outputDebug) + showChunk(buffer, i, buffLen, posInFile + i); + while (list.note) + deleteNode(&list); + return (false); + } + if (outputDebug) + printf("End of track !\n"); + while (list.note) + deleteNode(&list); + return (true); + case 0x51: + i++; + buff = malloc(sizeof(int)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(int)); + while (list.note) + deleteNode(&list); + return (false); + } + *(int *)buff = (buffer[i++] << 16) + (buffer[i++] << 8) + buffer[i++]; + if (outputDebug) + printf("Tempo changed: %i", *(int *)buff); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiTempoChanged; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0x54: + i++; + buff = malloc(sizeof(char) * 5); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 5); + while (list.note) + deleteNode(&list); + return (false); + } + for (int j = 0; j < 5; j++) + ((char *)buff)[j] = buffer[i++]; + if (outputDebug) + printf( + "New offset: %ih %im %is %iframes %ihundreths of a frame", + ((char *)buff)[0], + ((char *)buff)[1], + ((char *)buff)[2], + ((char *)buff)[3], + ((char *)buff)[4] + ); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiSMTPEOffset; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0x58: + i++; + buff = malloc(sizeof(char) * 4); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 4); + while (list.note) + deleteNode(&list); + return (false); + } + for (int j = 0; j < 4; j++) + ((char *)buff)[j] = buffer[i++]; + if (outputDebug) + printf( + "Tempo infos: time signature %i/%i 1/4 note is %i ticks %i", + ((unsigned char *)buff)[0], + ((unsigned char *)buff)[1], + ((unsigned char *)buff)[2], + ((unsigned char *)buff)[3] + ); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNewTimeSignature; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0x59: + i++; + buff = malloc(sizeof(char) * 2); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 2); + while (list.note) + deleteNode(&list); + return (false); + } + for (int j = 0; j < 2; j++) + ((char *)buff)[j] = buffer[i++]; + if (outputDebug) + printf( + "Key signature %i %i", + ((char *)buff)[0], + ((char *)buff)[1] + ); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNewKeySignature; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + break; + case 0x7F: + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + buff = malloc(len + 1); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)len + 1); + while (list.note) + deleteNode(&list); + return (false); + } + strncpy(buff, (char *)&buffer[i], len); + ((char *)buff)[len] = 0; + if (outputDebug) { + printf("Sequencer-Specific Meta-event: '"); + fflush(stdout); + write(1, buff, len); + printf("'"); + } + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiSequencerSpecificEvent; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + i += len; + break; + } + } else if (statusByte >= 0x80 && statusByte < 0x90) { + if (createNoteArray) { + for (node = &list; node && (!node->note || node->note->pitch != buffer[i] || node->note->channel != statusByte - 0x80); node = node->next); + if (!node) { + printf("Error: Note %s from channel %i is released but has never been pressed\n", getNoteString(buffer[i]), statusByte - 0x80); + while (list.note) + deleteNode(&list); + return (false); + } + node->note->fadeOutVelocity = buffer[i + 1]; + node->note = NULL; + } + buff = malloc(sizeof(MidiNote)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(MidiNote)); + while (list.note) + deleteNode(&list); + return (false); + } + ((MidiNote *)buff)->channel = statusByte - 0x80; + ((MidiNote *)buff)->pitch = buffer[i++]; + ((MidiNote *)buff)->velocity = buffer[i++]; + if (outputDebug) + printf("%s off in channel %i (velocity: %i)", getNoteString(((MidiNote *)buff)->pitch), ((MidiNote *)buff)->channel, ((MidiNote *)buff)->velocity); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNoteReleased; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + } else if (statusByte >= 0x90 && statusByte < 0xA0) { + if (createNoteArray) { + noteBuffer = &track->notes[currentNote++]; + noteBuffer->channel = statusByte - 0x90; + noteBuffer->timeBeforeAppear = totalTime + deltaTime; + noteBuffer->pitch = buffer[i]; + noteBuffer->velocity = buffer[i + 1]; + addNode(&list, noteBuffer); + } + buff = malloc(sizeof(MidiNote)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(MidiNote)); + while (list.note) + deleteNode(&list); + return (false); + } + ((MidiNote *)buff)->channel = statusByte - 0x90; + ((MidiNote *)buff)->pitch = buffer[i++]; + ((MidiNote *)buff)->velocity = buffer[i++]; + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiNotePressed; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + if (outputDebug) + printf("%s on in channel %i (velocity: %i)", getNoteString(((MidiNote *)buff)->pitch), ((MidiNote *)buff)->channel, ((MidiNote *)buff)->velocity); + } else if (statusByte >= 0xA0 && statusByte < 0xB0) { + buff = malloc(sizeof(MidiNote)); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(MidiNote)); + while (list.note) + deleteNode(&list); + return (false); + } + ((MidiNote *)buff)->channel = statusByte - 0xA0; + ((MidiNote *)buff)->pitch = buffer[i++]; + ((MidiNote *)buff)->velocity = buffer[i++]; + if (outputDebug) + printf("Polyphonic pressure on note %s in channel %i (velocity: %i)", getNoteString(((MidiNote *)buff)->pitch), ((MidiNote *)buff)->channel, ((MidiNote *)buff)->velocity); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiPolyphonicPressure; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + } else if (statusByte >= 0xB0 && statusByte < 0xC0) { + buff = malloc(sizeof(char) * 3); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 3); + while (list.note) + deleteNode(&list); + return (false); + } + ((char *)buff)[0] = statusByte - 0xB0; + ((char *)buff)[1] = buffer[i++]; + ((char *)buff)[2] = buffer[i++]; + if (outputDebug) + printf("Controller %i in channel %i is now at value %i", ((unsigned char *)buff)[1], ((unsigned char *)buff)[0], ((unsigned char *)buff)[2]); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiControllerValueChanged; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + } else if (statusByte >= 0xC0 && statusByte < 0xD0) { + buff = malloc(sizeof(char) * 2); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 2); + while (list.note) + deleteNode(&list); + return (false); + } + ((char *)buff)[0] = statusByte - 0xC0; + ((char *)buff)[1] = buffer[i++]; + if (outputDebug) + printf("Changed program of channel %i to %i", ((unsigned char *)buff)[0], ((unsigned char *)buff)[1]); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiProgramChanged; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + } else if (statusByte >= 0xD0 && statusByte < 0xE0) { + buff = malloc(sizeof(char) * 2); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 2); + while (list.note) + deleteNode(&list); + return (false); + } + ((char *)buff)[0] = statusByte - 0xD0; + ((char *)buff)[1] = buffer[i++]; + if (outputDebug) + printf("Changed pressure of all note in channel %i to %i", ((unsigned char *)buff)[0], ((unsigned char *)buff)[1]); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiPressureOfChannelChanged; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + } else if (statusByte >= 0xE0 && statusByte < 0xF0) { + buff = malloc(sizeof(char) * 3); + if (!buff) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(char) * 3); + while (list.note) + deleteNode(&list); + return (false); + } + ((char *)buff)[0] = statusByte - 0xE0; + ((char *)buff)[1] = buffer[i++]; + ((char *)buff)[2] = buffer[i++]; + if (outputDebug) + printf("Changed pitch bend of all note in channel %i to %i %i", ((char *)buff)[0], ((unsigned char *)buff)[1], ((unsigned char *)buff)[2]); + currentEvent = &track->events[currentEventId++]; + currentEvent->type = MidiPitchBendChanged; + currentEvent->infos = buff; + currentEvent->timeToAppear = deltaTime; + } else if (statusByte == 0xF0 || statusByte == 0xF7) { + for (len = buffer[i] & 0x7F; buffer[i++] & 0x80; len = (len << 7) + (buffer[i] & 0x7F)); + i += len; + } else { + printf("Warning: Unsupported event (status byte: %#x, delta time: %u) (At pos %i)\n", statusByte, deltaTime, i + posInFile); + i++; + } + if (outputDebug) + printf(" New position: %i\n", i + posInFile); + totalTime += deltaTime; + } + printf("Error: The end of track wasn't found (expected EOT before %i but didn't find it after %i)\n", buffLen + posInFile, i + posInFile); + while (list.note) + deleteNode(&list); + return (false); +} + +MidiParser *parseMidi(char *path, bool outputDebug, bool createNoteArray) +{ + char type[5]; + int length = 0; + unsigned char *buffer = NULL; + int bytes = 0; + int full = 0; + int buffLen = 0; + FILE *stream = fopen(path, "rb"); + int fd = stream ? fileno(stream) : -1; + static MidiParser result; + int tracksFound = 0; + bool foundHeader = false; + int j = 0; + + if (fd < 0) + return (NULL); + memset(type, 0, sizeof(type)); + memset(&length, 0, sizeof(length)); + memset(&result, 0, sizeof(result)); + for (; read(fd, type, 4) > 0; ) { + full += 4; + if (strcmp(type, "MThd") && strcmp(type, "MTrk")) { + printf("Error: Invalid type '%s'\n", type); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + for (int i = 0; i < 4; i++) { + length <<= 8; + length += readSingleByte(fd, &j); + if (j == -1) { + printf("Error: Unexpected \n"); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + } + full += 4; + if (length > buffLen) { + buffer = realloc(buffer, length + 1); + if (!buffer) { + printf("Error: Cannot alloc %iB\n", length + 1); + deleteMidiParserStruct(&result); + close(fd); + free(buffer); + return (NULL); + } + memset(buffer, 0, length + 1); + buffLen = length; + } + bytes = read(fd, buffer, length); + if (bytes != length) { + printf("Error: Unexpected \n"); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + if (outputDebug) + printf("Type: %s\nlength: %i\n", type, length); + if (strcmp(type, "MThd") == 0) { + if (length != 6) { + printf("Error: Invalid header: Length is supposed to be 6 but it was %i\n", length); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } else if (foundHeader) { + printf("Error: Two headers were found\n"); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + foundHeader = true; + for (int i = 0; i < 2; i++) { + result.format <<= 8; + result.format += buffer[i]; + } + if (result.format > 1) { + printf("Error: Unsupported format (%i)\n", result.format); + deleteMidiParserStruct(&result); + return (NULL); + } + for (int i = 0; i < 2; i++) { + result.nbOfTracks <<= 8; + result.nbOfTracks += buffer[i + 2]; + } + result.tracks = malloc(sizeof(*result.tracks) * result.nbOfTracks); + if (!result.tracks) { + printf("Error: Cannot alloc %iB\n", (int)sizeof(*result.tracks) * result.nbOfTracks); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + memset(result.tracks, 0, sizeof(*result.tracks) * result.nbOfTracks); + if (buffer[4] >> 15) { + result.fps = buffer[5] % 128; + result.ticks = buffer[4]; + } else + result.ticks = ((buffer[4] << 9) + (buffer[5] << 1)) >> 1; + } else if (!foundHeader) { + printf("Error: Tracks starts before headers\n"); + deleteMidiParserStruct(&result); + return (NULL); + } else if (tracksFound++ < result.nbOfTracks && !parseMidiTrack(buffer, length, &result.tracks[tracksFound - 1], outputDebug, &result, full, createNoteArray)) { + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + full += length; + if (outputDebug) + printf(strcmp(type, "MThd") == 0 ? "Found header !\n\n" : "End of track %i\n\n", tracksFound); + memset(type, 0, sizeof(type)); + memset(&length, 0, sizeof(length)); + } + if (outputDebug) + printf("Read %iB of file\n", full); + if (!foundHeader) { + printf("Error: No header were found\n"); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } else if (tracksFound != result.nbOfTracks) { + printf("Error: Invalid header: expected %i tracks but found %i\n", result.nbOfTracks, tracksFound); + deleteMidiParserStruct(&result); + close(fd); + return (NULL); + } + if (outputDebug) { + printf("%s: format %hi, %hi tracks, %i notes, ", path, result.format, result.nbOfTracks, result.nbOfNotes); + if (result.fps) { + printf("division: %i FPS and %i ticks/frame\n", result.fps, result.ticks); + } else + printf("division: %i ticks / 1/4 note\n", result.ticks); + } + close(fd); + return (&result); +} \ No newline at end of file diff --git a/src/midi_parser.h b/src/midi_parser.h new file mode 100644 index 0000000..a1c1b1a --- /dev/null +++ b/src/midi_parser.h @@ -0,0 +1,91 @@ +/** + * https://github.com/Gegel85/midi_parser + */ + +#ifndef __LIB_MIDI_PARSER_HEADER_ +#define __LIB_MIDI_PARSER_HEADER_ + +#include + +typedef enum { + MidiSequenceNumber, + MidiTextEvent, + MidiNewLyric, + MidiNewMarker, + MidiNewCuePoint, + MidiNewChannelPrefix, + MidiPortChange, + MidiTempoChanged, + MidiSMTPEOffset, + MidiNewTimeSignature, + MidiNewKeySignature, + MidiSequencerSpecificEvent, + MidiNoteReleased, + MidiNotePressed, + MidiPolyphonicPressure, + MidiControllerValueChanged, + MidiProgramChanged, + MidiPressureOfChannelChanged, + MidiPitchBendChanged, +} EventType; + +typedef struct { + EventType type; + int timeToAppear; + void *infos; +} Event; + +typedef struct { + unsigned char numerator; + unsigned char denominator; + unsigned char clockTicksPerMetTick; + unsigned char ticksPerQuarterNote; +} MidiTimeSignature; + +typedef struct { + unsigned char channel; + unsigned char pitch; + unsigned char velocity; +} MidiNote; + +typedef struct Note { + unsigned char pitch; + unsigned char channel; + unsigned char velocity; + unsigned char fadeOutVelocity; + unsigned long int timeBeforeAppear; + unsigned long int duration; +} Note; + +typedef struct { + char *copyright; + char *name; + char *instrumentName; + int nbOfEvents; + int nbOfNotes; + Note *notes; + Event *events; +} Track; + +typedef struct NoteList { + Note *note; + struct NoteList *next; + struct NoteList *prev; +} NoteList; + +typedef struct { + unsigned short format; + short nbOfTracks; + char fps; + short ticks; + int nbOfNotes; + Track *tracks; +} MidiParser; + +bool parseMidiTrack(unsigned char *buffer, int buffLen, Track *track, bool outputDebug, MidiParser *result, int posInFile, bool createNoteArray); +MidiParser *parseMidi(char *path, bool outputDebug, bool createNoteArray); +char *getNoteString(char note); +void deleteTrack(Track *track); +void deleteMidiParserStruct(MidiParser *result); + +#endif \ No newline at end of file