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