shithub: choc

Download patch

ref: 3dd7e1db9bf26e1e50bc13f6b176e54d63f2da85
parent: b2d78c296a5a2f9cf37bf8bd3dafb5c8ba166812
author: Simon Howard <[email protected]>
date: Mon Apr 13 18:23:05 EDT 2009

Extend MIDI file code to support reading multi-track files.

Subversion-branch: /branches/opl-branch
Subversion-revision: 1498

--- a/src/midifile.c
+++ b/src/midifile.c
@@ -50,17 +50,33 @@
     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 @@
     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 @@
 
 // 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 @@
 
     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,63 +153,37 @@
     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;
 
-    // Check that we have enough space:
+    // Allocate a buffer:
 
-    if (!ExpandBuffer(file, num_bytes))
+    result = malloc(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.
@@ -310,8 +190,9 @@
 // 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 @@
 
     // Read parameters:
 
-    if (!ReadByte(file, &b))
+    if (!ReadByte(&b, stream))
     {
         fprintf(stderr, "ReadChannelEvent: Error while reading channel "
                         "event parameters\n");
@@ -335,7 +216,7 @@
 
     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 @@
 
 // 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 @@
 
     // 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 @@
 
     // 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 @@
 
     // 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 @@
 
     // 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 @@
         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 @@
             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 @@
     }
 }
 
+// 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 @@
     }
 }
 
-int main(int argc, char *argv[])
+void PrintTrack(midi_track_t *track)
 {
-    midi_file_t *file;
-    midi_event_t event;
+    midi_event_t *event;
+    unsigned int i;
 
-    if (argc < 2)
+    for (i=0; i<track->num_events; ++i)
     {
-        printf("Usage: %s <filename>\n", argv[0]);
-        exit(1);
-    }
+        event = &track->events[i];
 
-    file = MIDI_OpenFile(argv[1]);
-
-    if (file == NULL)
-    {
-        fprintf(stderr, "Failed to open %s\n", argv[1]);
-        exit(1);
-    }
-
-    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 @@
             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;
--- a/src/midifile.h
+++ b/src/midifile.h
@@ -130,7 +130,7 @@
 } 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 */