foxygit / doom Log in
commit 3dd7e1db9bf26e1e50bc13f6b176e54d63f2da85
Author:     Simon Howard <fraggle@gmail.com>
AuthorDate: Mon Apr 13 22:23:05 2009 +0000
Commit:     Simon Howard <fraggle@gmail.com>
CommitDate: Mon Apr 13 22:23:05 2009 +0000

    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; i<num_bytes; ++i)
     {
-        if (!ReadByte(file, &file->buffer[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; i<track->num_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; i<file->num_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; i<file->num_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 <filename>\n", argv[0]);
-        exit(1);
-    }
-
-    file = MIDI_OpenFile(argv[1]);
+    midi_event_t *event;
+    unsigned int i;

-    if (file == NULL)
+    for (i=0; i<track->num_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 <filename>\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; i<file->num_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 */