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 */