From 3dd7e1db9bf26e1e50bc13f6b176e54d63f2da85 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Mon, 13 Apr 2009 22:23:05 +0000 Subject: Extend MIDI file code to support reading multi-track files. Subversion-branch: /branches/opl-branch Subversion-revision: 1498 --- src/midifile.c | 562 +++++++++++++++++++++++++++++++++++---------------------- src/midifile.h | 2 +- 2 files changed, 351 insertions(+), 213 deletions(-) diff --git a/src/midifile.c b/src/midifile.c index ef2ceb88..cca0189e 100644 --- a/src/midifile.c +++ b/src/midifile.c @@ -50,17 +50,33 @@ typedef struct unsigned short time_division; } PACKEDATTR midi_header_t; +typedef struct +{ + // Length in bytes: + + unsigned int data_len; + + // Events in this track: + + midi_event_t *events; + int num_events; +} PACKEDATTR midi_track_t; + struct midi_file_s { - FILE *stream; midi_header_t header; - unsigned int data_len; + + // All tracks in this file: + midi_track_t *tracks; + unsigned int num_tracks; // Data buffer used to store data read for SysEx or meta events: byte *buffer; unsigned int buffer_size; }; +// Check the header of a chunk: + static boolean CheckChunkHeader(chunk_header_t *chunk, char *expected_id) { @@ -80,123 +96,13 @@ static boolean CheckChunkHeader(chunk_header_t *chunk, return result; } -// Read and check the header chunk. - -static boolean ReadHeaderChunk(midi_file_t *file) -{ - size_t records_read; - - records_read = fread(&file->header, sizeof(midi_header_t), 1, file->stream); - - if (records_read < 1) - { - return false; - } - - if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) - || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) - { - fprintf(stderr, "ReadHeaderChunk: Invalid MIDI chunk header! " - "chunk_size=%i\n", - SDL_SwapBE32(file->header.chunk_header.chunk_size)); - return false; - } - - if (SDL_SwapBE16(file->header.format_type) != 0 - || SDL_SwapBE16(file->header.num_tracks) != 1) - { - fprintf(stderr, "ReadHeaderChunk: Only single track, " - "type 0 MIDI files supported!\n"); - return false; - } - - return true; -} - -// Read and check the track chunk header - -static boolean ReadTrackChunk(midi_file_t *file) -{ - size_t records_read; - chunk_header_t chunk_header; - - records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, file->stream); - - if (records_read < 1) - { - return false; - } - - if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) - { - return false; - } - - file->data_len = SDL_SwapBE32(chunk_header.chunk_size); - - return true; -} - -midi_file_t *MIDI_OpenFile(char *filename) -{ - midi_file_t *file; - - file = malloc(sizeof(midi_file_t)); - - if (file == NULL) - { - return NULL; - } - - file->buffer = NULL; - file->buffer_size = 0; - - // Open file - - file->stream = fopen(filename, "rb"); - - if (file->stream == NULL) - { - fprintf(stderr, "MIDI_OpenFile: Failed to open '%s'\n", filename); - free(file); - return NULL; - } - - // Read MIDI file header - - if (!ReadHeaderChunk(file)) - { - fclose(file->stream); - free(file); - return NULL; - } - - // Read track header - - if (!ReadTrackChunk(file)) - { - fclose(file->stream); - free(file); - return NULL; - } - - return file; -} - -void MIDI_CloseFile(midi_file_t *file) -{ - fclose(file->stream); - free(file->buffer); - free(file); -} - // Read a single byte. Returns false on error. -static boolean ReadByte(midi_file_t *file, byte *result) +static boolean ReadByte(byte *result, FILE *stream) { int c; - c = fgetc(file->stream); + c = fgetc(stream); if (c == EOF) { @@ -213,7 +119,7 @@ static boolean ReadByte(midi_file_t *file, byte *result) // Read a variable-length value. -static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) +static boolean ReadVariableLength(unsigned int *result, FILE *stream) { int i; byte b; @@ -222,7 +128,7 @@ static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) for (i=0; i<4; ++i) { - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadVariableLength: Error while reading " "variable-length value\n"); @@ -247,71 +153,46 @@ static boolean ReadVariableLength(midi_file_t *file, unsigned int *result) return false; } -// Expand the size of the buffer used for SysEx/Meta events: - -static boolean ExpandBuffer(midi_file_t *file, unsigned int new_size) -{ - byte *new_buffer; - - if (new_size > MAX_BUFFER_SIZE) - { - fprintf(stderr, "ExpandBuffer: Tried to expand buffer to %u bytes\n", - new_size); - return false; - } - - if (file->buffer_size < new_size) - { - // Reallocate to a larger size: - - new_buffer = realloc(file->buffer, new_size); - - if (new_buffer == NULL) - { - fprintf(stderr, "ExpandBuffer: Failed to expand buffer to %u " - "bytes\n", new_size); - return false; - } - - file->buffer = new_buffer; - file->buffer_size = new_size; - } - - return true; -} - // Read a byte sequence into the data buffer. -static boolean ReadByteSequence(midi_file_t *file, unsigned int num_bytes) +static void *ReadByteSequence(unsigned int num_bytes, FILE *stream) { unsigned int i; + byte *result; + + // Allocate a buffer: - // Check that we have enough space: + result = malloc(num_bytes); - if (!ExpandBuffer(file, num_bytes)) + if (result == NULL) { - return false; + fprintf(stderr, "ReadByteSequence: Failed to allocate buffer\n"); + return NULL; } + // Read the data: + for (i=0; ibuffer[i])) + if (!ReadByte(&result[i], stream)) { fprintf(stderr, "ReadByteSequence: Error while reading byte %u\n", i); - return false; + free(result); + return NULL; } } - return true; + return result; } // Read a MIDI channel event. // two_param indicates that the event type takes two parameters // (three byte) otherwise it is single parameter (two byte) -static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, - byte event_type, boolean two_param) +static boolean ReadChannelEvent(midi_event_t *event, + byte event_type, boolean two_param, + FILE *stream) { byte b; @@ -322,7 +203,7 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, // Read parameters: - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadChannelEvent: Error while reading channel " "event parameters\n"); @@ -335,7 +216,7 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, if (two_param) { - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadChannelEvent: Error while reading channel " "event parameters\n"); @@ -350,12 +231,12 @@ static boolean ReadChannelEvent(midi_file_t *file, midi_event_t *event, // Read sysex event: -static boolean ReadSysExEvent(midi_file_t *file, midi_event_t *event, - int event_type) +static boolean ReadSysExEvent(midi_event_t *event, int event_type, + FILE *stream) { event->event_type = event_type; - if (!ReadVariableLength(file, &event->data.sysex.length)) + if (!ReadVariableLength(&event->data.sysex.length, stream)) { fprintf(stderr, "ReadSysExEvent: Failed to read length of " "SysEx block\n"); @@ -364,20 +245,20 @@ static boolean ReadSysExEvent(midi_file_t *file, midi_event_t *event, // Read the byte sequence: - if (!ReadByteSequence(file, event->data.sysex.length)) + event->data.sysex.data = ReadByteSequence(event->data.sysex.length, stream); + + if (event->data.sysex.data == NULL) { fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); return false; } - event->data.sysex.data = file->buffer; - return true; } // Read meta event: -static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) +static boolean ReadMetaEvent(midi_event_t *event, FILE *stream) { byte b; @@ -385,7 +266,7 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) // Read meta event type: - if (!ReadByte(file, &b)) + if (!ReadByte(&b, stream)) { fprintf(stderr, "ReadMetaEvent: Failed to read meta event type\n"); return false; @@ -395,7 +276,7 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) // Read length of meta event data: - if (!ReadVariableLength(file, &event->data.meta.length)) + if (!ReadVariableLength(&event->data.meta.length, stream)) { fprintf(stderr, "ReadSysExEvent: Failed to read length of " "SysEx block\n"); @@ -404,30 +285,30 @@ static boolean ReadMetaEvent(midi_file_t *file, midi_event_t *event) // Read the byte sequence: - if (!ReadByteSequence(file, event->data.meta.length)) + event->data.meta.data = ReadByteSequence(event->data.meta.length, stream); + + if (event->data.meta.data == NULL) { fprintf(stderr, "ReadSysExEvent: Failed while reading SysEx event\n"); return false; } - event->data.meta.data = file->buffer; - return true; } -boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) +static boolean ReadEvent(midi_event_t *event, FILE *stream) { byte event_type; - if (!ReadVariableLength(file, &event->delta_time)) + if (!ReadVariableLength(&event->delta_time, stream)) { - fprintf(stderr, "MIDI_ReadEvent: Failed to read event timestamp\n"); + fprintf(stderr, "ReadEvent: Failed to read event timestamp\n"); return false; } - if (!ReadByte(file, &event_type)) + if (!ReadByte(&event_type, stream)) { - fprintf(stderr, "MIDI_ReadEvent: Failed to read event type\n"); + fprintf(stderr, "ReadEvent: Failed to read event type\n"); return false; } @@ -442,13 +323,13 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) case MIDI_EVENT_AFTERTOUCH: case MIDI_EVENT_CONTROLLER: case MIDI_EVENT_PITCH_BEND: - return ReadChannelEvent(file, event, event_type, true); + return ReadChannelEvent(event, event_type, true, stream); // Single parameter channel events: case MIDI_EVENT_PROGRAM_CHANGE: case MIDI_EVENT_CHAN_AFTERTOUCH: - return ReadChannelEvent(file, event, event_type, false); + return ReadChannelEvent(event, event_type, false, stream); // Other event types: @@ -456,11 +337,11 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) if (event_type == MIDI_EVENT_SYSEX || event_type == MIDI_EVENT_SYSEX_SPLIT) { - return ReadSysExEvent(file, event, event_type); + return ReadSysExEvent(event, event_type, stream); } else if (event_type == MIDI_EVENT_META) { - return ReadMetaEvent(file, event); + return ReadMetaEvent(event, stream); } // --- Fall-through deliberate --- @@ -472,6 +353,254 @@ boolean MIDI_ReadEvent(midi_file_t *file, midi_event_t *event) } } +// Free an event: + +static void FreeEvent(midi_event_t *event) +{ + // Some event types have dynamically allocated buffers assigned + // to them that must be freed. + + switch (event->event_type) + { + case MIDI_EVENT_SYSEX: + case MIDI_EVENT_SYSEX_SPLIT: + free(event->data.sysex.data); + break; + + case MIDI_EVENT_META: + free(event->data.meta.data); + break; + + default: + // Nothing to do. + break; + } +} + +// Read and check the track chunk header + +static boolean ReadTrackHeader(midi_track_t *track, FILE *stream) +{ + size_t records_read; + chunk_header_t chunk_header; + + records_read = fread(&chunk_header, sizeof(chunk_header_t), 1, stream); + + if (records_read < 1) + { + return false; + } + + if (!CheckChunkHeader(&chunk_header, TRACK_CHUNK_ID)) + { + return false; + } + + track->data_len = SDL_SwapBE32(chunk_header.chunk_size); + + return true; +} + +static boolean ReadTrack(midi_track_t *track, FILE *stream) +{ + midi_event_t *new_events; + midi_event_t *event; + + track->num_events = 0; + track->events = NULL; + + // Read the header: + + if (!ReadTrackHeader(track, stream)) + { + return false; + } + + // Then the events: + + for (;;) + { + // Resize the track slightly larger to hold another event: + + new_events = realloc(track->events, + sizeof(midi_event_t) * (track->num_events + 1)); + + if (new_events == NULL) + { + return false; + } + + track->events = new_events; + + // Read the next event: + + event = &track->events[track->num_events]; + if (!ReadEvent(event, stream)) + { + return false; + } + + ++track->num_events; + + // End of track? + + if (event->event_type == MIDI_EVENT_META + && event->data.meta.type == MIDI_META_END_OF_TRACK) + { + break; + } + } + + return true; +} + +// Free a track: + +static void FreeTrack(midi_track_t *track) +{ + unsigned int i; + + for (i=0; inum_events; ++i) + { + FreeEvent(&track->events[i]); + } + + free(track->events); +} + +static boolean ReadAllTracks(midi_file_t *file, FILE *stream) +{ + unsigned int i; + + // Allocate list of tracks and read each track: + + file->tracks = malloc(sizeof(midi_track_t) * file->num_tracks); + + if (file->tracks == NULL) + { + return false; + } + + memset(file->tracks, 0, sizeof(midi_track_t) * file->num_tracks); + + // Read each track: + + for (i=0; inum_tracks; ++i) + { + if (!ReadTrack(&file->tracks[i], stream)) + { + return false; + } + } + + return true; +} + +// Read and check the header chunk. + +static boolean ReadFileHeader(midi_file_t *file, FILE *stream) +{ + size_t records_read; + unsigned int format_type; + + records_read = fread(&file->header, sizeof(midi_header_t), 1, stream); + + if (records_read < 1) + { + return false; + } + + if (!CheckChunkHeader(&file->header.chunk_header, HEADER_CHUNK_ID) + || SDL_SwapBE32(file->header.chunk_header.chunk_size) != 6) + { + fprintf(stderr, "ReadFileHeader: Invalid MIDI chunk header! " + "chunk_size=%i\n", + SDL_SwapBE32(file->header.chunk_header.chunk_size)); + return false; + } + + format_type = SDL_SwapBE16(file->header.format_type); + file->num_tracks = SDL_SwapBE16(file->header.num_tracks); + + if ((format_type != 0 && format_type != 1) + || file->num_tracks < 1) + { + fprintf(stderr, "ReadFileHeader: Only type 0/1 " + "MIDI files supported!\n"); + return false; + } + + return true; +} + +void MIDI_FreeFile(midi_file_t *file) +{ + int i; + + if (file->tracks != NULL) + { + for (i=0; inum_tracks; ++i) + { + FreeTrack(&file->tracks[i]); + } + + free(file->tracks); + } + + free(file); +} + +midi_file_t *MIDI_OpenFile(char *filename) +{ + midi_file_t *file; + FILE *stream; + + file = malloc(sizeof(midi_file_t)); + + if (file == NULL) + { + return NULL; + } + + file->tracks = NULL; + file->num_tracks = 0; + file->buffer = NULL; + file->buffer_size = 0; + + // Open file + + stream = fopen(filename, "rb"); + + if (stream == NULL) + { + fprintf(stderr, "MIDI_OpenFile: Failed to open '%s'\n", filename); + MIDI_FreeFile(file); + return NULL; + } + + // Read MIDI file header + + if (!ReadFileHeader(file, stream)) + { + fclose(stream); + MIDI_FreeFile(file); + return NULL; + } + + // Read all tracks: + + if (!ReadAllTracks(file, stream)) + { + fclose(stream); + MIDI_FreeFile(file); + return NULL; + } + + fclose(stream); + + return file; +} + #ifdef TEST static char *MIDI_EventTypeToString(midi_event_type_t event_type) @@ -504,32 +633,20 @@ static char *MIDI_EventTypeToString(midi_event_type_t event_type) } } -int main(int argc, char *argv[]) +void PrintTrack(midi_track_t *track) { - midi_file_t *file; - midi_event_t event; - - if (argc < 2) - { - printf("Usage: %s \n", argv[0]); - exit(1); - } - - file = MIDI_OpenFile(argv[1]); + midi_event_t *event; + unsigned int i; - if (file == NULL) + for (i=0; inum_events; ++i) { - fprintf(stderr, "Failed to open %s\n", argv[1]); - exit(1); - } + event = &track->events[i]; - while (MIDI_ReadEvent(file, &event)) - { printf("Event type: %s (%i)\n", - MIDI_EventTypeToString(event.event_type), - event.event_type); + MIDI_EventTypeToString(event->event_type), + event->event_type); - switch(event.event_type) + switch(event->event_type) { case MIDI_EVENT_NOTE_OFF: case MIDI_EVENT_NOTE_ON: @@ -538,27 +655,48 @@ int main(int argc, char *argv[]) case MIDI_EVENT_PROGRAM_CHANGE: case MIDI_EVENT_CHAN_AFTERTOUCH: case MIDI_EVENT_PITCH_BEND: - printf("\tChannel: %i\n", event.data.channel.channel); - printf("\tParameter 1: %i\n", event.data.channel.param1); - printf("\tParameter 2: %i\n", event.data.channel.param2); + printf("\tChannel: %i\n", event->data.channel.channel); + printf("\tParameter 1: %i\n", event->data.channel.param1); + printf("\tParameter 2: %i\n", event->data.channel.param2); break; case MIDI_EVENT_SYSEX: case MIDI_EVENT_SYSEX_SPLIT: - printf("\tLength: %i\n", event.data.sysex.length); + printf("\tLength: %i\n", event->data.sysex.length); break; case MIDI_EVENT_META: - printf("\tMeta type: %i\n", event.data.meta.type); - printf("\tLength: %i\n", event.data.meta.length); + printf("\tMeta type: %i\n", event->data.meta.type); + printf("\tLength: %i\n", event->data.meta.length); break; } + } +} - if (event.event_type == MIDI_EVENT_META - && event.data.meta.type == MIDI_META_END_OF_TRACK) - { - break; - } +int main(int argc, char *argv[]) +{ + midi_file_t *file; + unsigned int i; + + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + exit(1); + } + + file = MIDI_OpenFile(argv[1]); + + if (file == NULL) + { + fprintf(stderr, "Failed to open %s\n", argv[1]); + exit(1); + } + + for (i=0; inum_tracks; ++i) + { + printf("\n== Track %i ==\n\n", i); + + PrintTrack(&file->tracks[i]); } return 0; diff --git a/src/midifile.h b/src/midifile.h index 7928fdda..16f911e7 100644 --- a/src/midifile.h +++ b/src/midifile.h @@ -130,7 +130,7 @@ typedef struct } midi_event_t; midi_file_t *MIDI_OpenFile(char *filename); -void MIDI_CloseFile(midi_file_t *file); +void MIDI_FreeFile(midi_file_t *file); #endif /* #ifndef MIDIFILE_H */ -- cgit v1.2.3