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 writer. 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: #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: #define MAX_VLQ_LENGTH 128 nuclear@0: nuclear@0: /** nuclear@0: * Extends (reallocates) smf->file_buffer and returns pointer to the newly added space, nuclear@0: * that is, pointer to the first byte after the previous buffer end. Returns NULL in case nuclear@0: * of error. nuclear@0: */ nuclear@0: static void * nuclear@0: smf_extend(smf_t *smf, const int length) nuclear@0: { nuclear@0: int i, previous_file_buffer_length = smf->file_buffer_length; nuclear@0: char *previous_file_buffer = smf->file_buffer; nuclear@0: nuclear@0: /* XXX: Not terribly efficient. */ nuclear@0: smf->file_buffer_length += length; nuclear@0: smf->file_buffer = realloc(smf->file_buffer, smf->file_buffer_length); nuclear@0: if (smf->file_buffer == NULL) { nuclear@0: fg_critical("realloc(3) failed: %s", strerror(errno)); nuclear@0: smf->file_buffer_length = 0; nuclear@0: return (NULL); nuclear@0: } nuclear@0: nuclear@0: /* Fix up pointers. XXX: omgwtf. */ nuclear@0: for (i = 1; i <= smf->number_of_tracks; i++) { nuclear@0: smf_track_t *track; nuclear@0: track = smf_get_track_by_number(smf, i); nuclear@0: if (track->file_buffer != NULL) nuclear@0: track->file_buffer = (char *)track->file_buffer + ((char *)smf->file_buffer - previous_file_buffer); nuclear@0: } nuclear@0: nuclear@0: return ((char *)smf->file_buffer + previous_file_buffer_length); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends "buffer_length" bytes pointed to by "buffer" to the smf, reallocating storage as needed. Returns 0 nuclear@0: * if everything went ok, different value if there was any problem. nuclear@0: */ nuclear@0: static int nuclear@0: smf_append(smf_t *smf, const void *buffer, const int buffer_length) nuclear@0: { nuclear@0: void *dest; nuclear@0: nuclear@0: dest = smf_extend(smf, buffer_length); nuclear@0: if (dest == NULL) { nuclear@0: fg_critical("Cannot extend track buffer."); nuclear@0: return (-1); nuclear@0: } nuclear@0: nuclear@0: memcpy(dest, buffer, buffer_length); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends MThd header to the track. Returns 0 if everything went ok, different value if not. nuclear@0: */ nuclear@0: static int nuclear@0: write_mthd_header(smf_t *smf) nuclear@0: { nuclear@0: struct mthd_chunk_struct mthd_chunk; nuclear@0: nuclear@0: memcpy(mthd_chunk.mthd_header.id, "MThd", 4); nuclear@0: mthd_chunk.mthd_header.length = htonl(6); nuclear@0: mthd_chunk.format = htons(smf->format); nuclear@0: mthd_chunk.number_of_tracks = htons(smf->number_of_tracks); nuclear@0: mthd_chunk.division = htons(smf->ppqn); nuclear@0: nuclear@0: return (smf_append(smf, &mthd_chunk, sizeof(mthd_chunk))); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Extends (reallocates) track->file_buffer and returns pointer to the newly added space, nuclear@0: * that is, pointer to the first byte after the previous buffer end. Returns NULL in case nuclear@0: * of error. nuclear@0: */ nuclear@0: static void * nuclear@0: track_extend(smf_track_t *track, const int length) nuclear@0: { nuclear@0: void *buf; nuclear@0: nuclear@0: assert(track->smf); nuclear@0: nuclear@0: buf = smf_extend(track->smf, length); nuclear@0: if (buf == NULL) nuclear@0: return (NULL); nuclear@0: nuclear@0: track->file_buffer_length += length; nuclear@0: if (track->file_buffer == NULL) nuclear@0: track->file_buffer = buf; nuclear@0: nuclear@0: return (buf); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends "buffer_length" bytes pointed to by "buffer" to the track, reallocating storage as needed. Returns 0 nuclear@0: * if everything went ok, different value if there was any problem. nuclear@0: */ nuclear@0: static int nuclear@0: track_append(smf_track_t *track, const void *buffer, const int buffer_length) nuclear@0: { nuclear@0: void *dest; nuclear@0: nuclear@0: dest = track_extend(track, buffer_length); nuclear@0: if (dest == NULL) { nuclear@0: fg_critical("Cannot extend track buffer."); nuclear@0: return (-1); nuclear@0: } nuclear@0: nuclear@0: memcpy(dest, buffer, buffer_length); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: static int nuclear@0: format_vlq(unsigned char *buf, int length, unsigned long value) nuclear@0: { nuclear@0: int i; nuclear@0: unsigned long buffer; nuclear@0: nuclear@0: /* Taken from http://www.borg.com/~jglatt/tech/midifile/vari.htm */ nuclear@0: buffer = value & 0x7F; nuclear@0: nuclear@0: while ((value >>= 7)) { nuclear@0: buffer <<= 8; nuclear@0: buffer |= ((value & 0x7F) | 0x80); nuclear@0: } nuclear@0: nuclear@0: for (i = 0;; i++) { nuclear@0: buf[i] = buffer; nuclear@0: nuclear@0: if (buffer & 0x80) nuclear@0: buffer >>= 8; nuclear@0: else nuclear@0: break; nuclear@0: } nuclear@0: nuclear@0: assert(i <= length); nuclear@0: nuclear@0: /* + 1, because "i" is an offset, not a count. */ nuclear@0: return (i + 1); nuclear@0: } nuclear@0: nuclear@0: smf_event_t * nuclear@0: smf_event_new_textual(int type, const char *text) nuclear@0: { nuclear@0: int vlq_length, text_length, copied_length; nuclear@0: smf_event_t *event; nuclear@0: nuclear@0: assert(type >= 1 && type <= 9); nuclear@0: nuclear@0: text_length = strlen(text); nuclear@0: nuclear@0: event = smf_event_new(); nuclear@0: if (event == NULL) nuclear@0: return (NULL); nuclear@0: nuclear@0: /* "2 +" is for leading 0xFF 0xtype. */ nuclear@0: event->midi_buffer_length = 2 + text_length + MAX_VLQ_LENGTH; nuclear@0: event->midi_buffer = malloc(event->midi_buffer_length); nuclear@0: if (event->midi_buffer == NULL) { nuclear@0: fg_critical("Cannot allocate MIDI buffer structure: %s", strerror(errno)); nuclear@0: smf_event_delete(event); nuclear@0: nuclear@0: return (NULL); nuclear@0: } nuclear@0: nuclear@0: event->midi_buffer[0] = 0xFF; nuclear@0: event->midi_buffer[1] = type; nuclear@0: nuclear@0: vlq_length = format_vlq(event->midi_buffer + 2, MAX_VLQ_LENGTH - 2, text_length); nuclear@0: copied_length = snprintf((char *)event->midi_buffer + vlq_length + 2, event->midi_buffer_length - vlq_length - 2, "%s", text); nuclear@0: nuclear@0: assert(copied_length == text_length); nuclear@0: nuclear@0: event->midi_buffer_length = 2 + vlq_length + text_length; nuclear@0: nuclear@0: return event; nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends value, expressed as Variable Length Quantity, to event->track. nuclear@0: */ nuclear@0: static int nuclear@0: write_vlq(smf_event_t *event, unsigned long value) nuclear@0: { nuclear@0: unsigned char buf[MAX_VLQ_LENGTH]; nuclear@0: int vlq_length; nuclear@0: nuclear@0: vlq_length = format_vlq(buf, MAX_VLQ_LENGTH, value); nuclear@0: nuclear@0: return (track_append(event->track, buf, vlq_length)); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends event time as Variable Length Quantity. Returns 0 if everything went ok, nuclear@0: * different value in case of error. nuclear@0: */ nuclear@0: static int nuclear@0: write_event_time(smf_event_t *event) nuclear@0: { nuclear@0: assert(event->delta_time_pulses >= 0); nuclear@0: nuclear@0: return (write_vlq(event, event->delta_time_pulses)); nuclear@0: } nuclear@0: nuclear@0: static int nuclear@0: write_sysex_contents(smf_event_t *event) nuclear@0: { nuclear@0: int ret; nuclear@0: unsigned char sysex_status = 0xF0; nuclear@0: nuclear@0: assert(smf_event_is_sysex(event)); nuclear@0: nuclear@0: ret = track_append(event->track, &sysex_status, 1); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: /* -1, because length does not include status byte. */ nuclear@0: ret = write_vlq(event, event->midi_buffer_length - 1); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: ret = track_append(event->track, event->midi_buffer + 1, event->midi_buffer_length - 1); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends contents of event->midi_buffer wrapped into 0xF7 MIDI event. nuclear@0: */ nuclear@0: static int nuclear@0: write_escaped_event_contents(smf_event_t *event) nuclear@0: { nuclear@0: int ret; nuclear@0: unsigned char escape_status = 0xF7; nuclear@0: nuclear@0: if (smf_event_is_sysex(event)) nuclear@0: return (write_sysex_contents(event)); nuclear@0: nuclear@0: ret = track_append(event->track, &escape_status, 1); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: ret = write_vlq(event, event->midi_buffer_length); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: ret = track_append(event->track, event->midi_buffer, event->midi_buffer_length); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Appends contents of event->midi_buffer. Returns 0 if everything went 0, nuclear@0: * different value in case of error. nuclear@0: */ nuclear@0: static int nuclear@0: write_event_contents(smf_event_t *event) nuclear@0: { nuclear@0: if (smf_event_is_system_realtime(event) || smf_event_is_system_common(event)) nuclear@0: return (write_escaped_event_contents(event)); nuclear@0: nuclear@0: return (track_append(event->track, event->midi_buffer, event->midi_buffer_length)); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Writes out an event. nuclear@0: */ nuclear@0: static int nuclear@0: write_event(smf_event_t *event) nuclear@0: { nuclear@0: int ret; nuclear@0: nuclear@0: ret = write_event_time(event); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: ret = write_event_contents(event); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Writes out MTrk header, except of MTrk chunk length, which is written by write_mtrk_length(). nuclear@0: */ nuclear@0: static int nuclear@0: write_mtrk_header(smf_track_t *track) nuclear@0: { nuclear@0: struct chunk_header_struct mtrk_header; nuclear@0: nuclear@0: memcpy(mtrk_header.id, "MTrk", 4); nuclear@0: nuclear@0: return (track_append(track, &mtrk_header, sizeof(mtrk_header))); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Updates MTrk chunk length of a given track. nuclear@0: */ nuclear@0: static int nuclear@0: write_mtrk_length(smf_track_t *track) nuclear@0: { nuclear@0: struct chunk_header_struct *mtrk_header; nuclear@0: nuclear@0: assert(track->file_buffer != NULL); nuclear@0: assert(track->file_buffer_length >= 6); nuclear@0: nuclear@0: mtrk_header = (struct chunk_header_struct *)track->file_buffer; nuclear@0: mtrk_header->length = htonl(track->file_buffer_length - sizeof(struct chunk_header_struct)); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Writes out the track. nuclear@0: */ nuclear@0: static int nuclear@0: write_track(smf_track_t *track) nuclear@0: { nuclear@0: int ret; nuclear@0: smf_event_t *event; nuclear@0: nuclear@0: ret = write_mtrk_header(track); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: while ((event = smf_track_get_next_event(track)) != NULL) { nuclear@0: ret = write_event(event); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: } nuclear@0: nuclear@0: ret = write_mtrk_length(track); nuclear@0: if (ret) nuclear@0: return (ret); nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Takes smf->file_buffer and saves it to the file. nuclear@0: */ nuclear@0: static int nuclear@0: write_file(smf_t *smf, const char *file_name) nuclear@0: { nuclear@0: FILE *stream; nuclear@0: nuclear@0: stream = fopen(file_name, "wb+"); 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 (fwrite(smf->file_buffer, 1, smf->file_buffer_length, stream) != smf->file_buffer_length) { nuclear@0: fg_critical("fwrite(3) failed: %s", strerror(errno)); nuclear@0: nuclear@0: return (-2); nuclear@0: } nuclear@0: nuclear@0: if (fclose(stream)) { nuclear@0: fg_critical("fclose(3) failed: %s", strerror(errno)); nuclear@0: nuclear@0: return (-3); nuclear@0: } nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: static void nuclear@0: free_buffer(smf_t *smf) nuclear@0: { nuclear@0: int i; nuclear@0: smf_track_t *track; nuclear@0: nuclear@0: /* Clear the pointers. */ nuclear@0: memset(smf->file_buffer, 0, smf->file_buffer_length); nuclear@0: free(smf->file_buffer); nuclear@0: smf->file_buffer = NULL; nuclear@0: smf->file_buffer_length = 0; nuclear@0: nuclear@0: for (i = 1; i <= smf->number_of_tracks; i++) { nuclear@0: track = smf_get_track_by_number(smf, i); nuclear@0: assert(track); nuclear@0: track->file_buffer = NULL; nuclear@0: track->file_buffer_length = 0; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: #ifndef NDEBUG nuclear@0: nuclear@0: /** nuclear@0: * \return Nonzero, if all pointers supposed to be NULL are NULL. Triggers assertion if not. nuclear@0: */ nuclear@0: static int nuclear@0: pointers_are_clear(smf_t *smf) nuclear@0: { nuclear@0: int i; nuclear@0: nuclear@0: smf_track_t *track; nuclear@0: assert(smf->file_buffer == NULL); nuclear@0: assert(smf->file_buffer_length == 0); nuclear@0: nuclear@0: for (i = 1; i <= smf->number_of_tracks; i++) { nuclear@0: track = smf_get_track_by_number(smf, i); nuclear@0: nuclear@0: assert(track != NULL); nuclear@0: assert(track->file_buffer == NULL); nuclear@0: assert(track->file_buffer_length == 0); nuclear@0: } nuclear@0: nuclear@0: return (1); nuclear@0: } nuclear@0: nuclear@0: #endif /* !NDEBUG */ nuclear@0: nuclear@0: /** nuclear@0: * \return Nonzero, if event is End Of Track metaevent. nuclear@0: */ nuclear@0: int nuclear@0: smf_event_is_eot(const smf_event_t *event) nuclear@0: { nuclear@0: if (event->midi_buffer_length != 3) nuclear@0: return (0); nuclear@0: nuclear@0: if (event->midi_buffer[0] != 0xFF || event->midi_buffer[1] != 0x2F || event->midi_buffer[2] != 0x00) nuclear@0: return (0); nuclear@0: nuclear@0: return (1); nuclear@0: } nuclear@0: nuclear@0: /** nuclear@0: * Check if SMF is valid and add missing EOT events. nuclear@0: * nuclear@0: * \return 0, if SMF is valid. nuclear@0: */ nuclear@0: static int nuclear@0: smf_validate(smf_t *smf) nuclear@0: { nuclear@0: int trackno, eventno, eot_found; nuclear@0: smf_track_t *track; nuclear@0: smf_event_t *event; nuclear@0: nuclear@0: if (smf->format < 0 || smf->format > 2) { nuclear@0: fg_critical("SMF error: smf->format is less than zero of greater than two."); nuclear@0: return (-1); nuclear@0: } nuclear@0: nuclear@0: if (smf->number_of_tracks < 1) { nuclear@0: fg_critical("SMF error: number of tracks is less than one."); nuclear@0: return (-2); nuclear@0: } nuclear@0: nuclear@0: if (smf->format == 0 && smf->number_of_tracks > 1) { nuclear@0: fg_critical("SMF error: format is 0, but number of tracks is more than one."); nuclear@0: return (-3); nuclear@0: } nuclear@0: nuclear@0: if (smf->ppqn <= 0) { nuclear@0: fg_critical("SMF error: PPQN has to be > 0."); nuclear@0: return (-4); nuclear@0: } nuclear@0: nuclear@0: for (trackno = 1; trackno <= smf->number_of_tracks; trackno++) { nuclear@0: track = smf_get_track_by_number(smf, trackno); nuclear@0: assert(track); nuclear@0: nuclear@0: eot_found = 0; nuclear@0: nuclear@0: for (eventno = 1; eventno <= track->number_of_events; eventno++) { nuclear@0: event = smf_track_get_event_by_number(track, eventno); nuclear@0: assert(event); nuclear@0: nuclear@0: if (!smf_event_is_valid(event)) { nuclear@0: fg_critical("Event #%d on track #%d is invalid.", eventno, trackno); nuclear@0: return (-5); nuclear@0: } nuclear@0: nuclear@0: if (smf_event_is_eot(event)) { nuclear@0: if (eot_found) { nuclear@0: fg_critical("Duplicate End Of Track event on track #%d.", trackno); nuclear@0: return (-6); nuclear@0: } nuclear@0: nuclear@0: eot_found = 1; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if (!eot_found) { nuclear@0: if (smf_track_add_eot_delta_pulses(track, 0)) { nuclear@0: fg_critical("smf_track_add_eot_delta_pulses failed."); nuclear@0: return (-6); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: } nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: nuclear@0: #ifndef NDEBUG nuclear@0: nuclear@0: static void nuclear@0: assert_smf_event_is_identical(const smf_event_t *a, const smf_event_t *b) nuclear@0: { nuclear@0: assert(a->event_number == b->event_number); nuclear@0: assert(a->delta_time_pulses == b->delta_time_pulses); nuclear@0: assert(abs(a->time_pulses - b->time_pulses) <= 2); nuclear@0: assert(fabs(a->time_seconds - b->time_seconds) <= 0.01); nuclear@0: assert(a->track_number == b->track_number); nuclear@0: assert(a->midi_buffer_length == b->midi_buffer_length); nuclear@0: assert(memcmp(a->midi_buffer, b->midi_buffer, a->midi_buffer_length) == 0); nuclear@0: } nuclear@0: nuclear@0: static void nuclear@0: assert_smf_track_is_identical(const smf_track_t *a, const smf_track_t *b) nuclear@0: { nuclear@0: int i; nuclear@0: nuclear@0: assert(a->track_number == b->track_number); nuclear@0: assert(a->number_of_events == b->number_of_events); nuclear@0: nuclear@0: for (i = 1; i <= a->number_of_events; i++) nuclear@0: assert_smf_event_is_identical(smf_track_get_event_by_number(a, i), smf_track_get_event_by_number(b, i)); nuclear@0: } nuclear@0: nuclear@0: static void nuclear@0: assert_smf_is_identical(const smf_t *a, const smf_t *b) nuclear@0: { nuclear@0: int i; nuclear@0: nuclear@0: assert(a->format == b->format); nuclear@0: assert(a->ppqn == b->ppqn); nuclear@0: assert(a->frames_per_second == b->frames_per_second); nuclear@0: assert(a->resolution == b->resolution); nuclear@0: assert(a->number_of_tracks == b->number_of_tracks); nuclear@0: nuclear@0: for (i = 1; i <= a->number_of_tracks; i++) nuclear@0: assert_smf_track_is_identical(smf_get_track_by_number(a, i), smf_get_track_by_number(b, i)); nuclear@0: nuclear@0: /* We do not need to compare tempos explicitly, as tempo is always computed from track contents. */ nuclear@0: } nuclear@0: nuclear@0: static void nuclear@0: assert_smf_saved_correctly(const smf_t *smf, const char *file_name) nuclear@0: { nuclear@0: smf_t *saved; nuclear@0: nuclear@0: saved = smf_load(file_name); nuclear@0: assert(saved != NULL); nuclear@0: nuclear@0: assert_smf_is_identical(smf, saved); nuclear@0: nuclear@0: smf_delete(saved); nuclear@0: } nuclear@0: nuclear@0: #endif /* !NDEBUG */ nuclear@0: nuclear@0: /** nuclear@0: * Writes the contents of SMF to the file given. nuclear@0: * \param smf SMF. nuclear@0: * \param file_name Path to the file. nuclear@0: * \return 0, if saving was successfull. nuclear@0: */ nuclear@0: int nuclear@0: smf_save(smf_t *smf, const char *file_name) nuclear@0: { nuclear@0: int i, error; nuclear@0: smf_track_t *track; nuclear@0: nuclear@0: smf_rewind(smf); nuclear@0: nuclear@0: assert(pointers_are_clear(smf)); nuclear@0: nuclear@0: if (smf_validate(smf)) nuclear@0: return (-1); nuclear@0: nuclear@0: if (write_mthd_header(smf)) nuclear@0: return (-2); nuclear@0: nuclear@0: for (i = 1; i <= smf->number_of_tracks; i++) { nuclear@0: track = smf_get_track_by_number(smf, i); nuclear@0: nuclear@0: assert(track != NULL); nuclear@0: nuclear@0: error = write_track(track); nuclear@0: if (error) { nuclear@0: free_buffer(smf); nuclear@0: return (error); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: error = write_file(smf, file_name); nuclear@0: nuclear@0: free_buffer(smf); nuclear@0: nuclear@0: if (error) nuclear@0: return (error); nuclear@0: nuclear@0: #ifndef NDEBUG nuclear@0: assert_smf_saved_correctly(smf, file_name); nuclear@0: #endif nuclear@0: nuclear@0: return (0); nuclear@0: } nuclear@0: