summaryrefslogtreecommitdiff
path: root/libs/evoral/libsmf/smf_save.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/evoral/libsmf/smf_save.c')
-rw-r--r--libs/evoral/libsmf/smf_save.c691
1 files changed, 691 insertions, 0 deletions
diff --git a/libs/evoral/libsmf/smf_save.c b/libs/evoral/libsmf/smf_save.c
new file mode 100644
index 0000000000..12eb879c4e
--- /dev/null
+++ b/libs/evoral/libsmf/smf_save.c
@@ -0,0 +1,691 @@
+/*-
+ * 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 writer.
+ *
+ */
+
+/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+#include <errno.h>
+#ifdef PLATFORM_WINDOWS
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include "smf.h"
+#include "smf_private.h"
+
+#define MAX_VLQ_LENGTH 128
+
+/**
+ * Extends (reallocates) smf->file_buffer and returns pointer to the newly added space,
+ * that is, pointer to the first byte after the previous buffer end. Returns NULL in case
+ * of error.
+ */
+static void *
+smf_extend(smf_t *smf, const int length)
+{
+ int i, previous_file_buffer_length = smf->file_buffer_length;
+ char *previous_file_buffer = (char*)smf->file_buffer;
+
+ /* XXX: Not terribly efficient. */
+ smf->file_buffer_length += length;
+ smf->file_buffer = realloc(smf->file_buffer, smf->file_buffer_length);
+ if (smf->file_buffer == NULL) {
+ g_warning("realloc(3) failed: %s", strerror(errno));
+ smf->file_buffer_length = 0;
+ return (NULL);
+ }
+
+ /* Fix up pointers. XXX: omgwtf. */
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ smf_track_t *track;
+ track = smf_get_track_by_number(smf, i);
+ if (track->file_buffer != NULL)
+ track->file_buffer = (char *)track->file_buffer + ((char *)smf->file_buffer - previous_file_buffer);
+ }
+
+ return ((char *)smf->file_buffer + previous_file_buffer_length);
+}
+
+/**
+ * Appends "buffer_length" bytes pointed to by "buffer" to the smf, reallocating storage as needed. Returns 0
+ * if everything went ok, different value if there was any problem.
+ */
+static int
+smf_append(smf_t *smf, const void *buffer, const int buffer_length)
+{
+ void *dest;
+
+ dest = smf_extend(smf, buffer_length);
+ if (dest == NULL) {
+ g_warning("Cannot extend track buffer.");
+ return (-1);
+ }
+
+ memcpy(dest, buffer, buffer_length);
+
+ return (0);
+}
+
+/**
+ * Appends MThd header to the track. Returns 0 if everything went ok, different value if not.
+ */
+static int
+write_mthd_header(smf_t *smf)
+{
+ struct mthd_chunk_struct mthd_chunk;
+
+ memcpy(mthd_chunk.mthd_header.id, "MThd", 4);
+ mthd_chunk.mthd_header.length = htonl(6);
+ mthd_chunk.format = htons(smf->format);
+ mthd_chunk.number_of_tracks = htons(smf->number_of_tracks);
+ mthd_chunk.division = htons(smf->ppqn);
+
+ return (smf_append(smf, &mthd_chunk, sizeof(mthd_chunk)));
+}
+
+/**
+ * Extends (reallocates) track->file_buffer and returns pointer to the newly added space,
+ * that is, pointer to the first byte after the previous buffer end. Returns NULL in case
+ * of error.
+ */
+static void *
+track_extend(smf_track_t *track, const int length)
+{
+ void *buf;
+
+ assert(track->smf);
+
+ buf = smf_extend(track->smf, length);
+ if (buf == NULL)
+ return (NULL);
+
+ track->file_buffer_length += length;
+ if (track->file_buffer == NULL)
+ track->file_buffer = buf;
+
+ return (buf);
+}
+
+/**
+ * Appends "buffer_length" bytes pointed to by "buffer" to the track, reallocating storage as needed. Returns 0
+ * if everything went ok, different value if there was any problem.
+ */
+static int
+track_append(smf_track_t *track, const void *buffer, const int buffer_length)
+{
+ void *dest;
+
+ dest = track_extend(track, buffer_length);
+ if (dest == NULL) {
+ g_warning("Cannot extend track buffer.");
+ return (-1);
+ }
+
+ memcpy(dest, buffer, buffer_length);
+
+ return (0);
+}
+
+int
+smf_format_vlq(unsigned char *buf, int length, unsigned long value)
+{
+ int i;
+ unsigned long buffer;
+
+ /* Taken from http://www.borg.com/~jglatt/tech/midifile/vari.htm */
+ buffer = value & 0x7F;
+
+ while ((value >>= 7)) {
+ buffer <<= 8;
+ buffer |= ((value & 0x7F) | 0x80);
+ }
+
+ for (i = 0;; i++) {
+ buf[i] = buffer;
+
+ if (buffer & 0x80)
+ buffer >>= 8;
+ else
+ break;
+ }
+
+ assert(i <= length);
+
+ /* + 1, because "i" is an offset, not a count. */
+ return (i + 1);
+}
+
+smf_event_t *
+smf_event_new_textual(int type, const char *text)
+{
+ int vlq_length, text_length, copied_length;
+ smf_event_t *event;
+
+ assert(type >= 1 && type <= 9);
+
+ text_length = strlen(text);
+
+ event = smf_event_new();
+ if (event == NULL)
+ return (NULL);
+
+ /* "2 +" is for leading 0xFF 0xtype. */
+ event->midi_buffer_length = 2 + text_length + MAX_VLQ_LENGTH;
+ event->midi_buffer = (uint8_t*)malloc(event->midi_buffer_length);
+ if (event->midi_buffer == NULL) {
+ g_warning("Cannot allocate MIDI buffer structure: %s", strerror(errno));
+ smf_event_delete(event);
+
+ return (NULL);
+ }
+
+ event->midi_buffer[0] = 0xFF;
+ event->midi_buffer[1] = type;
+
+ vlq_length = smf_format_vlq(event->midi_buffer + 2, MAX_VLQ_LENGTH - 2, text_length);
+ copied_length = snprintf((char *)event->midi_buffer + vlq_length + 2, event->midi_buffer_length - vlq_length - 2, "%s", text);
+
+#ifndef NDEBUG
+ (void) copied_length; /* stop gcc warning about unusued vars for non-debug build */
+#else
+ assert(copied_length == text_length);
+#endif
+
+ event->midi_buffer_length = 2 + vlq_length + text_length;
+
+ return event;
+}
+
+/**
+ * Appends value, expressed as Variable Length Quantity, to event->track.
+ */
+static int
+write_vlq(smf_event_t *event, unsigned long value)
+{
+ unsigned char buf[MAX_VLQ_LENGTH];
+ int vlq_length;
+
+ vlq_length = smf_format_vlq(buf, MAX_VLQ_LENGTH, value);
+
+ return (track_append(event->track, buf, vlq_length));
+}
+
+/**
+ * Appends event time as Variable Length Quantity. Returns 0 if everything went ok,
+ * different value in case of error.
+ */
+static int
+write_event_time(smf_event_t *event)
+{
+ assert(event->delta_time_pulses >= 0);
+
+ return (write_vlq(event, event->delta_time_pulses));
+}
+
+static int
+write_sysex_contents(smf_event_t *event)
+{
+ int ret;
+ unsigned char sysex_status = 0xF0;
+
+ assert(smf_event_is_sysex(event));
+
+ ret = track_append(event->track, &sysex_status, 1);
+ if (ret)
+ return (ret);
+
+ /* -1, because length does not include status byte. */
+ ret = write_vlq(event, event->midi_buffer_length - 1);
+ if (ret)
+ return (ret);
+
+ ret = track_append(event->track, event->midi_buffer + 1, event->midi_buffer_length - 1);
+ if (ret)
+ return (ret);
+
+ return (0);
+}
+
+/**
+ * Appends contents of event->midi_buffer wrapped into 0xF7 MIDI event.
+ */
+static int
+write_escaped_event_contents(smf_event_t *event)
+{
+ int ret;
+ unsigned char escape_status = 0xF7;
+
+ if (smf_event_is_sysex(event))
+ return (write_sysex_contents(event));
+
+ ret = track_append(event->track, &escape_status, 1);
+ if (ret)
+ return (ret);
+
+ ret = write_vlq(event, event->midi_buffer_length);
+ if (ret)
+ return (ret);
+
+ ret = track_append(event->track, event->midi_buffer, event->midi_buffer_length);
+ if (ret)
+ return (ret);
+
+ return (0);
+}
+
+/**
+ * Appends contents of event->midi_buffer. Returns 0 if everything went 0,
+ * different value in case of error.
+ */
+static int
+write_event_contents(smf_event_t *event)
+{
+ if (smf_event_is_system_realtime(event) || smf_event_is_system_common(event))
+ return (write_escaped_event_contents(event));
+
+ return (track_append(event->track, event->midi_buffer, event->midi_buffer_length));
+}
+
+/**
+ * Writes out an event.
+ */
+static int
+write_event(smf_event_t *event)
+{
+ int ret;
+
+ ret = write_event_time(event);
+ if (ret)
+ return (ret);
+
+ ret = write_event_contents(event);
+ if (ret)
+ return (ret);
+
+ return (0);
+}
+
+/**
+ * Writes out MTrk header, except of MTrk chunk length, which is written by write_mtrk_length().
+ */
+static int
+write_mtrk_header(smf_track_t *track)
+{
+ struct chunk_header_struct mtrk_header;
+
+ memcpy(mtrk_header.id, "MTrk", 4);
+
+ return (track_append(track, &mtrk_header, sizeof(mtrk_header)));
+}
+
+/**
+ * Updates MTrk chunk length of a given track.
+ */
+static int
+write_mtrk_length(smf_track_t *track)
+{
+ struct chunk_header_struct *mtrk_header;
+
+ assert(track->file_buffer != NULL);
+ assert(track->file_buffer_length >= 6);
+
+ mtrk_header = (struct chunk_header_struct *)track->file_buffer;
+ mtrk_header->length = htonl(track->file_buffer_length - sizeof(struct chunk_header_struct));
+
+ return (0);
+}
+
+/**
+ * Writes out the track.
+ */
+static int
+write_track(smf_track_t *track)
+{
+ int ret;
+ smf_event_t *event;
+
+ ret = write_mtrk_header(track);
+ if (ret)
+ return (ret);
+
+ if (track->name) {
+ printf ("save track name [%s]\n", track->name);
+ smf_event_t *ev = smf_event_new_textual (0x03, track->name);
+ ev->delta_time_pulses = 0; /* time zero */
+ ev->track = track;
+ ret = write_event (ev);
+ ev->track = 0;
+ smf_event_delete (ev);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ if (track->instrument) {
+ printf ("save track instrument [%s]\n", track->instrument);
+ smf_event_t *ev = smf_event_new_textual (0x04, track->instrument);
+ ev->delta_time_pulses = 0; /* time zero */
+ ev->track = track;
+ ret = write_event (ev);
+ ev->track = 0;
+ smf_event_delete (ev);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ while ((event = smf_track_get_next_event(track)) != NULL) {
+ ret = write_event(event);
+ if (ret)
+ return (ret);
+ }
+
+ ret = write_mtrk_length(track);
+ if (ret)
+ return (ret);
+
+ return (0);
+}
+
+/**
+ * Takes smf->file_buffer and saves it to the file.
+ */
+static int
+write_file(smf_t *smf, FILE* stream)
+{
+ if (fwrite(smf->file_buffer, 1, smf->file_buffer_length, stream) != smf->file_buffer_length) {
+ g_warning("fwrite(3) failed: %s", strerror(errno));
+
+ return (-2);
+ }
+
+ return (0);
+}
+
+static void
+free_buffer(smf_t *smf)
+{
+ int i;
+ smf_track_t *track;
+
+ /* Clear the pointers. */
+ memset(smf->file_buffer, 0, smf->file_buffer_length);
+ free(smf->file_buffer);
+ smf->file_buffer = NULL;
+ smf->file_buffer_length = 0;
+
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ track = smf_get_track_by_number(smf, i);
+ assert(track);
+ track->file_buffer = NULL;
+ track->file_buffer_length = 0;
+ }
+}
+
+#ifndef NDEBUG
+
+/**
+ * \return Nonzero, if all pointers supposed to be NULL are NULL. Triggers assertion if not.
+ */
+static int
+pointers_are_clear(smf_t *smf)
+{
+ int i;
+
+ smf_track_t *track;
+ if (smf->file_buffer != NULL) {
+ fprintf (stderr, "SFB != null but == %p\n", smf->file_buffer);
+ }
+ assert(smf->file_buffer == NULL);
+ assert(smf->file_buffer_length == 0);
+
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ track = smf_get_track_by_number(smf, i);
+
+ assert(track != NULL);
+ assert(track->file_buffer == NULL);
+ assert(track->file_buffer_length == 0);
+ }
+
+ return (1);
+}
+
+#endif /* !NDEBUG */
+
+/**
+ * \return Nonzero, if event is End Of Track metaevent.
+ */
+int
+smf_event_is_eot(const smf_event_t *event)
+{
+ if (event->midi_buffer_length != 3)
+ return (0);
+
+ if (event->midi_buffer[0] != 0xFF || event->midi_buffer[1] != 0x2F || event->midi_buffer[2] != 0x00)
+ return (0);
+
+ return (1);
+}
+
+/**
+ * Check if SMF is valid and add missing EOT events.
+ *
+ * \return 0, if SMF is valid.
+ */
+static int
+smf_validate(smf_t *smf)
+{
+ int trackno, eot_found;
+ size_t eventno;
+ smf_track_t *track;
+ smf_event_t *event;
+
+ if (smf->format < 0 || smf->format > 2) {
+ g_warning("SMF error: smf->format is less than zero of greater than two.");
+ return (-1);
+ }
+
+ if (smf->number_of_tracks < 1) {
+ g_warning("SMF error: number of tracks is less than one.");
+ return (-2);
+ }
+
+ if (smf->format == 0 && smf->number_of_tracks > 1) {
+ g_warning("SMF error: format is 0, but number of tracks is more than one.");
+ return (-3);
+ }
+
+ if (smf->ppqn <= 0) {
+ g_warning("SMF error: PPQN has to be > 0.");
+ return (-4);
+ }
+
+ for (trackno = 1; trackno <= smf->number_of_tracks; trackno++) {
+ track = smf_get_track_by_number(smf, trackno);
+ assert(track);
+
+ eot_found = 0;
+
+ for (eventno = 1; eventno <= track->number_of_events; eventno++) {
+ event = smf_track_get_event_by_number(track, eventno);
+ assert(event);
+
+ if (!smf_event_is_valid(event)) {
+ g_warning("Event #%" G_GSIZE_FORMAT " on track #%d is invalid.", eventno, trackno);
+ return (-5);
+ }
+
+ if (smf_event_is_eot(event)) {
+ if (eot_found) {
+ g_warning("Duplicate End Of Track event on track #%d.", trackno);
+ return (-6);
+ }
+
+ eot_found = 1;
+ }
+ }
+
+ if (!eot_found) {
+ if (smf_track_add_eot_delta_pulses(track, 0)) {
+ g_warning("smf_track_add_eot_delta_pulses failed.");
+ return (-6);
+ }
+ }
+
+ }
+
+ return (0);
+}
+
+#ifndef NDEBUG
+
+#define CHECK(cond) if (!(cond)) { return -1; }
+
+static int
+check_smf_event_is_identical(const smf_event_t *a, const smf_event_t *b)
+{
+ CHECK(a->event_number == b->event_number);
+ CHECK(a->delta_time_pulses == b->delta_time_pulses);
+ CHECK(abs((long)(a->time_pulses - b->time_pulses)) <= 2);
+ CHECK(fabs(a->time_seconds - b->time_seconds) <= 0.01);
+ CHECK(a->track_number == b->track_number);
+ CHECK(a->midi_buffer_length == b->midi_buffer_length);
+ CHECK(memcmp(a->midi_buffer, b->midi_buffer, a->midi_buffer_length) == 0);
+ return 0;
+}
+
+static int
+check_smf_track_is_identical(const smf_track_t *a, const smf_track_t *b)
+{
+ size_t i;
+
+ CHECK(a->track_number == b->track_number);
+ CHECK(a->number_of_events == b->number_of_events);
+
+ for (i = 1; i <= a->number_of_events; i++)
+ check_smf_event_is_identical(smf_track_get_event_by_number(a, i), smf_track_get_event_by_number(b, i));
+
+ return 0;
+}
+
+static int
+check_smf_is_identical(const smf_t *a, const smf_t *b)
+{
+ int i;
+
+ CHECK(a->format == b->format);
+ CHECK(a->ppqn == b->ppqn);
+ CHECK(a->frames_per_second == b->frames_per_second);
+ CHECK(a->resolution == b->resolution);
+ CHECK(a->number_of_tracks == b->number_of_tracks);
+
+ for (i = 1; i <= a->number_of_tracks; i++)
+ check_smf_track_is_identical(smf_get_track_by_number(a, i), smf_get_track_by_number(b, i));
+
+ /* We do not need to compare tempos explicitly, as tempo is always computed from track contents. */
+ return 0;
+}
+
+static int
+check_smf_saved_correctly(const smf_t *smf, FILE* file)
+{
+ smf_t *saved;
+ int ret;
+
+ saved = smf_load (file);
+ if (!saved) {
+ ret = -1;
+ } else {
+ ret = check_smf_is_identical(smf, saved);
+ }
+
+ smf_delete(saved);
+ return (ret);
+}
+
+#endif /* !NDEBUG */
+
+/**
+ * Writes the contents of SMF to the file given.
+ * \param smf SMF.
+ * \param file File descriptor.
+ * \return 0, if saving was successfull.
+ */
+int
+smf_save(smf_t *smf, FILE* file)
+{
+ int i, error;
+ smf_track_t *track;
+
+ smf_rewind(smf);
+
+ assert(pointers_are_clear(smf));
+
+ if (smf_validate(smf))
+ return (-1);
+
+ if (write_mthd_header(smf))
+ return (-2);
+
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ track = smf_get_track_by_number(smf, i);
+
+ assert(track != NULL);
+
+ error = write_track(track);
+ if (error) {
+ free_buffer(smf);
+ return (error);
+ }
+ }
+
+ error = write_file(smf, file);
+
+ free_buffer(smf);
+
+ if (error)
+ return (error);
+
+#ifndef NDEBUG
+ if (check_smf_saved_correctly(smf, file)) {
+ g_warning("SMF warning: Did not save correctly, possible data loss.");
+ }
+#endif
+
+ return (0);
+}
+