ardour/libs/evoral/libsmf/smf_load.c
Robin Gareus cd53301d06
Significantly speed up loading SMF tempo-maps
Files that have many tracks, each with tempo information
were near impossible to load (30+ mins on modern 4.2Ghz CPU!),
because tempo is parsed incrementally:

```
For each new track:
  for each new tempo-event:
    rewind()
    for each loaded track so far:
      for each event on this track so far
```

This reduces the complexity from O(tracks^2 * tempos^2)
to O(tracks * tempos).

"Come Thou Fount Tempo Map.mid" has 238 Tracks and 56168 total
Tempo Changes (236 per track). This now requires only 56168 iterations
in smf_create_tempo_map_and_compute_seconds, rather than 1.64e+9
iterations
2022-02-05 17:33:21 +01:00

1016 lines
25 KiB
C

/*-
* Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
* AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/**
* \file
*
* Standard MIDI File format loader.
*
*/
/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <errno.h>
#include <ctype.h>
#include "smf.h"
#include "smf_private.h"
/**
* Returns pointer to the next SMF chunk in smf->buffer, based on length of the previous one.
* Returns NULL in case of error.
*/
static struct chunk_header_struct *
next_chunk(smf_t *smf)
{
struct chunk_header_struct *chunk;
void *next_chunk_ptr;
assert(smf->file_buffer != NULL);
assert(smf->file_buffer_length > 0);
if (smf->next_chunk_offset + sizeof(struct chunk_header_struct) >= smf->file_buffer_length) {
g_warning("SMF warning: no more chunks left.");
return (NULL);
}
next_chunk_ptr = (unsigned char *)smf->file_buffer + smf->next_chunk_offset;
chunk = (struct chunk_header_struct *)next_chunk_ptr;
if (!isalpha(chunk->id[0]) || !isalpha(chunk->id[1]) || !isalpha(chunk->id[2]) || !isalpha(chunk->id[3])) {
g_warning("SMF error: chunk signature contains at least one non-alphanumeric byte.");
return (NULL);
}
/*
* XXX: On SPARC, after compiling with "-fast" option there will be SIGBUS here.
* Please compile with -xmemalign=8i".
*/
smf->next_chunk_offset += sizeof(struct chunk_header_struct) + GUINT32_FROM_BE(chunk->length);
if (smf->next_chunk_offset > smf->file_buffer_length) {
g_warning("SMF warning: malformed chunk; truncated file?");
smf->next_chunk_offset = smf->file_buffer_length;
}
return (chunk);
}
/**
* Returns 1, iff signature of the "chunk" is the same as string passed as "signature".
*/
static int
chunk_signature_matches(const struct chunk_header_struct *chunk, const char *signature)
{
if (!memcmp(chunk->id, signature, 4))
return (1);
return (0);
}
/**
* Verifies if MThd header looks OK. Returns 0 iff it does.
*/
static int
parse_mthd_header(smf_t *smf)
{
int len;
struct chunk_header_struct *mthd, *tmp_mthd;
/* Make sure compiler didn't do anything stupid. */
assert(sizeof(struct chunk_header_struct) == 8);
/*
* We could just do "mthd = smf->file_buffer;" here, but this way we wouldn't
* get useful error messages.
*/
if (smf->file_buffer_length < 6) {
g_warning ("SMF error: file is too short, it cannot be a MIDI file.");
return (-1);
}
tmp_mthd = (struct chunk_header_struct*)smf->file_buffer;
if (!chunk_signature_matches(tmp_mthd, "MThd")) {
g_warning("SMF error: MThd signature not found, is that a MIDI file?");
return (-2);
}
/* Ok, now use next_chunk(). */
mthd = next_chunk(smf);
if (mthd == NULL)
return (-3);
assert(mthd == tmp_mthd);
len = GUINT32_FROM_BE(mthd->length);
if (len != 6) {
g_warning("SMF error: MThd chunk length %d, must be 6.", len);
return (-4);
}
return (0);
}
/**
* Parses MThd chunk, filling "smf" structure with values extracted from it. Returns 0 iff everything went OK.
*/
static int
parse_mthd_chunk(smf_t *smf)
{
signed char first_byte_of_division, second_byte_of_division;
struct mthd_chunk_struct *mthd;
assert(sizeof(struct mthd_chunk_struct) == 14);
if (parse_mthd_header(smf))
return (1);
mthd = (struct mthd_chunk_struct *)smf->file_buffer;
smf->format = GUINT16_FROM_BE(mthd->format);
if (smf->format < 0 || smf->format > 2) {
g_warning("SMF error: bad MThd format field value: %d, valid values are 0-2, inclusive.", smf->format);
return (-1);
}
if (smf->format == 2) {
g_warning("SMF file uses format #2, no support for that yet.");
return (-2);
}
smf->expected_number_of_tracks = GUINT16_FROM_BE(mthd->number_of_tracks);
if (smf->expected_number_of_tracks <= 0) {
g_warning("SMF error: bad number of tracks: %d, must be greater than zero.", smf->expected_number_of_tracks);
return (-3);
}
/* XXX: endianess? */
first_byte_of_division = *((signed char *)&(mthd->division));
second_byte_of_division = *((signed char *)&(mthd->division) + 1);
if (first_byte_of_division >= 0) {
smf->ppqn = GUINT16_FROM_BE(mthd->division);
smf->frames_per_second = 0;
smf->resolution = 0;
} else {
smf->ppqn = 0;
smf->frames_per_second = - first_byte_of_division;
smf->resolution = second_byte_of_division;
}
if (smf->ppqn == 0) {
g_warning("SMF file uses FPS timing instead of PPQN, no support for that yet.");
return (-4);
}
return (0);
}
/**
* Interprets Variable Length Quantity pointed at by "buf" and puts its value into "value" and number
* of bytes consumed into "len", making sure it does not read past "buf" + "buffer_length".
* Explanation of Variable Length Quantities is here: http://www.borg.com/~jglatt/tech/midifile/vari.htm
* Returns 0 iff everything went OK, different value in case of error.
*/
int
smf_extract_vlq(const unsigned char *buf, const size_t buffer_length, uint32_t *value, uint32_t *len)
{
uint32_t val = 0;
const unsigned char *c = buf;
int i = 0;
assert(buffer_length > 0);
for (;; ++i) {
if (c >= buf + buffer_length) {
g_warning("End of buffer in extract_vlq().");
return (-1);
}
if (i == 4 && (val & 0xfe000000)) {
g_warning("SMF error: Variable Length Quantities longer than four bytes are not supported yet.");
return (-2);
}
val = (val << 7) + (*c & 0x7F);
if (*c & 0x80)
c++;
else
break;
};
assert(c >= buf);
*value = val;
*len = c - buf + 1;
if (*len > 5) {
g_warning("SMF error: Variable Length Quantities longer than four bytes are not supported yet.");
return (-2);
}
return (0);
}
/**
* Returns 1 if the given byte is a valid status byte, 0 otherwise.
*/
int
is_status_byte(const unsigned char status)
{
return (status & 0x80);
}
static int
is_sysex_byte(const unsigned char status)
{
if (status == 0xF0)
return (1);
return (0);
}
static int
is_escape_byte(const unsigned char status)
{
if (status == 0xF7)
return (1);
return (0);
}
/**
* Just like expected_message_length(), but only for System Exclusive messages.
* Note that value returned by this thing here is the length of SysEx "on the wire",
* not the number of bytes that this sysex takes in the file - in SMF format sysex
* contains VLQ telling how many bytes it takes, "on the wire" format does not have
* this.
*/
static int32_t
expected_sysex_length(const unsigned char status, const unsigned char *second_byte, const size_t buffer_length, int32_t *consumed_bytes)
{
uint32_t sysex_length = 0;
uint32_t len = 0;
#ifndef NDEBUG
(void) status;
#else
assert(status == 0xF0 || status == 0xF7);
#endif
if (buffer_length < 3) {
g_warning("SMF error: end of buffer in expected_sysex_length().");
return (-1);
}
if (smf_extract_vlq(second_byte, buffer_length, &sysex_length, &len)) {
return (-1);
}
if (consumed_bytes != NULL)
*consumed_bytes = len;
/* +1, because the length does not include status byte. */
return (sysex_length + 1);
}
static int32_t
expected_escaped_length(const unsigned char status, const unsigned char *second_byte, const size_t buffer_length, int32_t *consumed_bytes)
{
/* -1, because we do not want to account for 0x7F status. */
return (expected_sysex_length(status, second_byte, buffer_length, consumed_bytes) - 1);
}
/**
* Returns expected length of the midi message (including the status byte), in bytes, for the given status byte.
* The "second_byte" points to the expected second byte of the MIDI message. "buffer_length" is the buffer
* length limit, counting from "second_byte". Returns value < 0 iff there was an error.
*/
static int32_t
expected_message_length(unsigned char status, const unsigned char *second_byte, const size_t buffer_length)
{
/* Make sure this really is a valid status byte. */
assert(is_status_byte(status));
/* We cannot use this routine for sysexes. */
assert(!is_sysex_byte(status));
/* We cannot use this routine for escaped events. */
assert(!is_escape_byte(status));
/* Is this a metamessage? */
if (status == 0xFF) {
if (buffer_length < 2) {
g_warning("SMF error: end of buffer in expected_message_length().");
return (-1);
}
/*
* Format of this kind of messages is like this: 0xFF 0xTYPE 0xlength and then "length" bytes.
* TYPE is < 127, length may be 0
*
* "lenght" is a 7bit value, the 8th bit is used to extend the length.
* eg. ff02 8266 <0x166 byte (C) message follows>
*/
int32_t mlen = 0;
int32_t off;
for (off = 1; off < 4; ++off) {
uint8_t val = *(second_byte + off);
mlen = mlen << 7 | (val & 0x7f);
if (0 == (val & 0x80)) {
mlen += 2 + off; // 2 byte "header" 0xff <type> + <length of length>
break;
}
}
return mlen;
}
if ((status & 0xF0) == 0xF0) {
switch (status) {
case 0xF2: /* Song Position Pointer. */
return (3);
case 0xF1: /* MTC Quarter Frame. */
case 0xF3: /* Song Select. */
return (2);
case 0xF6: /* Tune Request. */
case 0xF8: /* MIDI Clock. */
case 0xF9: /* Tick. */
case 0xFA: /* MIDI Start. */
case 0xFB: /* MIDI Continue. */
case 0xFC: /* MIDI Stop. */
case 0xFE: /* Active Sense. */
return (1);
default:
g_warning("SMF error: unknown 0xFx-type status byte '0x%x'.", status);
return (-2);
}
}
/* Filter out the channel. */
status &= 0xF0;
switch (status) {
case 0x80: /* Note Off. */
case 0x90: /* Note On. */
case 0xA0: /* AfterTouch. */
case 0xB0: /* Control Change. */
case 0xE0: /* Pitch Wheel. */
return (3);
case 0xC0: /* Program Change. */
case 0xD0: /* Channel Pressure. */
return (2);
default:
g_warning("SMF error: unknown status byte '0x%x'.", status);
return (-3);
}
}
static int
extract_sysex_event(const unsigned char *buf, const size_t buffer_length, smf_event_t *event, uint32_t *len, int last_status)
{
(void) last_status;
int status;
int32_t vlq_length, message_length;
const unsigned char *c = buf;
status = *buf;
if (!(is_sysex_byte(status))) {
g_warning("Corrupt sysex status byte in extract_sysex_event().");
return (-6);
}
c++;
message_length = expected_sysex_length(status, c, buffer_length - 1, &vlq_length);
if (message_length < 0)
return (-3);
c += vlq_length;
if (vlq_length + (size_t)message_length >= buffer_length) {
g_warning("End of buffer in extract_sysex_event().");
return (-5);
}
event->midi_buffer_length = message_length;
event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
if (event->midi_buffer == NULL) {
g_warning("Cannot allocate memory in extract_sysex_event(): %s", strerror(errno));
return (-4);
}
event->midi_buffer[0] = status;
memcpy(event->midi_buffer + 1, c, message_length - 1);
*len = vlq_length + message_length;
return (0);
}
static int
extract_escaped_event(const unsigned char *buf, const size_t buffer_length, smf_event_t *event, uint32_t *len, int last_status)
{
(void) last_status;
int status;
int32_t message_length = 0;
int32_t vlq_length = 0;
const unsigned char *c = buf;
status = *buf;
if (!(is_escape_byte(status))) {
g_warning("Corrupt escape status byte in extract_escaped_event().");
return (-6);
}
c++;
message_length = expected_escaped_length(status, c, buffer_length - 1, &vlq_length);
if (message_length <= 0)
return (-3);
c += vlq_length;
if (vlq_length + (size_t)message_length >= buffer_length) {
g_warning("End of buffer in extract_escaped_event().");
return (-5);
}
event->midi_buffer_length = message_length;
event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
if (event->midi_buffer == NULL) {
g_warning("Cannot allocate memory in extract_escaped_event(): %s", strerror(errno));
return (-4);
}
memcpy(event->midi_buffer, c, message_length);
if (!smf_event_is_valid(event)) {
g_warning("Escaped event is invalid.");
return (-1);
}
if (!(smf_event_is_system_realtime(event) || smf_event_is_system_common(event))) {
g_warning("Escaped event is not System Realtime nor System Common.");
}
*len = vlq_length + message_length;
return (0);
}
/**
* Puts MIDI data extracted from from "buf" into "event" and number of consumed bytes into "len".
* In case valid status is not found, it uses "last_status" (so called "running status").
* Returns 0 iff everything went OK, value < 0 in case of error.
*/
static int
extract_midi_event(const unsigned char *buf, const size_t buffer_length, smf_event_t *event, uint32_t *len, int last_status)
{
int status;
int32_t message_length;
const unsigned char *c = buf;
assert(buffer_length > 0);
/* Is the first byte the status byte? */
if (is_status_byte(*c)) {
status = *c;
c++;
} else {
/* No, we use running status then. */
status = last_status;
}
if (!is_status_byte(status)) {
g_warning("SMF error: bad status byte (MSB is zero).");
return (-1);
}
if (is_sysex_byte(status)) {
if (c == buf) {
g_critical("SMF error: running status is not applicable to System Exclusive events.");
return (-2);
}
return (extract_sysex_event(buf, buffer_length, event, len, last_status));
}
if (is_escape_byte(status)) {
if (c == buf) {
g_critical("SMF error: running status is not applicable to Escape events.");
return (-2);
}
return (extract_escaped_event(buf, buffer_length, event, len, last_status));
}
/* At this point, "c" points to first byte following the status byte. */
message_length = expected_message_length(status, c, buffer_length - (c - buf));
if (message_length < 0)
return (-3);
if ((size_t)message_length > buffer_length - (c - buf) + 1) {
g_warning("End of buffer in extract_midi_event().");
return (-5);
}
event->midi_buffer_length = message_length;
event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
if (event->midi_buffer == NULL) {
g_warning("Cannot allocate memory in extract_midi_event(): %s", strerror(errno));
return (-4);
}
event->midi_buffer[0] = status;
memcpy(event->midi_buffer + 1, c, message_length - 1);
*len = c + message_length - 1 - buf;
return (0);
}
/**
* Locates, basing on track->next_event_offset, the next event data in track->buffer,
* interprets it, allocates smf_event_t and fills it properly. Returns smf_event_t
* or NULL, if there was an error. Allocating event means adding it to the track;
* see smf_event_new().
*/
static smf_event_t *
parse_next_event(smf_track_t *track)
{
uint32_t etime = 0;
uint32_t len;
size_t buffer_length;
unsigned char *c, *start;
smf_event_t *event = smf_event_new();
if (event == NULL)
goto error;
c = start = (unsigned char *)track->file_buffer + track->next_event_offset;
assert(track->file_buffer != NULL);
assert(track->file_buffer_length > 0);
assert(track->next_event_offset > 0);
buffer_length = track->file_buffer_length - track->next_event_offset;
/* if there was no meta-EOT event, buffer_length can be zero. This is
an error in the SMF file, but it shouldn't be treated as fatal.
*/
if (buffer_length == 0) {
g_warning ("SMF warning: expected EOT at end of track, but none found");
goto error;
}
/* First, extract time offset from previous event. */
if (smf_extract_vlq(c, buffer_length, &etime, &len)) {
goto error;
}
c += len;
buffer_length -= len;
if (buffer_length <= 0)
goto error;
/* Now, extract the actual event. */
if (extract_midi_event(c, buffer_length, event, &len, track->last_status)) {
goto error;
}
c += len;
buffer_length -= len;
if (!smf_event_is_metadata (event)) {
track->last_status = event->midi_buffer[0];
}
track->next_event_offset += c - start;
smf_track_add_event_delta_pulses(track, event, etime);
return (event);
error:
if (event != NULL)
smf_event_delete(event);
return (NULL);
}
/**
* Takes "len" characters starting in "buf", making sure it does not access past the length of the buffer,
* and makes ordinary, zero-terminated string from it. May return NULL if there was any problem.
*/
static char *
make_string(const unsigned char *buf, const size_t buffer_length, uint32_t len)
{
char *str;
assert(buffer_length > 0);
assert(len > 0);
if (len > buffer_length) {
g_warning("End of buffer in make_string().");
len = buffer_length;
}
str = (char*)malloc(len + 1);
if (str == NULL) {
g_warning("Cannot allocate memory in make_string().");
return (NULL);
}
memcpy(str, buf, len);
str[len] = '\0';
return (str);
}
/**
* \return 1, if passed a metaevent containing text, that is, Text, Copyright,
* Sequence/Track Name, Instrument, Lyric, Marker, Cue Point, Program Name,
* or Device Name; 0 otherwise.
*/
int
smf_event_is_textual(const smf_event_t *event)
{
if (!smf_event_is_metadata(event)) {
return (0);
}
if (event->midi_buffer_length < 4) {
return (0);
}
if (event->midi_buffer[1] < 1 || event->midi_buffer[1] > 7) {
return (0);
}
return (1);
}
/**
* Extracts text from "textual metaevents", such as Text or Lyric.
*
* \return Zero-terminated string extracted from "text events" or NULL, if there was any problem.
*/
char *
smf_event_extract_text(const smf_event_t *event)
{
uint32_t string_length = 0;
uint32_t length_length = 0;
if (!smf_event_is_textual(event)) {
g_warning ("smf_event_extract_text: event is not textual.");
return (NULL);
}
smf_extract_vlq((const unsigned char*)(void *)&(event->midi_buffer[2]), event->midi_buffer_length - 2, &string_length, &length_length);
if (string_length <= 0) {
g_warning("smf_event_extract_text: truncated MIDI message.");
return (NULL);
}
return (make_string((const unsigned char*)(void *)(&event->midi_buffer[2] + length_length), event->midi_buffer_length - 2 - length_length, string_length));
}
/**
* Verify if the next chunk really is MTrk chunk, and if so, initialize some track variables and return 0.
* Return different value otherwise.
*/
static int
parse_mtrk_header(smf_track_t *track)
{
struct chunk_header_struct *mtrk;
/* Make sure compiler didn't do anything stupid. */
assert(sizeof(struct chunk_header_struct) == 8);
assert(track->smf != NULL);
mtrk = next_chunk(track->smf);
if (mtrk == NULL)
return (-1);
if (!chunk_signature_matches(mtrk, "MTrk")) {
g_warning("SMF warning: Expected MTrk signature, got %c%c%c%c instead; ignoring this chunk.",
mtrk->id[0], mtrk->id[1], mtrk->id[2], mtrk->id[3]);
return (-2);
}
track->file_buffer = mtrk;
track->file_buffer_length = sizeof(struct chunk_header_struct) + GUINT32_FROM_BE(mtrk->length);
track->next_event_offset = sizeof(struct chunk_header_struct);
return (0);
}
/**
* Return 1 if event is end-of-the-track, 0 otherwise.
*/
static int
event_is_end_of_track(const smf_event_t *event)
{
if (event->midi_buffer[0] == 0xFF && event->midi_buffer[1] == 0x2F)
return (1);
return (0);
}
/**
* \return Nonzero, if event is as long as it should be, from the MIDI specification point of view.
* Does not work for SysExes - it doesn't recognize internal structure of SysEx.
*/
int
smf_event_length_is_valid(const smf_event_t *event)
{
assert(event);
assert(event->midi_buffer);
int32_t expected;
if (event->midi_buffer_length < 1)
return (0);
/* We cannot use expected_message_length on sysexes. */
if (smf_event_is_sysex(event))
return (1);
expected = expected_message_length(event->midi_buffer[0],
&(event->midi_buffer[1]), event->midi_buffer_length - 1);
if (expected < 0 || event->midi_buffer_length != (size_t)expected) {
return (0);
}
return (1);
}
/**
* \return Nonzero, if MIDI data in the event is valid, 0 otherwise. For example,
* it checks if event length is correct.
*/
/* XXX: this routine requires some more work to detect more errors. */
int
smf_event_is_valid(const smf_event_t *event)
{
assert(event);
assert(event->midi_buffer);
assert(event->midi_buffer_length >= 1);
if (!is_status_byte(event->midi_buffer[0])) {
g_warning("First byte of MIDI message is not a valid status byte.");
return (0);
}
if (!smf_event_length_is_valid(event))
return (0);
return (1);
}
/**
* Parse events and put it on the track.
*/
static int
parse_mtrk_chunk(smf_track_t *track)
{
smf_event_t *event;
if (parse_mtrk_header(track))
return (-1);
for (;;) {
if (track->next_event_offset == track->file_buffer_length) {
g_warning("SMF warning: The track did not finish with the End of Track event.");
break;
}
event = parse_next_event(track);
/* Couldn't parse an event? */
if (event == NULL) {
g_warning("Unable to parse MIDI event; truncating track.");
if (smf_track_add_eot_delta_pulses(track, 0) != 0) {
g_critical("smf_track_add_eot_delta_pulses failed.");
return (-2);
}
break;
}
assert(smf_event_is_valid(event));
if (event_is_end_of_track(event)) {
break;
}
if (smf_event_is_metadata (event)) {
switch (event->midi_buffer[1]) {
case 0x03:
track->name = smf_event_extract_text (event);
break;
case 0x04:
track->instrument = smf_event_extract_text (event);
break;
default:
break;
}
}
}
track->file_buffer = NULL;
track->file_buffer_length = 0;
track->next_event_offset = -1;
return (0);
}
/**
* Allocate buffer of proper size and read file contents into it.
*/
static int
load_file_into_buffer(void **file_buffer, size_t *file_buffer_length, FILE* stream)
{
long offset;
if (stream == NULL) {
g_warning("Cannot open input file: %s", strerror(errno));
return (-1);
}
if (fseek(stream, 0, SEEK_END)) {
g_warning("fseek(3) failed: %s", strerror(errno));
return (-2);
}
offset = ftell(stream);
if (offset < 0) {
g_warning("ftell(3) failed: %s", strerror(errno));
return (-3);
}
*file_buffer_length = (size_t)offset;
if (fseek(stream, 0, SEEK_SET)) {
g_warning("fseek(3) failed: %s", strerror(errno));
return (-4);
}
*file_buffer = malloc(*file_buffer_length);
if (*file_buffer == NULL) {
g_warning("malloc(3) failed: %s", strerror(errno));
return (-5);
}
if (fread(*file_buffer, 1, *file_buffer_length, stream) != *file_buffer_length) {
g_warning("fread(3) failed: %s", strerror(errno));
free (*file_buffer);
*file_buffer = NULL;
return (-6);
}
return (0);
}
/**
* Creates new SMF and fills it with data loaded from the given buffer.
* \return SMF or NULL, if loading failed.
*/
smf_t *
smf_load_from_memory(void *buffer, const size_t buffer_length)
{
int i;
smf_t *smf = smf_new();
smf->file_buffer = (void *) buffer;
smf->file_buffer_length = buffer_length;
smf->next_chunk_offset = 0;
if (parse_mthd_chunk(smf)) {
smf_delete(smf);
return (NULL);
}
for (i = 1; i <= smf->expected_number_of_tracks; i++) {
smf_track_t *track = smf_track_new();
if (track == NULL) {
smf_delete(smf);
return (NULL);
}
smf_add_track(smf, track);
/* Skip unparseable chunks. */
if (parse_mtrk_chunk(track)) {
g_warning("SMF warning: Cannot load track.");
smf_track_delete(track);
break;
}
track->file_buffer = NULL;
track->file_buffer_length = 0;
track->next_event_offset = -1;
}
if (smf->expected_number_of_tracks != smf->number_of_tracks) {
g_warning("SMF warning: MThd header declared %d tracks, but only %d found; continuing anyway.",
smf->expected_number_of_tracks, smf->number_of_tracks);
smf->expected_number_of_tracks = smf->number_of_tracks;
}
/* process tempo-map */
if (smf->need_tempo_map_compute) {
smf_create_tempo_map_and_compute_seconds(smf);
}
smf->file_buffer = NULL;
smf->file_buffer_length = 0;
smf->next_chunk_offset = 0;
return (smf);
}
/**
* Loads SMF file.
*
* \param file Open file.
* \return SMF or NULL, if loading failed.
*/
smf_t *
smf_load(FILE *file)
{
size_t file_buffer_length;
void *file_buffer;
smf_t *smf;
if (load_file_into_buffer(&file_buffer, &file_buffer_length, file))
return (NULL);
smf = smf_load_from_memory(file_buffer, file_buffer_length);
memset(file_buffer, 0, file_buffer_length);
free(file_buffer);
if (smf == NULL)
return (NULL);
smf_rewind(smf);
return (smf);
}