summaryrefslogtreecommitdiff
path: root/libs/evoral/libsmf
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2019-10-25 13:41:33 -0600
committerPaul Davis <paul@linuxaudiosystems.com>2019-11-02 16:32:18 -0600
commiteaae38ba842c4acf079fbc6e405af433576ad16e (patch)
tree1e7c88f36564795f6bbe09cb2e0e3534da5c8763 /libs/evoral/libsmf
parenta855119bdd94aad90f4cfec3066a367b0675a8e9 (diff)
move evoral/src/* to evoral/
Diffstat (limited to 'libs/evoral/libsmf')
-rw-r--r--libs/evoral/libsmf/COPYING24
-rw-r--r--libs/evoral/libsmf/README4
-rw-r--r--libs/evoral/libsmf/smf.c1137
-rw-r--r--libs/evoral/libsmf/smf.h418
-rw-r--r--libs/evoral/libsmf/smf_decode.c646
-rw-r--r--libs/evoral/libsmf/smf_load.c984
-rw-r--r--libs/evoral/libsmf/smf_private.h81
-rw-r--r--libs/evoral/libsmf/smf_save.c691
-rw-r--r--libs/evoral/libsmf/smf_tempo.c454
-rw-r--r--libs/evoral/libsmf/smfsh.c1034
10 files changed, 5473 insertions, 0 deletions
diff --git a/libs/evoral/libsmf/COPYING b/libs/evoral/libsmf/COPYING
new file mode 100644
index 0000000000..79ec85f5bf
--- /dev/null
+++ b/libs/evoral/libsmf/COPYING
@@ -0,0 +1,24 @@
+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.
+
diff --git a/libs/evoral/libsmf/README b/libs/evoral/libsmf/README
new file mode 100644
index 0000000000..c1a403564c
--- /dev/null
+++ b/libs/evoral/libsmf/README
@@ -0,0 +1,4 @@
+This is a stripped down version of libsmf 1.2 by Edward Tomasz Napiera
+for internal use by Evoral. See COPYING for licensing information.
+
+The complete version can be found at <http://libsmf.sf.net>.
diff --git a/libs/evoral/libsmf/smf.c b/libs/evoral/libsmf/smf.c
new file mode 100644
index 0000000000..1fcdb8ec38
--- /dev/null
+++ b/libs/evoral/libsmf/smf.c
@@ -0,0 +1,1137 @@
+/*-
+ * 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
+ *
+ * Various functions.
+ *
+ */
+
+/* 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"
+
+/**
+ * Allocates new smf_t structure.
+ * \return pointer to smf_t or NULL.
+ */
+smf_t *
+smf_new(void)
+{
+ int cantfail;
+
+ smf_t *smf = (smf_t*)malloc(sizeof(smf_t));
+ if (smf == NULL) {
+ g_warning("Cannot allocate smf_t structure: %s", strerror(errno));
+ return (NULL);
+ }
+
+ memset(smf, 0, sizeof(smf_t));
+
+ smf->tracks_array = g_ptr_array_new();
+ assert(smf->tracks_array);
+
+ smf->tempo_array = g_ptr_array_new();
+ assert(smf->tempo_array);
+
+ cantfail = smf_set_ppqn(smf, 120);
+ assert(!cantfail);
+
+ cantfail = smf_set_format(smf, 0);
+ assert(!cantfail);
+
+ smf_init_tempo(smf);
+
+ return (smf);
+}
+
+/**
+ * Frees smf and all it's descendant structures.
+ */
+void
+smf_delete(smf_t *smf)
+{
+ /* Remove all the tracks, from last to first. */
+ while (smf->tracks_array->len > 0)
+ smf_track_delete((smf_track_t*)g_ptr_array_index(smf->tracks_array, smf->tracks_array->len - 1));
+
+ smf_fini_tempo(smf);
+
+ assert(smf->tracks_array->len == 0);
+ assert(smf->number_of_tracks == 0);
+ g_ptr_array_free(smf->tracks_array, TRUE);
+ g_ptr_array_free(smf->tempo_array, TRUE);
+
+ memset(smf, 0, sizeof(smf_t));
+ free(smf);
+}
+
+/**
+ * Allocates new smf_track_t structure.
+ * \return pointer to smf_track_t or NULL.
+ */
+smf_track_t *
+smf_track_new(void)
+{
+ smf_track_t *track = (smf_track_t*)malloc(sizeof(smf_track_t));
+ if (track == NULL) {
+ g_warning("Cannot allocate smf_track_t structure: %s", strerror(errno));
+ return (NULL);
+ }
+
+ memset(track, 0, sizeof(smf_track_t));
+ track->next_event_number = 0;
+
+ track->events_array = g_ptr_array_new();
+ assert(track->events_array);
+
+ return (track);
+}
+
+/**
+ * Detaches track from its smf and frees it.
+ */
+void
+smf_track_delete(smf_track_t *track)
+{
+ assert(track);
+ assert(track->events_array);
+
+ /* Remove all the events */
+ unsigned int i;
+ for (i = 0; i < track->events_array->len; ++i) {
+ smf_event_t* ev = (smf_event_t*)g_ptr_array_index(track->events_array, i);
+ free (ev->midi_buffer);
+ free (ev);
+ }
+
+ g_ptr_array_remove_range(track->events_array, 0, track->events_array->len);
+ track->number_of_events = 0;
+
+ if (track->smf)
+ smf_track_remove_from_smf(track);
+
+ assert(track->events_array->len == 0);
+ g_ptr_array_free(track->events_array, TRUE);
+
+ if (track->name) {
+ free (track->name);
+ }
+ if (track->instrument) {
+ free (track->instrument);
+ }
+
+ memset(track, 0, sizeof(smf_track_t));
+ free(track);
+}
+
+
+/**
+ * Appends smf_track_t to smf.
+ */
+void
+smf_add_track(smf_t *smf, smf_track_t *track)
+{
+#ifndef NDEBUG
+ int cantfail;
+#endif
+
+ assert(track->smf == NULL);
+
+ track->smf = smf;
+ g_ptr_array_add(smf->tracks_array, track);
+
+ smf->number_of_tracks++;
+ track->track_number = smf->number_of_tracks;
+
+ if (smf->number_of_tracks > 1) {
+#ifndef NDEBUG
+ cantfail = smf_set_format(smf, 1);
+ assert(!cantfail);
+#else
+ smf_set_format(smf, 1);
+#endif
+
+ }
+}
+
+/**
+ * Detaches track from the smf.
+ */
+void
+smf_track_remove_from_smf(smf_track_t *track)
+{
+ int i;
+ size_t j;
+ smf_track_t *tmp;
+ smf_event_t *ev;
+
+ assert(track->smf != NULL);
+
+ track->smf->number_of_tracks--;
+
+ assert(track->smf->tracks_array);
+ g_ptr_array_remove(track->smf->tracks_array, track);
+
+ /* Renumber the rest of the tracks, so they are consecutively numbered. */
+ for (i = track->track_number; i <= track->smf->number_of_tracks; i++) {
+ tmp = smf_get_track_by_number(track->smf, i);
+ tmp->track_number = i;
+
+ /*
+ * Events have track numbers too. I guess this wasn't a wise
+ * decision. ;-/
+ */
+ for (j = 1; j <= tmp->number_of_events; j++) {
+ ev = smf_track_get_event_by_number(tmp, j);
+ ev->track_number = i;
+ }
+ }
+
+ track->track_number = -1;
+ track->smf = NULL;
+}
+
+/**
+ * Allocates new smf_event_t structure. The caller is responsible for allocating
+ * event->midi_buffer, filling it with MIDI data and setting event->midi_buffer_length properly.
+ * Note that event->midi_buffer will be freed by smf_event_delete.
+ * \return pointer to smf_event_t or NULL.
+ */
+smf_event_t *
+smf_event_new(void)
+{
+ smf_event_t *event = (smf_event_t*)malloc(sizeof(smf_event_t));
+ if (event == NULL) {
+ g_warning("Cannot allocate smf_event_t structure: %s", strerror(errno));
+ return (NULL);
+ }
+
+ memset(event, 0, sizeof(smf_event_t));
+
+ event->delta_time_pulses = -1;
+ event->time_pulses = -1;
+ event->time_seconds = -1.0;
+ event->track_number = -1;
+
+ return (event);
+}
+
+/**
+ * Allocates an smf_event_t structure and fills it with "len" bytes copied
+ * from "midi_data".
+ * \param midi_data Pointer to MIDI data. It sill be copied to the newly allocated event->midi_buffer.
+ * \param len Length of the buffer. It must be proper MIDI event length, e.g. 3 for Note On event.
+ * \return Event containing MIDI data or NULL.
+ */
+smf_event_t *
+smf_event_new_from_pointer(const void *midi_data, size_t len)
+{
+ smf_event_t *event;
+
+ event = smf_event_new();
+ if (event == NULL)
+ return (NULL);
+
+ event->midi_buffer_length = len;
+ 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);
+ }
+
+ memcpy(event->midi_buffer, midi_data, len);
+
+ return (event);
+}
+
+/**
+ * Allocates an smf_event_t structure and fills it with at most three bytes of data.
+ * For example, if you need to create Note On event, do something like this:
+ *
+ * smf_event_new_from_bytes(0x90, 0x3C, 0x7f);
+ *
+ * To create event for MIDI message that is shorter than three bytes, do something
+ * like this:
+ *
+ * smf_event_new_from_bytes(0xC0, 0x42, -1);
+ *
+ * \param first_byte First byte of MIDI message. Must be valid status byte.
+ * \param second_byte Second byte of MIDI message or -1, if message is one byte long.
+ * \param third_byte Third byte of MIDI message or -1, if message is two bytes long.
+ * \return Event containing MIDI data or NULL.
+ */
+smf_event_t *
+smf_event_new_from_bytes(int first_byte, int second_byte, int third_byte)
+{
+ size_t len;
+
+ smf_event_t *event;
+
+ event = smf_event_new();
+ if (event == NULL)
+ return (NULL);
+
+ if (first_byte < 0) {
+ g_warning("First byte of MIDI message cannot be < 0");
+ smf_event_delete(event);
+
+ return (NULL);
+ }
+
+ if (first_byte > 255) {
+ g_warning("smf_event_new_from_bytes: first byte is %d, which is larger than 255.", first_byte);
+ return (NULL);
+ }
+
+ if (!is_status_byte(first_byte)) {
+ g_warning("smf_event_new_from_bytes: first byte is not a valid status byte.");
+ return (NULL);
+ }
+
+
+ if (second_byte < 0)
+ len = 1;
+ else if (third_byte < 0)
+ len = 2;
+ else
+ len = 3;
+
+ if (len > 1) {
+ if (second_byte > 255) {
+ g_warning("smf_event_new_from_bytes: second byte is %d, which is larger than 255.", second_byte);
+ return (NULL);
+ }
+
+ if (is_status_byte(second_byte)) {
+ g_warning("smf_event_new_from_bytes: second byte cannot be a status byte.");
+ return (NULL);
+ }
+ }
+
+ if (len > 2) {
+ if (third_byte > 255) {
+ g_warning("smf_event_new_from_bytes: third byte is %d, which is larger than 255.", third_byte);
+ return (NULL);
+ }
+
+ if (is_status_byte(third_byte)) {
+ g_warning("smf_event_new_from_bytes: third byte cannot be a status byte.");
+ return (NULL);
+ }
+ }
+
+ event->midi_buffer_length = len;
+ 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] = first_byte;
+ if (len > 1)
+ event->midi_buffer[1] = second_byte;
+ if (len > 2)
+ event->midi_buffer[2] = third_byte;
+
+ return (event);
+}
+
+/**
+ * Detaches event from its track and frees it.
+ */
+void
+smf_event_delete(smf_event_t *event)
+{
+ if (event->track != NULL)
+ smf_event_remove_from_track(event);
+
+ if (event->midi_buffer != NULL) {
+ memset(event->midi_buffer, 0, event->midi_buffer_length);
+ free(event->midi_buffer);
+ }
+
+ memset(event, 0, sizeof(smf_event_t));
+ free(event);
+}
+
+/**
+ * Used for sorting track->events_array.
+ */
+static gint
+events_array_compare_function(gconstpointer aa, gconstpointer bb)
+{
+ const smf_event_t *a, *b;
+
+ /* "The comparison function for g_ptr_array_sort() doesn't take the pointers
+ from the array as arguments, it takes pointers to the pointers in the array." */
+ a = (const smf_event_t *)*(const gpointer *)aa;
+ b = (const smf_event_t *)*(const gpointer *)bb;
+
+ if (a->time_pulses < b->time_pulses)
+ return (-1);
+
+ if (a->time_pulses > b->time_pulses)
+ return (1);
+
+ /*
+ * We need to preserve original order, otherwise things will break
+ * when there are several events with the same ->time_pulses.
+ * XXX: This is an ugly hack; we should remove sorting altogether.
+ */
+
+ if (a->event_number < b->event_number)
+ return (-1);
+
+ if (a->event_number > b->event_number)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * An assumption here is that if there is an EOT event, it will be at the end of the track.
+ */
+static void
+remove_eot_if_before_pulses(smf_track_t *track, size_t pulses)
+{
+ smf_event_t *event;
+
+ event = smf_track_get_last_event(track);
+
+ if (event == NULL)
+ return;
+
+ if (!smf_event_is_eot(event))
+ return;
+
+ if (event->time_pulses > pulses)
+ return;
+
+ smf_event_remove_from_track(event);
+}
+
+/**
+ * Adds the event to the track and computes ->delta_pulses. Note that it is faster
+ * to append events to the end of the track than to insert them in the middle.
+ * Usually you want to use smf_track_add_event_seconds or smf_track_add_event_pulses
+ * instead of this one. Event needs to have ->time_pulses and ->time_seconds already set.
+ * If you try to add event after an EOT, EOT event will be automatically deleted.
+ */
+void
+smf_track_add_event(smf_track_t *track, smf_event_t *event)
+{
+ size_t i, last_pulses = 0;
+
+ assert(track->smf != NULL);
+ assert(event->track == NULL);
+ assert(event->delta_time_pulses == -1);
+ assert(event->time_seconds >= 0.0);
+
+ remove_eot_if_before_pulses(track, event->time_pulses);
+
+ event->track = track;
+ event->track_number = track->track_number;
+
+ if (track->number_of_events == 0) {
+ assert(track->next_event_number == 0);
+ track->next_event_number = 1;
+ }
+
+ if (track->number_of_events > 0)
+ last_pulses = smf_track_get_last_event(track)->time_pulses;
+
+ track->number_of_events++;
+
+ /* Are we just appending element at the end of the track? */
+ if (last_pulses <= event->time_pulses) {
+ event->delta_time_pulses = event->time_pulses - last_pulses;
+ assert(event->delta_time_pulses >= 0);
+ g_ptr_array_add(track->events_array, event);
+ event->event_number = track->number_of_events;
+
+ /* We need to insert in the middle of the track. XXX: This is slow. */
+ } else {
+ /* Append, then sort according to ->time_pulses. */
+ g_ptr_array_add(track->events_array, event);
+ g_ptr_array_sort(track->events_array, events_array_compare_function);
+
+ /* Renumber entries and fix their ->delta_pulses. */
+ for (i = 1; i <= track->number_of_events; i++) {
+ smf_event_t *tmp = smf_track_get_event_by_number(track, i);
+ tmp->event_number = i;
+
+ if (tmp->delta_time_pulses != -1)
+ continue;
+
+ if (i == 1) {
+ tmp->delta_time_pulses = tmp->time_pulses;
+ } else {
+ tmp->delta_time_pulses = tmp->time_pulses -
+ smf_track_get_event_by_number(track, i - 1)->time_pulses;
+ assert(tmp->delta_time_pulses >= 0);
+ }
+ }
+
+ /* Adjust ->delta_time_pulses of the next event. */
+ if (event->event_number < track->number_of_events) {
+ smf_event_t *next_event = smf_track_get_event_by_number(track, event->event_number + 1);
+ assert(next_event);
+ assert(next_event->time_pulses >= event->time_pulses);
+ next_event->delta_time_pulses -= event->delta_time_pulses;
+ assert(next_event->delta_time_pulses >= 0);
+ }
+ }
+
+ if (smf_event_is_tempo_change_or_time_signature(event)) {
+ if (smf_event_is_last(event))
+ maybe_add_to_tempo_map(event);
+ else
+ smf_create_tempo_map_and_compute_seconds(event->track->smf);
+ }
+}
+
+/**
+ * Add End Of Track metaevent. Using it is optional, libsmf will automatically
+ * add EOT to the tracks during smf_save, with delta_pulses 0. If you try to add EOT
+ * in the middle of the track, it will fail and nonzero value will be returned.
+ * If you try to add EOT after another EOT event, it will be added, but the existing
+ * EOT event will be removed.
+ *
+ * \return 0 if everything went ok, nonzero otherwise.
+ */
+int
+smf_track_add_eot_delta_pulses(smf_track_t *track, uint32_t delta)
+{
+ smf_event_t *event;
+
+ event = smf_event_new_from_bytes(0xFF, 0x2F, 0x00);
+ if (event == NULL)
+ return (-1);
+
+ smf_track_add_event_delta_pulses(track, event, delta);
+
+ return (0);
+}
+
+int
+smf_track_add_eot_pulses(smf_track_t *track, size_t pulses)
+{
+ smf_event_t *event, *last_event;
+
+ last_event = smf_track_get_last_event(track);
+ if (last_event != NULL) {
+ if (last_event->time_pulses > pulses)
+ return (-2);
+ }
+
+ event = smf_event_new_from_bytes(0xFF, 0x2F, 0x00);
+ if (event == NULL)
+ return (-3);
+
+ smf_track_add_event_pulses(track, event, pulses);
+
+ return (0);
+}
+
+int
+smf_track_add_eot_seconds(smf_track_t *track, double seconds)
+{
+ smf_event_t *event, *last_event;
+
+ last_event = smf_track_get_last_event(track);
+ if (last_event != NULL) {
+ if (last_event->time_seconds > seconds)
+ return (-2);
+ }
+
+ event = smf_event_new_from_bytes(0xFF, 0x2F, 0x00);
+ if (event == NULL)
+ return (-1);
+
+ smf_track_add_event_seconds(track, event, seconds);
+
+ return (0);
+}
+
+/**
+ * Detaches event from its track.
+ */
+void
+smf_event_remove_from_track(smf_event_t *event)
+{
+ size_t i;
+ int was_last;
+ smf_event_t *tmp;
+ smf_track_t *track;
+
+ assert(event->track != NULL);
+ assert(event->track->smf != NULL);
+
+ track = event->track;
+ was_last = smf_event_is_last(event);
+
+ /* Adjust ->delta_time_pulses of the next event. */
+ if (event->event_number < track->number_of_events) {
+ tmp = smf_track_get_event_by_number(track, event->event_number + 1);
+ assert(tmp);
+ tmp->delta_time_pulses += event->delta_time_pulses;
+ }
+
+ track->number_of_events--;
+ g_ptr_array_remove(track->events_array, event);
+
+ if (track->number_of_events == 0)
+ track->next_event_number = 0;
+
+ /* Renumber the rest of the events, so they are consecutively numbered. */
+ for (i = event->event_number; i <= track->number_of_events; i++) {
+ tmp = smf_track_get_event_by_number(track, i);
+ tmp->event_number = i;
+ }
+
+ if (smf_event_is_tempo_change_or_time_signature(event)) {
+ /* XXX: This will cause problems, when there is more than one Tempo Change event at a given time. */
+ if (was_last)
+ remove_last_tempo_with_pulses(event->track->smf, event->time_pulses);
+ else
+ smf_create_tempo_map_and_compute_seconds(track->smf);
+ }
+
+ event->track = NULL;
+ event->event_number = 0;
+ event->delta_time_pulses = -1;
+ event->time_pulses = 0;
+ event->time_seconds = -1.0;
+}
+
+/**
+ * \return Nonzero if event is Tempo Change or Time Signature metaevent.
+ */
+int
+smf_event_is_tempo_change_or_time_signature(const smf_event_t *event)
+{
+ if (!smf_event_is_metadata(event))
+ return (0);
+
+ assert(event->midi_buffer_length >= 2);
+
+ if (event->midi_buffer[1] == 0x51 || event->midi_buffer[1] == 0x58)
+ return (1);
+
+ return (0);
+}
+
+/**
+ * Sets "Format" field of MThd header to the specified value. Note that you
+ * don't really need to use this, as libsmf will automatically change format
+ * from 0 to 1 when you add the second track.
+ * \param smf SMF.
+ * \param format 0 for one track per file, 1 for several tracks per file.
+ */
+int
+smf_set_format(smf_t *smf, int format)
+{
+ assert(format == 0 || format == 1);
+
+ if (smf->number_of_tracks > 1 && format == 0) {
+ g_warning("There is more than one track, cannot set format to 0.");
+ return (-1);
+ }
+
+ smf->format = format;
+
+ return (0);
+}
+
+/**
+ * Sets the PPQN ("Division") field of MThd header. This is mandatory, you
+ * should call it right after smf_new. Note that changing PPQN will change time_seconds
+ * of all the events.
+ * \param smf SMF.
+ * \param ppqn New PPQN.
+ */
+int
+smf_set_ppqn(smf_t *smf, uint16_t ppqn)
+{
+ smf->ppqn = ppqn;
+
+ return (0);
+}
+
+/**
+ * Returns next event from the track given and advances next event counter.
+ * Do not depend on End Of Track event being the last event on the track - it
+ * is possible that the track will not end with EOT if you haven't added it
+ * yet. EOTs are added automatically during smf_save().
+ *
+ * \return Event or NULL, if there are no more events left in this track.
+ */
+smf_event_t *
+smf_track_get_next_event(smf_track_t *track)
+{
+ smf_event_t *event, *next_event;
+
+ /* Track is empty? */
+ if (track->number_of_events == 0)
+ return (NULL);
+
+ /* End of track? */
+ if (track->next_event_number == 0)
+ return (NULL);
+
+ assert(track->next_event_number >= 1);
+
+ event = smf_track_get_event_by_number(track, track->next_event_number);
+
+ assert(event != NULL);
+
+ /* Is this the last event in the track? */
+ if (track->next_event_number < track->number_of_events) {
+ next_event = smf_track_get_event_by_number(track, track->next_event_number + 1);
+ assert(next_event);
+
+ track->time_of_next_event = next_event->time_pulses;
+ track->next_event_number++;
+ } else {
+ track->next_event_number = 0;
+ }
+
+ return (event);
+}
+
+/**
+ * Returns next event from the track given. Does not change next event counter,
+ * so repeatedly calling this routine will return the same event.
+ * \return Event or NULL, if there are no more events left in this track.
+ */
+static smf_event_t *
+smf_peek_next_event_from_track(smf_track_t *track)
+{
+ smf_event_t *event;
+
+ /* End of track? */
+ if (track->next_event_number == 0)
+ return (NULL);
+
+ assert(track->next_event_number >= 1);
+ assert(track->events_array->len != 0);
+
+ event = smf_track_get_event_by_number(track, track->next_event_number);
+
+ return (event);
+}
+
+/**
+ * \return Track with a given number or NULL, if there is no such track.
+ * Tracks are numbered consecutively starting from one.
+ */
+smf_track_t *
+smf_get_track_by_number(const smf_t *smf, int track_number)
+{
+ smf_track_t *track;
+
+ assert(track_number >= 1);
+
+ if (track_number > smf->number_of_tracks)
+ return (NULL);
+
+ track = (smf_track_t *)g_ptr_array_index(smf->tracks_array, track_number - 1);
+
+ assert(track);
+
+ return (track);
+}
+
+/**
+ * \return Event with a given number or NULL, if there is no such event.
+ * Events are numbered consecutively starting from one.
+ */
+smf_event_t *
+smf_track_get_event_by_number(const smf_track_t *track, size_t event_number)
+{
+ smf_event_t *event;
+
+ assert(event_number >= 1);
+
+ if (event_number > track->number_of_events)
+ return (NULL);
+
+ event = (smf_event_t*)g_ptr_array_index(track->events_array, event_number - 1);
+
+ assert(event);
+
+ return (event);
+}
+
+/**
+ * \return Last event on the track or NULL, if track is empty.
+ */
+smf_event_t *
+smf_track_get_last_event(const smf_track_t *track)
+{
+ smf_event_t *event;
+
+ if (track->number_of_events == 0)
+ return (NULL);
+
+ event = smf_track_get_event_by_number(track, track->number_of_events);
+
+ return (event);
+}
+
+/**
+ * Searches for track that contains next event, in time order. In other words,
+ * returns the track that contains event that should be played next.
+ * \return Track with next event or NULL, if there are no events left.
+ */
+smf_track_t *
+smf_find_track_with_next_event(smf_t *smf)
+{
+ int i;
+ size_t min_time = 0;
+ smf_track_t *track = NULL, *min_time_track = NULL;
+
+ /* Find track with event that should be played next. */
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ track = smf_get_track_by_number(smf, i);
+
+ assert(track);
+
+ /* No more events in this track? */
+ if (track->next_event_number == 0)
+ continue;
+
+ if (track->time_of_next_event < min_time || min_time_track == NULL) {
+ min_time = track->time_of_next_event;
+ min_time_track = track;
+ }
+ }
+
+ return (min_time_track);
+}
+
+/**
+ * \return Next event, in time order, or NULL, if there are none left.
+ */
+smf_event_t *
+smf_get_next_event(smf_t *smf)
+{
+ smf_event_t *event;
+ smf_track_t *track = smf_find_track_with_next_event(smf);
+
+ if (track == NULL) {
+#if 0
+ g_debug("End of the song.");
+#endif
+
+ return (NULL);
+ }
+
+ event = smf_track_get_next_event(track);
+
+ assert(event != NULL);
+
+ event->track->smf->last_seek_position = -1.0;
+
+ return (event);
+}
+
+/**
+ * Advance the "next event counter". This is functionally the same as calling
+ * smf_get_next_event and ignoring the return value.
+ */
+void
+smf_skip_next_event(smf_t *smf)
+{
+ smf_event_t *ignored = smf_get_next_event(smf);
+ (void) ignored;
+}
+
+/**
+ * \return Next event, in time order, or NULL, if there are none left. Does
+ * not advance position in song.
+ */
+smf_event_t *
+smf_peek_next_event(smf_t *smf)
+{
+ smf_event_t *event;
+ smf_track_t *track = smf_find_track_with_next_event(smf);
+
+ if (track == NULL) {
+#if 0
+ g_debug("End of the song.");
+#endif
+
+ return (NULL);
+ }
+
+ event = smf_peek_next_event_from_track(track);
+
+ assert(event != NULL);
+
+ return (event);
+}
+
+/**
+ * Rewinds the SMF. What that means is, after calling this routine, smf_get_next_event
+ * will return first event in the song.
+ */
+void
+smf_rewind(smf_t *smf)
+{
+ int i;
+ smf_track_t *track = NULL;
+ smf_event_t *event;
+
+ assert(smf);
+
+ smf->last_seek_position = 0.0;
+
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ track = smf_get_track_by_number(smf, i);
+
+ assert(track != NULL);
+
+ if (track->number_of_events > 0) {
+ track->next_event_number = 1;
+ event = smf_peek_next_event_from_track(track);
+ assert(event);
+ track->time_of_next_event = event->time_pulses;
+ } else {
+ track->next_event_number = 0;
+ track->time_of_next_event = 0;
+#if 0
+ g_warning("Warning: empty track.");
+#endif
+ }
+ }
+}
+
+/**
+ * Seeks the SMF to the given event. After calling this routine, smf_get_next_event
+ * will return the event that was the second argument of this call.
+ */
+int
+smf_seek_to_event(smf_t *smf, const smf_event_t *target)
+{
+ smf_event_t *event;
+
+ smf_rewind(smf);
+
+#if 0
+ g_debug("Seeking to event %d, track %d.", target->event_number, target->track->track_number);
+#endif
+
+ for (;;) {
+ event = smf_peek_next_event(smf);
+
+ /* There can't be NULL here, unless "target" is not in this smf. */
+ assert(event);
+
+ if (event != target)
+ smf_skip_next_event(smf);
+ else
+ break;
+ }
+
+ smf->last_seek_position = event->time_seconds;
+
+ return (0);
+}
+
+/**
+ * Seeks the SMF to the given position. For example, after seeking to 1.0 seconds,
+ * smf_get_next_event will return first event that happens after the first second of song.
+ */
+int
+smf_seek_to_seconds(smf_t *smf, double seconds)
+{
+ smf_event_t *event;
+
+ assert(seconds >= 0.0);
+
+ if (seconds == smf->last_seek_position) {
+#if 0
+ g_debug("Avoiding seek to %f seconds.", seconds);
+#endif
+ return (0);
+ }
+
+ smf_rewind(smf);
+
+#if 0
+ g_debug("Seeking to %f seconds.", seconds);
+#endif
+
+ for (;;) {
+ event = smf_peek_next_event(smf);
+
+ if (event == NULL) {
+ g_warning("Trying to seek past the end of song.");
+ return (-1);
+ }
+
+ if (event->time_seconds < seconds)
+ smf_skip_next_event(smf);
+ else
+ break;
+ }
+
+ smf->last_seek_position = seconds;
+
+ return (0);
+}
+
+/**
+ * Seeks the SMF to the given position. For example, after seeking to 10 pulses,
+ * smf_get_next_event will return first event that happens after the first ten pulses.
+ */
+int
+smf_seek_to_pulses(smf_t *smf, size_t pulses)
+{
+ smf_event_t *event;
+
+ smf_rewind(smf);
+
+#if 0
+ g_debug("Seeking to %d pulses.", pulses);
+#endif
+
+ for (;;) {
+ event = smf_peek_next_event(smf);
+
+ if (event == NULL) {
+ g_warning("Trying to seek past the end of song.");
+ return (-1);
+ }
+
+ if (event->time_pulses < pulses)
+ smf_skip_next_event(smf);
+ else
+ break;
+ }
+
+ smf->last_seek_position = event->time_seconds;
+
+ return (0);
+}
+
+/**
+ * \return Length of SMF, in pulses.
+ */
+size_t
+smf_get_length_pulses(const smf_t *smf)
+{
+ int i;
+ size_t pulses = 0;
+
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ smf_track_t *track;
+ smf_event_t *event;
+
+ track = smf_get_track_by_number(smf, i);
+ assert(track);
+
+ event = smf_track_get_last_event(track);
+ /* Empty track? */
+ if (event == NULL)
+ continue;
+
+ if (event->time_pulses > pulses)
+ pulses = event->time_pulses;
+ }
+
+ return (pulses);
+}
+
+/**
+ * \return Length of SMF, in seconds.
+ */
+double
+smf_get_length_seconds(const smf_t *smf)
+{
+ int i;
+ double seconds = 0.0;
+
+ for (i = 1; i <= smf->number_of_tracks; i++) {
+ smf_track_t *track;
+ smf_event_t *event;
+
+ track = smf_get_track_by_number(smf, i);
+ assert(track);
+
+ event = smf_track_get_last_event(track);
+ /* Empty track? */
+ if (event == NULL)
+ continue;
+
+ if (event->time_seconds > seconds)
+ seconds = event->time_seconds;
+ }
+
+ return (seconds);
+}
+
+/**
+ * \return Nonzero, if there are no events in the SMF after this one.
+ * Note that may be more than one "last event", if they occur at the same time.
+ */
+int
+smf_event_is_last(const smf_event_t *event)
+{
+ if (smf_get_length_pulses(event->track->smf) <= event->time_pulses)
+ return (1);
+
+ return (0);
+}
+
+/**
+ * \return Version of libsmf.
+ */
+const char *
+smf_get_version(void)
+{
+ return (SMF_VERSION);
+}
+
diff --git a/libs/evoral/libsmf/smf.h b/libs/evoral/libsmf/smf.h
new file mode 100644
index 0000000000..82647779e8
--- /dev/null
+++ b/libs/evoral/libsmf/smf.h
@@ -0,0 +1,418 @@
+/*-
+ * 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
+ *
+ * Public interface declaration for libsmf, Standard MIDI File format library.
+ */
+
+/**
+ *
+ * \page libsmf libsmf - general usage instructions
+ *
+ * An smf_t structure represents a "song". Every valid smf contains one or more tracks.
+ * Tracks contain zero or more events. Libsmf doesn't care about actual MIDI data, as long
+ * as it is valid from the MIDI specification point of view - it may be realtime message,
+ * SysEx, whatever.
+ *
+ * The only field in smf_t, smf_track_t, smf_event_t and smf_tempo_t structures your
+ * code may modify is event->midi_buffer and event->midi_buffer_length. Do not modify
+ * other fields, _ever_. You may read them, though. Do not declare static instances
+ * of these types, i.e. never do something like this: "smf_t smf;". Always use
+ * "smf_t *smf = smf_new();". The same applies to smf_track_t and smf_event_t.
+ *
+ * Say you want to load a Standard MIDI File (.mid) file and play it back somehow.
+ * This is (roughly) how you do this:
+ *
+ * \code
+ * smf_t *smf;
+ * smf_event_t *event;
+ *
+ * smf = smf_load(file_name);
+ * if (smf == NULL) {
+ * Whoops, something went wrong.
+ * return;
+ * }
+ *
+ * while ((event = smf_get_next_event(smf)) != NULL) {
+ * if (smf_event_is_metadata(event))
+ * continue;
+ *
+ * wait until event->time_seconds.
+ * feed_to_midi_output(event->midi_buffer, event->midi_buffer_length);
+ * }
+ *
+ * smf_delete(smf);
+ *
+ * \endcode
+ *
+ * Saving works like this:
+ *
+ * \code
+ *
+ * smf_t *smf;
+ * smf_track_t *track;
+ * smf_event_t *event;
+ *
+ * smf = smf_new();
+ * if (smf == NULL) {
+ * Whoops.
+ * return;
+ * }
+ *
+ * for (int i = 1; i <= number of tracks; i++) {
+ * track = smf_track_new();
+ * if (track == NULL) {
+ * Whoops.
+ * return;
+ * }
+ *
+ * smf_add_track(smf, track);
+ *
+ * for (int j = 1; j <= number of events you want to put into this track; j++) {
+ * event = smf_event_new_from_pointer(your MIDI message, message length);
+ * if (event == NULL) {
+ * Whoops.
+ * return;
+ * }
+ *
+ * smf_track_add_event_seconds(track, event, seconds since start of the song);
+ * }
+ * }
+ *
+ * ret = smf_save(smf, file_name);
+ * if (ret) {
+ * Whoops, saving failed for some reason.
+ * return;
+ * }
+ *
+ * smf_delete(smf);
+ *
+ * \endcode
+ *
+ * There are two basic ways of getting MIDI data out of smf - sequential or by track/event number.
+ * You may mix them if you need to. First one is used in the example above - seek to the point
+ * from which you want the playback to start (using smf_seek_to_seconds(), smf_seek_to_pulses()
+ * or smf_seek_to_event()) and then do smf_get_next_event() in loop, until it returns NULL.
+ * Calling smf_load() causes the smf to be rewound to the start of the song.
+ *
+ * Getting events by number works like this:
+ *
+ * \code
+ *
+ * smf_track_t *track = smf_get_track_by_number(smf, track_number);
+ * smf_event_t *event = smf_track_get_event_by_number(track, event_number);
+ *
+ * \endcode
+ *
+ * To create new event, use smf_event_new(), smf_event_new_from_pointer() or
+ * smf_event_new_from_bytes(). First one creates an empty event - you need to manually allocate
+ * (using malloc(3)) buffer for MIDI data, write MIDI data into it, put the address of that
+ * buffer into event->midi_buffer, and the length of MIDI data into event->midi_buffer_length.
+ * Note that deleting the event (using smf_event_delete()) will free the buffer.
+ *
+ * Second form does most of this for you: it takes an address of the buffer containing MIDI data,
+ * allocates storage and copies MIDI data into it.
+ *
+ * Third form is useful for manually creating short events, up to three bytes in length, for
+ * example Note On or Note Off events. It simply takes three bytes and creates MIDI event
+ * containing them. If you need to create MIDI message that takes only two bytes, pass -1 as
+ * the third byte. For one byte message (System Realtime), pass -1 as second and third byte.
+ *
+ * To free an event, use smf_event_delete().
+ *
+ * To add event to the track, use smf_track_add_event_delta_pulses(), smf_track_add_event_pulses(),
+ * or smf_track_add_event_seconds(). The difference between them is in the way you specify the
+ * time of the event - with the first one, you specify it as an interval, in pulses, from the
+ * previous event in this track; with the second one, you specify it as pulses from the start
+ * of the song, and with the last one, you specify it as seconds from the start of the song.
+ * Obviously, the first version can only append events at the end of the track.
+ *
+ * To remove an event from the track it's attached to, use smf_event_remove_from_track().
+ * You may want to free the event (using smf_event_delete()) afterwards.
+ *
+ * To create new track, use smf_track_new(). To add track to the smf, use smf_add_track().
+ * To remove track from its smf, use smf_track_remove_from_smf(). To free the track structure,
+ * use smf_track_delete().
+ *
+ * Note that libsmf keeps things consistent. If you free (using smf_track_delete()) a track
+ * that is attached to an smf and contains events, libsmf will detach the events, free them,
+ * detach the track, free it etc.
+ *
+ * Tracks and events are numbered consecutively, starting from one. If you remove a track
+ * or event, the rest of tracks/events will get renumbered. To get the number of a given
+ * event in its track, use event->event_number. To get the number of track in its smf, use
+ * track->track_number. To get the number of events in the track, use track->number_of_events.
+ * To get the number of tracks in the smf, use smf->number_of_tracks.
+ *
+ * In SMF File Format, each track has to end with End Of Track metaevent. If you load SMF file
+ * using smf_load(), that will be the case. If you want to create or edit an SMF, you don't
+ * need to worry about EOT events; libsmf automatically takes care of them for you. If you
+ * try to save an SMF with tracks that do not end with EOTs, smf_save() will append them.
+ * If you try to add event that happens after EOT metaevent, libsmf will remove the EOT.
+ * If you want to add EOT manually, you can, of course, using smf_track_add_eot_seconds()
+ * or smf_track_add_eot_pulses().
+ *
+ * Each event carries three time values - event->time_seconds, which is seconds since
+ * the start of the song, event->time_pulses, which is PPQN clocks since the start of
+ * the song, and event->delta_pulses, which is PPQN clocks since the previous event
+ * in that track. These values are invalid if the event is not attached to the track.
+ * If event is attached, all three values are valid. Time of the event is specified when
+ * adding the event (using smf_track_add_event_seconds(), smf_track_add_event_pulses() or
+ * smf_track_add_event_delta_pulses()); the remaining two values are computed from that.
+ *
+ * Tempo related stuff happens automatically - when you add a metaevent that is Tempo PropertyChange or
+ * Time Signature, libsmf adds that event to the tempo map. If you remove Tempo PropertyChange event
+ * that is in the middle of the song, the rest of the events will have their event->time_seconds
+ * recomputed from event->time_pulses before smf_event_remove_from_track() function returns.
+ * Adding Tempo PropertyChange in the middle of the song works in a similar way.
+ *
+ * MIDI data (event->midi_buffer) is always kept in normalized form - it always begins with
+ * status byte (no running status), there are no System Realtime events embedded in them etc.
+ * Events like SysExes are in "on the wire" form, without embedded length that is used in SMF
+ * file format. Obviously libsmf "normalizes" MIDI data during loading and "denormalizes" (adding
+ * length to SysExes, escaping System Common and System Realtime messages etc) during writing.
+ *
+ * Note that you always have to first add the track to smf, and then add events to the track.
+ * Doing it the other way around will trip asserts. Also, try to add events at the end of the
+ * track and remove them from the end of the track, that's much more efficient.
+ *
+ * All the libsmf functions have prefix "smf_". First argument for routines whose names start
+ * with "smf_event_" is "smf_event_t *", for routines whose names start with "smf_track_" -
+ * "smf_track_t *", and for plain "smf_" - "smf_t *". The only exception are smf_whatever_new
+ * routines. Library does not use any global variables and is thread-safe, as long as you
+ * don't try to work on the same SMF (smf_t and its descendant tracks and events) from several
+ * threads at once without protecting it with mutex. Library depends on glib and nothing else.
+ * License is BSD, two clause, which basically means you can use it freely in your software,
+ * both Open Source (including GPL) and closed source.
+ *
+ */
+
+#ifndef SMF_H
+#define SMF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <glib.h>
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+#define WARN_UNUSED_RESULT __attribute__ ((warn_unused_result))
+#else
+#define WARN_UNUSED_RESULT
+#endif
+
+/** Represents a "song", that is, collection of one or more tracks. */
+struct smf_struct {
+ int format;
+
+ /** These fields are extracted from "division" field of MThd header.
+ * Valid is _either_ ppqn or frames_per_second/resolution. */
+ uint16_t ppqn;
+ int frames_per_second;
+ int resolution;
+ int number_of_tracks;
+
+ /** These are private fields using only by loading and saving routines. */
+ FILE *stream;
+ void *file_buffer;
+ size_t file_buffer_length;
+ size_t next_chunk_offset;
+ int expected_number_of_tracks;
+
+ /** Private, used by smf.c. */
+ GPtrArray *tracks_array;
+ double last_seek_position;
+
+ /** Private, used by smf_tempo.c. */
+ /** Array of pointers to smf_tempo_struct. */
+ GPtrArray *tempo_array;
+};
+
+typedef struct smf_struct smf_t;
+
+/** Describes a single tempo or time signature change. */
+struct smf_tempo_struct {
+ size_t time_pulses;
+ double time_seconds;
+ int microseconds_per_quarter_note;
+ int numerator;
+ int denominator;
+ int clocks_per_click;
+ int notes_per_note;
+};
+
+typedef struct smf_tempo_struct smf_tempo_t;
+
+/** Represents a single track. */
+struct smf_track_struct {
+ smf_t *smf;
+
+ int track_number;
+ size_t number_of_events;
+ /* this will be set from the SMF file if present, during loading */
+ char* name;
+ char* instrument;
+ /** These are private fields using only by loading and saving routines. */
+ void *file_buffer;
+ size_t file_buffer_length;
+ int last_status; /* Used for "running status". */
+
+ /** Private, used by smf.c. */
+ /** Offset into buffer, used in parse_next_event(). */
+ size_t next_event_offset;
+ size_t next_event_number;
+
+ /** Absolute time of next event on events_queue. */
+ size_t time_of_next_event;
+ GPtrArray *events_array;
+};
+
+typedef struct smf_track_struct smf_track_t;
+
+/** Represents a single MIDI event or metaevent. */
+struct smf_event_struct {
+ /** Pointer to the track, or NULL if event is not attached. */
+ smf_track_t *track;
+
+ /** Number of this event in the track. Events are numbered consecutively, starting from 1. */
+ size_t event_number;
+
+ /** Note that the time fields are invalid, if event is not attached to a track. */
+ /** Time, in pulses, since the previous event on this track. */
+ int32_t delta_time_pulses;
+
+ /** Time, in pulses, since the start of the song. */
+ size_t time_pulses;
+
+ /** Time, in seconds, since the start of the song. */
+ double time_seconds;
+
+ /** Tracks are numbered consecutively, starting from 1. */
+ int track_number;
+
+ /** Pointer to the buffer containing MIDI message. This is freed by smf_event_delete. */
+ uint8_t *midi_buffer;
+
+ /** Length of the MIDI message in the buffer, in bytes. */
+ size_t midi_buffer_length;
+};
+
+typedef struct smf_event_struct smf_event_t;
+
+/* Routines for manipulating smf_t. */
+smf_t *smf_new(void) WARN_UNUSED_RESULT;
+void smf_delete(smf_t *smf);
+
+int smf_set_format(smf_t *smf, int format) WARN_UNUSED_RESULT;
+int smf_set_ppqn(smf_t *smf, uint16_t ppqn) WARN_UNUSED_RESULT;
+
+char *smf_decode(const smf_t *smf) WARN_UNUSED_RESULT;
+
+smf_track_t *smf_get_track_by_number(const smf_t *smf, int track_number) WARN_UNUSED_RESULT;
+
+smf_event_t *smf_peek_next_event(smf_t *smf) WARN_UNUSED_RESULT;
+smf_event_t *smf_get_next_event(smf_t *smf) WARN_UNUSED_RESULT;
+void smf_skip_next_event(smf_t *smf);
+
+void smf_rewind(smf_t *smf);
+int smf_seek_to_seconds(smf_t *smf, double seconds) WARN_UNUSED_RESULT;
+int smf_seek_to_pulses(smf_t *smf, size_t pulses) WARN_UNUSED_RESULT;
+int smf_seek_to_event(smf_t *smf, const smf_event_t *event) WARN_UNUSED_RESULT;
+
+size_t smf_get_length_pulses(const smf_t *smf) WARN_UNUSED_RESULT;
+double smf_get_length_seconds(const smf_t *smf) WARN_UNUSED_RESULT;
+int smf_event_is_last(const smf_event_t *event) WARN_UNUSED_RESULT;
+
+void smf_add_track(smf_t *smf, smf_track_t *track);
+void smf_track_remove_from_smf(smf_track_t *track);
+
+/* Routines for manipulating smf_track_t. */
+smf_track_t *smf_track_new(void) WARN_UNUSED_RESULT;
+void smf_track_delete(smf_track_t *track);
+
+smf_event_t *smf_track_get_next_event(smf_track_t *track) WARN_UNUSED_RESULT;
+smf_event_t *smf_track_get_event_by_number(const smf_track_t *track, size_t num) WARN_UNUSED_RESULT;
+smf_event_t *smf_track_get_last_event(const smf_track_t *track) WARN_UNUSED_RESULT;
+
+void smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, uint32_t delta);
+void smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, size_t pulses);
+void smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds);
+int smf_track_add_eot_delta_pulses(smf_track_t *track, uint32_t delta) WARN_UNUSED_RESULT;
+int smf_track_add_eot_pulses(smf_track_t *track, size_t pulses) WARN_UNUSED_RESULT;
+int smf_track_add_eot_seconds(smf_track_t *track, double seconds) WARN_UNUSED_RESULT;
+void smf_event_remove_from_track(smf_event_t *event);
+
+/* Routines for manipulating smf_event_t. */
+smf_event_t *smf_event_new(void) WARN_UNUSED_RESULT;
+smf_event_t *smf_event_new_from_pointer(const void *midi_data, size_t len) WARN_UNUSED_RESULT;
+smf_event_t *smf_event_new_from_bytes(int byte1, int byte2, int byte3) WARN_UNUSED_RESULT;
+smf_event_t *smf_event_new_textual(int type, const char *text);
+void smf_event_delete(smf_event_t *event);
+
+int smf_event_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_is_metadata(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_is_system_realtime(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_is_system_common(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_is_sysex(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_is_eot(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_is_textual(const smf_event_t *event) WARN_UNUSED_RESULT;
+char *smf_event_decode(const smf_event_t *event) WARN_UNUSED_RESULT;
+char *smf_event_extract_text(const smf_event_t *event) WARN_UNUSED_RESULT;
+
+/* Routines for dealing with Variable Length Quantities (VLQ's).
+ Slightly odd names reflect original static names within libsmf
+ */
+int smf_format_vlq (unsigned char *buf, int length, unsigned long value);
+int smf_extract_vlq(const unsigned char *buf, const size_t buffer_length, uint32_t *value, uint32_t *len);
+
+/* Routines for loading SMF files. */
+smf_t *smf_load(FILE *) WARN_UNUSED_RESULT;
+smf_t *smf_load_from_memory(void *buffer, const size_t buffer_length) WARN_UNUSED_RESULT;
+
+/* Routine for writing SMF files. */
+int smf_save(smf_t *smf, FILE *file) WARN_UNUSED_RESULT;
+
+/* Routines for manipulating smf_tempo_t. */
+smf_tempo_t *smf_get_tempo_by_pulses(const smf_t *smf, size_t pulses) WARN_UNUSED_RESULT;
+smf_tempo_t *smf_get_tempo_by_seconds(const smf_t *smf, double seconds) WARN_UNUSED_RESULT;
+int smf_get_tempo_count (const smf_t *smf) WARN_UNUSED_RESULT;
+smf_tempo_t *smf_get_tempo_by_number(const smf_t *smf, size_t number) WARN_UNUSED_RESULT;
+smf_tempo_t *smf_get_last_tempo(const smf_t *smf) WARN_UNUSED_RESULT;
+
+const char *smf_get_version(void) WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SMF_H */
+
diff --git a/libs/evoral/libsmf/smf_decode.c b/libs/evoral/libsmf/smf_decode.c
new file mode 100644
index 0000000000..4bd774f345
--- /dev/null
+++ b/libs/evoral/libsmf/smf_decode.c
@@ -0,0 +1,646 @@
+/*-
+ * 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
+ *
+ * Event decoding routines.
+ *
+ */
+
+#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 <stdint.h>
+#include "smf.h"
+#include "smf_private.h"
+
+#define BUFFER_SIZE 1024
+
+/**
+ * \return Nonzero if event is metaevent. You should never send metaevents;
+ * they are not really MIDI messages. They carry information like track title,
+ * time signature etc.
+ */
+int
+smf_event_is_metadata(const smf_event_t *event)
+{
+ assert(event->midi_buffer);
+ assert(event->midi_buffer_length > 0);
+
+ if (event->midi_buffer[0] == 0xFF)
+ return (1);
+
+ return (0);
+}
+
+/**
+ * \return Nonzero if event is System Realtime.
+ */
+int
+smf_event_is_system_realtime(const smf_event_t *event)
+{
+ assert(event->midi_buffer);
+ assert(event->midi_buffer_length > 0);
+
+ if (smf_event_is_metadata(event))
+ return (0);
+
+ if (event->midi_buffer[0] >= 0xF8)
+ return (1);
+
+ return (0);
+}
+
+/**
+ * \return Nonzero if event is System Common.
+ */
+int
+smf_event_is_system_common(const smf_event_t *event)
+{
+ assert(event->midi_buffer);
+ assert(event->midi_buffer_length > 0);
+
+ if (event->midi_buffer[0] >= 0xF0 && event->midi_buffer[0] <= 0xF7)
+ return (1);
+
+ return (0);
+}
+/**
+ * \return Nonzero if event is SysEx message.
+ */
+int
+smf_event_is_sysex(const smf_event_t *event)
+{
+ assert(event->midi_buffer);
+ assert(event->midi_buffer_length > 0);
+
+ if (event->midi_buffer[0] == 0xF0)
+ return (1);
+
+ return (0);
+}
+
+static char *
+smf_event_decode_textual(const smf_event_t *event, const char *name)
+{
+ int off = 0;
+ char *buf, *extracted;
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode_textual: malloc failed.");
+ return (NULL);
+ }
+
+ extracted = smf_event_extract_text(event);
+ if (extracted == NULL) {
+ free(buf);
+ return (NULL);
+ }
+
+ snprintf(buf + off, BUFFER_SIZE - off, "%s: %s", name, extracted);
+
+ return (buf);
+}
+
+static char *
+smf_event_decode_metadata(const smf_event_t *event)
+{
+ int off = 0, mspqn, flats, isminor;
+ char *buf;
+
+ static const char *const major_keys[] = {"Fb", "Cb", "Gb", "Db", "Ab",
+ "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", "G#"};
+
+ static const char *const minor_keys[] = {"Dbm", "Abm", "Ebm", "Bbm", "Fm",
+ "Cm", "Gm", "Dm", "Am", "Em", "Bm", "F#m", "C#m", "G#m", "D#m", "A#m", "E#m"};
+
+ assert(smf_event_is_metadata(event));
+
+ switch (event->midi_buffer[1]) {
+ case 0x01:
+ return (smf_event_decode_textual(event, "Text"));
+
+ case 0x02:
+ return (smf_event_decode_textual(event, "Copyright"));
+
+ case 0x03:
+ return (smf_event_decode_textual(event, "Sequence/Track Name"));
+
+ case 0x04:
+ return (smf_event_decode_textual(event, "Instrument"));
+
+ case 0x05:
+ return (smf_event_decode_textual(event, "Lyric"));
+
+ case 0x06:
+ return (smf_event_decode_textual(event, "Marker"));
+
+ case 0x07:
+ return (smf_event_decode_textual(event, "Cue Point"));
+
+ case 0x08:
+ return (smf_event_decode_textual(event, "Program Name"));
+
+ case 0x09:
+ return (smf_event_decode_textual(event, "Device (Port) Name"));
+
+ default:
+ break;
+ }
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode_metadata: malloc failed.");
+ return (NULL);
+ }
+
+ switch (event->midi_buffer[1]) {
+ case 0x00:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Sequence number");
+ break;
+
+ /* http://music.columbia.edu/pipermail/music-dsp/2004-August/061196.html */
+ case 0x20:
+ if (event->midi_buffer_length < 4) {
+ g_warning("smf_event_decode_metadata: truncated MIDI message.");
+ goto error;
+ }
+
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Channel Prefix: %d", event->midi_buffer[3]);
+ break;
+
+ case 0x21:
+ if (event->midi_buffer_length < 4) {
+ g_warning("smf_event_decode_metadata: truncated MIDI message.");
+ goto error;
+ }
+
+ off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Port: %d", event->midi_buffer[3]);
+ break;
+
+ case 0x2F:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "End Of Track");
+ break;
+
+ case 0x51:
+ if (event->midi_buffer_length < 6) {
+ g_warning("smf_event_decode_metadata: truncated MIDI message.");
+ goto error;
+ }
+
+ mspqn = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5];
+
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Tempo: %d microseconds per quarter note, %.2f BPM",
+ mspqn, 60000000.0 / (double)mspqn);
+ break;
+
+ case 0x54:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "SMPTE Offset");
+ break;
+
+ case 0x58:
+ if (event->midi_buffer_length < 7) {
+ g_warning("smf_event_decode_metadata: truncated MIDI message.");
+ goto error;
+ }
+
+ off += snprintf(buf + off, BUFFER_SIZE - off,
+ "Time Signature: %d/%d, %d clocks per click, %d notated 32nd notes per quarter note",
+ event->midi_buffer[3], (int)pow((double)2, event->midi_buffer[4]), event->midi_buffer[5],
+ event->midi_buffer[6]);
+ break;
+
+ case 0x59:
+ if (event->midi_buffer_length < 5) {
+ g_warning("smf_event_decode_metadata: truncated MIDI message.");
+ goto error;
+ }
+
+ flats = event->midi_buffer[3];
+ isminor = event->midi_buffer[4];
+
+ if (isminor != 0 && isminor != 1) {
+ g_warning("smf_event_decode_metadata: last byte of the Key Signature event has invalid value %d.", isminor);
+ goto error;
+ }
+
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Key Signature: ");
+
+ if (flats > 8 && flats < 248) {
+ off += snprintf(buf + off, BUFFER_SIZE - off, "%d %s, %s key", abs((int8_t)flats),
+ flats > 127 ? "flats" : "sharps", isminor ? "minor" : "major");
+ } else {
+ int i = (flats - 248) & 255;
+
+ assert(i >= 0 && (size_t)i < sizeof(minor_keys) / sizeof(*minor_keys));
+ assert(i >= 0 && (size_t)i < sizeof(major_keys) / sizeof(*major_keys));
+
+ if (isminor)
+ off += snprintf(buf + off, BUFFER_SIZE - off, "%s", minor_keys[i]);
+ else
+ off += snprintf(buf + off, BUFFER_SIZE - off, "%s", major_keys[i]);
+ }
+
+ break;
+
+ case 0x7F:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Proprietary (aka Sequencer) Event, length %" G_GSIZE_FORMAT,
+ event->midi_buffer_length);
+ break;
+
+ default:
+ goto error;
+ }
+
+ assert (off <= BUFFER_SIZE);
+ return (buf);
+
+error:
+ free(buf);
+
+ return (NULL);
+}
+
+static char *
+smf_event_decode_system_realtime(const smf_event_t *event)
+{
+ int off = 0;
+ char *buf;
+
+ assert(smf_event_is_system_realtime(event));
+
+ if (event->midi_buffer_length != 1) {
+ g_warning("smf_event_decode_system_realtime: event length is not 1.");
+ return (NULL);
+ }
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode_system_realtime: malloc failed.");
+ return (NULL);
+ }
+
+ switch (event->midi_buffer[0]) {
+ case 0xF8:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Clock (realtime)");
+ break;
+
+ case 0xF9:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Tick (realtime)");
+ break;
+
+ case 0xFA:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Start (realtime)");
+ break;
+
+ case 0xFB:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Continue (realtime)");
+ break;
+
+ case 0xFC:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "MIDI Stop (realtime)");
+ break;
+
+ case 0xFE:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Active Sense (realtime)");
+ break;
+
+ default:
+ free(buf);
+ return (NULL);
+ }
+
+ assert (off <= BUFFER_SIZE);
+ return (buf);
+}
+
+static char *
+smf_event_decode_sysex(const smf_event_t *event)
+{
+ int off = 0;
+ char *buf, manufacturer, subid, subid2;
+
+ assert(smf_event_is_sysex(event));
+
+ if (event->midi_buffer_length < 5) {
+ g_warning("smf_event_decode_sysex: truncated MIDI message.");
+ return (NULL);
+ }
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode_sysex: malloc failed.");
+ return (NULL);
+ }
+
+ manufacturer = event->midi_buffer[1];
+
+ if (manufacturer == 0x7F) {
+ off += snprintf(buf + off, BUFFER_SIZE - off, "SysEx, realtime, channel %d", event->midi_buffer[2]);
+ } else if (manufacturer == 0x7E) {
+ off += snprintf(buf + off, BUFFER_SIZE - off, "SysEx, non-realtime, channel %d", event->midi_buffer[2]);
+ } else {
+ off += snprintf(buf + off, BUFFER_SIZE - off, "SysEx, manufacturer 0x%x", manufacturer);
+
+ assert (off <= BUFFER_SIZE);
+ return (buf);
+ }
+
+ subid = event->midi_buffer[3];
+ subid2 = event->midi_buffer[4];
+
+ if (subid == 0x01)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Header");
+
+ else if (subid == 0x02)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Data Packet");
+
+ else if (subid == 0x03)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Request");
+
+ else if (subid == 0x04 && subid2 == 0x01)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Master Volume");
+
+ else if (subid == 0x05 && subid2 == 0x01)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Loop Point Retransmit");
+
+ else if (subid == 0x05 && subid2 == 0x02)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Loop Point Request");
+
+ else if (subid == 0x06 && subid2 == 0x01)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Identity Request");
+
+ else if (subid == 0x06 && subid2 == 0x02)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Identity Reply");
+
+ else if (subid == 0x08 && subid2 == 0x00)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Bulk Tuning Dump Request");
+
+ else if (subid == 0x08 && subid2 == 0x01)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Bulk Tuning Dump");
+
+ else if (subid == 0x08 && subid2 == 0x02)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Single Note Tuning Change");
+
+ else if (subid == 0x08 && subid2 == 0x03)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Bulk Tuning Dump Request (Bank)");
+
+ else if (subid == 0x08 && subid2 == 0x04)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Key Based Tuning Dump");
+
+ else if (subid == 0x08 && subid2 == 0x05)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Scale/Octave Tuning Dump, 1 byte format");
+
+ else if (subid == 0x08 && subid2 == 0x06)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Scale/Octave Tuning Dump, 2 byte format");
+
+ else if (subid == 0x08 && subid2 == 0x07)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Single Note Tuning Change (Bank)");
+
+ else if (subid == 0x09)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", General MIDI %s", subid2 == 0 ? "disable" : "enable");
+
+ else if (subid == 0x7C)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Wait");
+
+ else if (subid == 0x7D)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump Cancel");
+
+ else if (subid == 0x7E)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump NAK");
+
+ else if (subid == 0x7F)
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Sample Dump ACK");
+
+ else
+ off += snprintf(buf + off, BUFFER_SIZE - off, ", Unknown");
+
+ assert (off <= BUFFER_SIZE);
+ return (buf);
+}
+
+static char *
+smf_event_decode_system_common(const smf_event_t *event)
+{
+ int off = 0;
+ char *buf;
+
+ assert(smf_event_is_system_common(event));
+
+ if (smf_event_is_sysex(event))
+ return (smf_event_decode_sysex(event));
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode_system_realtime: malloc failed.");
+ return (NULL);
+ }
+
+ switch (event->midi_buffer[0]) {
+ case 0xF1:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "MTC Quarter Frame");
+ break;
+
+ case 0xF2:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Song Position Pointer");
+ break;
+
+ case 0xF3:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Song Select");
+ break;
+
+ case 0xF6:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Tune Request");
+ break;
+
+ default:
+ free(buf);
+ return (NULL);
+ }
+
+ assert (off <= BUFFER_SIZE);
+ return (buf);
+}
+
+static void
+note_from_int(char *buf, int note_number)
+{
+ int note, octave;
+ const char *names[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
+
+ octave = note_number / 12 - 1;
+ note = note_number % 12;
+
+ sprintf(buf, "%s%d", names[note], octave);
+}
+
+/**
+ * \return Textual representation of the event given, or NULL, if event is unknown.
+ * Returned string looks like this:
+ *
+ * Note On, channel 1, note F#3, velocity 0
+ *
+ * You should free the returned string afterwards, using free(3).
+ */
+char *
+smf_event_decode(const smf_event_t *event)
+{
+ int off = 0, channel;
+ char *buf, note[5];
+
+ if (smf_event_is_metadata(event))
+ return (smf_event_decode_metadata(event));
+
+ if (smf_event_is_system_realtime(event))
+ return (smf_event_decode_system_realtime(event));
+
+ if (smf_event_is_system_common(event))
+ return (smf_event_decode_system_common(event));
+
+ if (!smf_event_length_is_valid(event)) {
+ g_warning("smf_event_decode: incorrect MIDI message length.");
+ return (NULL);
+ }
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode: malloc failed.");
+ return (NULL);
+ }
+
+ /* + 1, because user-visible channels used to be in range <1-16>. */
+ channel = (event->midi_buffer[0] & 0x0F) + 1;
+
+ switch (event->midi_buffer[0] & 0xF0) {
+ case 0x80:
+ note_from_int(note, event->midi_buffer[1]);
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Note Off, channel %d, note %s, velocity %d",
+ channel, note, event->midi_buffer[2]);
+ break;
+
+ case 0x90:
+ note_from_int(note, event->midi_buffer[1]);
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Note On, channel %d, note %s, velocity %d",
+ channel, note, event->midi_buffer[2]);
+ break;
+
+ case 0xA0:
+ note_from_int(note, event->midi_buffer[1]);
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Aftertouch, channel %d, note %s, pressure %d",
+ channel, note, event->midi_buffer[2]);
+ break;
+
+ case 0xB0:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Controller, channel %d, controller %d, value %d",
+ channel, event->midi_buffer[1], event->midi_buffer[2]);
+ break;
+
+ case 0xC0:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Program Change, channel %d, controller %d",
+ channel, event->midi_buffer[1]);
+ break;
+
+ case 0xD0:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Channel Pressure, channel %d, pressure %d",
+ channel, event->midi_buffer[1]);
+ break;
+
+ case 0xE0:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "Pitch Wheel, channel %d, value %d",
+ channel, ((int)event->midi_buffer[2] << 7) | (int)event->midi_buffer[2]);
+ break;
+
+ default:
+ free(buf);
+ return (NULL);
+ }
+
+ assert(off <= BUFFER_SIZE);
+
+ return (buf);
+}
+
+/**
+ * \return Textual representation of the data extracted from MThd header, or NULL, if something goes wrong.
+ * Returned string looks like this:
+ *
+ * format: 1 (several simultaneous tracks); number of tracks: 4; division: 192 PPQN.
+ *
+ * You should free the returned string afterwards, using free(3).
+ */
+char *
+smf_decode(const smf_t *smf)
+{
+ int off = 0;
+ char *buf;
+
+ buf = (char*)malloc(BUFFER_SIZE);
+ if (buf == NULL) {
+ g_warning("smf_event_decode: malloc failed.");
+ return (NULL);
+ }
+
+ off += snprintf(buf + off, BUFFER_SIZE - off, "format: %d ", smf->format);
+
+ switch (smf->format) {
+ case 0:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "(single track)");
+ break;
+
+ case 1:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "(several simultaneous tracks)");
+ break;
+
+ case 2:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "(several independent tracks)");
+ break;
+
+ default:
+ off += snprintf(buf + off, BUFFER_SIZE - off, "(INVALID FORMAT)");
+ break;
+ }
+
+ off += snprintf(buf + off, BUFFER_SIZE - off, "; number of tracks: %d", smf->number_of_tracks);
+
+ if (smf->ppqn != 0)
+ off += snprintf(buf + off, BUFFER_SIZE - off, "; division: %d PPQN", smf->ppqn);
+ else
+ off += snprintf(buf + off, BUFFER_SIZE - off, "; division: %d FPS, %d resolution", smf->frames_per_second, smf->resolution);
+
+ assert (off <= BUFFER_SIZE);
+ return (buf);
+}
+
diff --git a/libs/evoral/libsmf/smf_load.c b/libs/evoral/libsmf/smf_load.c
new file mode 100644
index 0000000000..f8b4158004
--- /dev/null
+++ b/libs/evoral/libsmf/smf_load.c
@@ -0,0 +1,984 @@
+/*-
+ * 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>
+#ifdef PLATFORM_WINDOWS
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#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) + ntohl(chunk->length);
+
+ if (smf->next_chunk_offset > smf->file_buffer_length) {
+ g_warning("SMF error: malformed chunk; truncated file?");
+ }
+
+ 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 = ntohl(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 = ntohs(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 = ntohs(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 = ntohs(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;
+
+ 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);
+#endif
+
+ if (buffer_length < 3) {
+ g_warning("SMF error: end of buffer in expected_sysex_length().");
+ return (-1);
+ }
+
+ smf_extract_vlq(second_byte, buffer_length, &sysex_length, &len);
+
+ 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))
+ return (extract_sysex_event(buf, buffer_length, event, len, last_status));
+
+ if (is_escape_byte(status))
+ 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;
+ 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) + ntohl(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;
+ int ret = 0;
+
+ if (parse_mtrk_header(track))
+ return (-1);
+
+ for (;;) {
+ event = parse_next_event(track);
+
+ /* Couldn't parse an event? */
+ if (event == NULL || !smf_event_is_valid(event)) {
+ ret = -1;
+ break;
+ }
+
+ 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 (ret);
+}
+
+/**
+ * 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;
+ int ret;
+
+ 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))
+ return (NULL);
+
+ for (i = 1; i <= smf->expected_number_of_tracks; i++) {
+ smf_track_t *track = smf_track_new();
+ if (track == NULL)
+ return (NULL);
+
+ smf_add_track(smf, track);
+
+ ret = parse_mtrk_chunk(track);
+
+ track->file_buffer = NULL;
+ track->file_buffer_length = 0;
+ track->next_event_offset = -1;
+
+ if (ret) {
+ g_warning("SMF warning: Error parsing track, continuing with data loaded so far.");
+ break;
+ }
+ }
+
+ 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;
+ }
+
+ 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);
+}
+
diff --git a/libs/evoral/libsmf/smf_private.h b/libs/evoral/libsmf/smf_private.h
new file mode 100644
index 0000000000..41ac7238fc
--- /dev/null
+++ b/libs/evoral/libsmf/smf_private.h
@@ -0,0 +1,81 @@
+/*-
+ * 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.
+ *
+ */
+
+#ifndef SMF_PRIVATE_H
+#define SMF_PRIVATE_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+//#include "config.h"
+//#define SMF_VERSION PACKAGE_VERSION
+
+/**
+ * \file
+ *
+ * Private header. Applications using libsmf should use smf.h.
+ *
+ */
+
+#if defined(__GNUC__)
+#define ATTRIBUTE_PACKED __attribute__((__packed__))
+#else
+#define ATTRIBUTE_PACKED
+#pragma pack(1)
+#endif
+
+/** SMF chunk header, used only by smf_load.c and smf_save.c. */
+struct chunk_header_struct {
+ char id[4];
+ uint32_t length;
+} ATTRIBUTE_PACKED;
+
+/** SMF chunk, used only by smf_load.c and smf_save.c. */
+struct mthd_chunk_struct {
+ struct chunk_header_struct mthd_header;
+ uint16_t format;
+ uint16_t number_of_tracks;
+ uint16_t division;
+} ATTRIBUTE_PACKED;
+
+#if (!defined __GNUC__)
+#pragma pack()
+#endif
+
+void smf_track_add_event(smf_track_t *track, smf_event_t *event);
+void smf_init_tempo(smf_t *smf);
+void smf_fini_tempo(smf_t *smf);
+void smf_create_tempo_map_and_compute_seconds(smf_t *smf);
+void maybe_add_to_tempo_map(smf_event_t *event);
+void remove_last_tempo_with_pulses(smf_t *smf, size_t pulses);
+int smf_event_is_tempo_change_or_time_signature(const smf_event_t *event) WARN_UNUSED_RESULT;
+int smf_event_length_is_valid(const smf_event_t *event) WARN_UNUSED_RESULT;
+int is_status_byte(const unsigned char status) WARN_UNUSED_RESULT;
+smf_track_t* smf_find_track_with_next_event (smf_t *smf);
+
+#endif /* SMF_PRIVATE_H */
+
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);
+}
+
diff --git a/libs/evoral/libsmf/smf_tempo.c b/libs/evoral/libsmf/smf_tempo.c
new file mode 100644
index 0000000000..eaa1b0ef34
--- /dev/null
+++ b/libs/evoral/libsmf/smf_tempo.c
@@ -0,0 +1,454 @@
+/*-
+ * 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
+ *
+ * Tempo map related part.
+ *
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+#include "smf.h"
+#include "smf_private.h"
+
+static double seconds_from_pulses(const smf_t *smf, size_t pulses);
+
+/**
+ * If there is tempo starting at "pulses" already, return it. Otherwise,
+ * allocate new one, fill it with values from previous one (or default ones,
+ * if there is no previous one) and attach it to "smf".
+ */
+static smf_tempo_t *
+new_tempo(smf_t *smf, size_t pulses)
+{
+ smf_tempo_t *tempo, *previous_tempo = NULL;
+
+ if (smf->tempo_array->len > 0) {
+ previous_tempo = smf_get_last_tempo(smf);
+
+ /* If previous tempo starts at the same time as new one, reuse it, updating in place. */
+ if (previous_tempo->time_pulses == pulses)
+ return (previous_tempo);
+ }
+
+ tempo = (smf_tempo_t*)malloc(sizeof(smf_tempo_t));
+ if (tempo == NULL) {
+ g_warning("Cannot allocate smf_tempo_t.");
+ return (NULL);
+ }
+
+ tempo->time_pulses = pulses;
+
+ if (previous_tempo != NULL) {
+ tempo->microseconds_per_quarter_note = previous_tempo->microseconds_per_quarter_note;
+ tempo->numerator = previous_tempo->numerator;
+ tempo->denominator = previous_tempo->denominator;
+ tempo->clocks_per_click = previous_tempo->clocks_per_click;
+ tempo->notes_per_note = previous_tempo->notes_per_note;
+ } else {
+ tempo->microseconds_per_quarter_note = 500000; /* Initial tempo is 120 BPM. */
+ tempo->numerator = 4;
+ tempo->denominator = 4;
+ tempo->clocks_per_click = -1;
+ tempo->notes_per_note = -1;
+ }
+
+ g_ptr_array_add(smf->tempo_array, tempo);
+
+ if (pulses == 0)
+ tempo->time_seconds = 0.0;
+ else
+ tempo->time_seconds = seconds_from_pulses(smf, pulses);
+
+ return (tempo);
+}
+
+static int
+add_tempo(smf_t *smf, int pulses, int tempo)
+{
+ smf_tempo_t *smf_tempo = new_tempo(smf, pulses);
+ if (smf_tempo == NULL)
+ return (-1);
+
+ smf_tempo->microseconds_per_quarter_note = tempo;
+
+ return (0);
+}
+
+static int
+add_time_signature(smf_t *smf, int pulses, int numerator, int denominator, int clocks_per_click, int notes_per_note)
+{
+ smf_tempo_t *smf_tempo = new_tempo(smf, pulses);
+ if (smf_tempo == NULL)
+ return (-1);
+
+ smf_tempo->numerator = numerator;
+ smf_tempo->denominator = denominator;
+ smf_tempo->clocks_per_click = clocks_per_click;
+ smf_tempo->notes_per_note = notes_per_note;
+
+ return (0);
+}
+
+/**
+ * \internal
+ */
+void
+maybe_add_to_tempo_map(smf_event_t *event)
+{
+ if (!smf_event_is_metadata(event))
+ return;
+
+ assert(event->track != NULL);
+ assert(event->track->smf != NULL);
+ assert(event->midi_buffer_length >= 1);
+
+ /* Tempo Change? */
+ if (event->midi_buffer[1] == 0x51) {
+ int ntempo = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5];
+ if (ntempo <= 0) {
+ g_warning("Ignoring invalid tempo change.");
+ return;
+ }
+
+ add_tempo(event->track->smf, event->time_pulses, ntempo);
+ }
+
+ /* Time Signature? */
+ if (event->midi_buffer[1] == 0x58) {
+ int numerator, denominator, clocks_per_click, notes_per_note;
+
+ if (event->midi_buffer_length < 7) {
+ g_warning("Time Signature event seems truncated.");
+ return;
+ }
+
+ numerator = event->midi_buffer[3];
+ denominator = (int)pow((double)2, event->midi_buffer[4]);
+ clocks_per_click = event->midi_buffer[5];
+ notes_per_note = event->midi_buffer[6];
+
+ add_time_signature(event->track->smf, event->time_pulses, numerator, denominator, clocks_per_click, notes_per_note);
+ }
+
+ return;
+}
+
+/**
+ * \internal
+ *
+ * This is an internal function, called from smf_track_remove_event when tempo-related
+ * event being removed does not require recreation of tempo map, i.e. there are no events
+ * after that one.
+ */
+void
+remove_last_tempo_with_pulses(smf_t *smf, size_t pulses)
+{
+ smf_tempo_t *tempo;
+
+ /* XXX: This is a partial workaround for the following problem: we have two tempo-related
+ events, A and B, that occur at the same time. We remove B, then try to remove
+ A. However, both tempo changes got coalesced in new_tempo(), so it is impossible
+ to remove B. */
+ if (smf->tempo_array->len == 0)
+ return;
+
+ tempo = smf_get_last_tempo(smf);
+
+ /* Workaround part two. */
+ if (tempo->time_pulses != pulses)
+ return;
+
+ memset(tempo, 0, sizeof(smf_tempo_t));
+ free(tempo);
+
+ g_ptr_array_remove_index(smf->tempo_array, smf->tempo_array->len - 1);
+}
+
+static double
+seconds_from_pulses(const smf_t *smf, size_t pulses)
+{
+ double seconds;
+ smf_tempo_t *tempo;
+
+ tempo = smf_get_tempo_by_pulses(smf, pulses);
+ assert(tempo);
+ assert(tempo->time_pulses <= pulses);
+
+ seconds = tempo->time_seconds + (double)(pulses - tempo->time_pulses) *
+ (tempo->microseconds_per_quarter_note / ((double)smf->ppqn * 1000000.0));
+
+ return (seconds);
+}
+
+static int
+pulses_from_seconds(const smf_t *smf, double seconds)
+{
+ int pulses = 0;
+ smf_tempo_t *tempo;
+
+ tempo = smf_get_tempo_by_seconds(smf, seconds);
+ assert(tempo);
+ assert(tempo->time_seconds <= seconds);
+
+ pulses = tempo->time_pulses + (seconds - tempo->time_seconds) *
+ ((double)smf->ppqn * 1000000.0 / tempo->microseconds_per_quarter_note);
+
+ return (pulses);
+}
+
+/**
+ * \internal
+ *
+ * Computes value of event->time_seconds for all events in smf.
+ * Warning: rewinds the smf.
+ */
+void
+smf_create_tempo_map_and_compute_seconds(smf_t *smf)
+{
+ smf_event_t *event;
+
+ smf_rewind(smf);
+ smf_init_tempo(smf);
+
+ for (;;) {
+ event = smf_get_next_event(smf);
+
+ if (event == NULL)
+ return;
+
+ maybe_add_to_tempo_map(event);
+
+ event->time_seconds = seconds_from_pulses(smf, event->time_pulses);
+ }
+
+ /* Not reached. */
+}
+
+int
+smf_get_tempo_count (const smf_t *smf)
+{
+ if (!smf->tempo_array) {
+ return 0;
+ }
+
+ return smf->tempo_array->len;
+}
+
+smf_tempo_t *
+smf_get_tempo_by_number(const smf_t *smf, size_t number)
+{
+ if (number >= smf->tempo_array->len)
+ return (NULL);
+
+ return ((smf_tempo_t*)g_ptr_array_index(smf->tempo_array, number));
+}
+
+/**
+ * Return last tempo (i.e. tempo with greatest time_pulses) that happens before "pulses".
+ */
+smf_tempo_t *
+smf_get_tempo_by_pulses(const smf_t *smf, size_t pulses)
+{
+ size_t i;
+ smf_tempo_t *tempo;
+
+ if (pulses == 0)
+ return (smf_get_tempo_by_number(smf, 0));
+
+ assert(smf->tempo_array != NULL);
+
+ for (i = smf->tempo_array->len; i > 0; i--) {
+ tempo = smf_get_tempo_by_number(smf, i - 1);
+
+ assert(tempo);
+ if (tempo->time_pulses < pulses)
+ return (tempo);
+ }
+
+ return (NULL);
+}
+
+/**
+ * Return last tempo (i.e. tempo with greatest time_seconds) that happens before "seconds".
+ */
+smf_tempo_t *
+smf_get_tempo_by_seconds(const smf_t *smf, double seconds)
+{
+ size_t i;
+ smf_tempo_t *tempo;
+
+ assert(seconds >= 0.0);
+
+ if (seconds == 0.0)
+ return (smf_get_tempo_by_number(smf, 0));
+
+ assert(smf->tempo_array != NULL);
+
+ for (i = smf->tempo_array->len; i > 0; i--) {
+ tempo = smf_get_tempo_by_number(smf, i - 1);
+
+ assert(tempo);
+ if (tempo->time_seconds < seconds)
+ return (tempo);
+ }
+
+ return (NULL);
+}
+
+
+/**
+ * Return last tempo.
+ */
+smf_tempo_t *
+smf_get_last_tempo(const smf_t *smf)
+{
+ smf_tempo_t *tempo;
+
+ assert(smf->tempo_array->len > 0);
+ tempo = smf_get_tempo_by_number(smf, smf->tempo_array->len - 1);
+ assert(tempo);
+
+ return (tempo);
+}
+
+/**
+ * \internal
+ *
+ * Remove all smf_tempo_t structures from SMF.
+ */
+void
+smf_fini_tempo(smf_t *smf)
+{
+ smf_tempo_t *tempo;
+
+ while (smf->tempo_array->len > 0) {
+ tempo = (smf_tempo_t*)g_ptr_array_index(smf->tempo_array, smf->tempo_array->len - 1);
+ assert(tempo);
+
+ memset(tempo, 0, sizeof(smf_tempo_t));
+ free(tempo);
+
+ g_ptr_array_remove_index(smf->tempo_array, smf->tempo_array->len - 1);
+ }
+
+ assert(smf->tempo_array->len == 0);
+}
+
+/**
+ * \internal
+ *
+ * Remove any existing tempos and add default one.
+ *
+ * \bug This will abort (by calling g_warning) if new_tempo() (memory allocation there) fails.
+ */
+void
+smf_init_tempo(smf_t *smf)
+{
+ smf_tempo_t *tempo;
+
+ smf_fini_tempo(smf);
+
+ tempo = new_tempo(smf, 0);
+ if (tempo == NULL) {
+ g_warning("tempo_init failed, sorry.");
+ }
+}
+
+/**
+ * Returns ->time_pulses of last event on the given track, or 0, if track is empty.
+ */
+static int
+last_event_pulses(const smf_track_t *track)
+{
+ /* Get time of last event on this track. */
+ if (track->number_of_events > 0) {
+ smf_event_t *previous_event = smf_track_get_last_event(track);
+ assert(previous_event);
+
+ return (previous_event->time_pulses);
+ }
+
+ return (0);
+}
+
+/**
+ * Adds event to the track at the time "pulses" clocks from the previous event in this track.
+ * The remaining two time fields will be computed automatically based on the third argument
+ * and current tempo map. Note that ->delta_pulses is computed by smf.c:smf_track_add_event,
+ * not here.
+ */
+void
+smf_track_add_event_delta_pulses(smf_track_t *track, smf_event_t *event, uint32_t delta)
+{
+ assert(event->time_seconds == -1.0);
+ assert(track->smf != NULL);
+
+ if (!smf_event_is_valid(event)) {
+ g_warning("Added event is invalid");
+ }
+
+ smf_track_add_event_pulses(track, event, last_event_pulses(track) + delta);
+}
+
+/**
+ * Adds event to the track at the time "pulses" clocks from the start of song.
+ * The remaining two time fields will be computed automatically based on the third argument
+ * and current tempo map.
+ */
+void
+smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, size_t pulses)
+{
+ assert(event->time_seconds == -1.0);
+ assert(track->smf != NULL);
+
+ event->time_pulses = pulses;
+ event->time_seconds = seconds_from_pulses(track->smf, pulses);
+ smf_track_add_event(track, event);
+}
+
+/**
+ * Adds event to the track at the time "seconds" seconds from the start of song.
+ * The remaining two time fields will be computed automatically based on the third argument
+ * and current tempo map.
+ */
+void
+smf_track_add_event_seconds(smf_track_t *track, smf_event_t *event, double seconds)
+{
+ assert(seconds >= 0.0);
+ assert(event->time_seconds == -1.0);
+ assert(track->smf != NULL);
+
+ event->time_seconds = seconds;
+ event->time_pulses = pulses_from_seconds(track->smf, seconds);
+ smf_track_add_event(track, event);
+}
+
diff --git a/libs/evoral/libsmf/smfsh.c b/libs/evoral/libsmf/smfsh.c
new file mode 100644
index 0000000000..493c664bd2
--- /dev/null
+++ b/libs/evoral/libsmf/smfsh.c
@@ -0,0 +1,1034 @@
+/*-
+ * 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
+ *
+ * "SMF shell", command line utility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include "smf.h"
+#include "config.h"
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+smf_track_t *selected_track = NULL;
+smf_event_t *selected_event = NULL;
+smf_t *smf = NULL;
+char *last_file_name = NULL;
+
+#define COMMAND_LENGTH 10
+
+static void
+log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer notused)
+{
+ if (strcmp(log_domain, "smfsh") == 0)
+ fprintf(stderr, "%s\n", message);
+ else
+ fprintf(stderr, "%s: %s\n", log_domain, message);
+}
+
+static int cmd_track(char *arg);
+
+static int
+cmd_load(char *file_name)
+{
+ char *decoded;
+
+ if (file_name == NULL) {
+ if (last_file_name == NULL) {
+ g_critical("Please specify file name.");
+ return (-1);
+ }
+
+ file_name = strdup(last_file_name);
+ } else {
+ file_name = strdup(file_name);
+ }
+
+ selected_track = NULL;
+ selected_event = NULL;
+
+ if (smf != NULL) {
+ smf_delete(smf);
+ smf = NULL;
+ }
+
+ if (last_file_name != NULL)
+ free(last_file_name);
+ last_file_name = strdup(file_name);
+
+ smf = smf_load(file_name);
+ if (smf == NULL) {
+ g_critical("Couldn't load '%s'.", file_name);
+
+ smf = smf_new();
+ if (smf == NULL) {
+ g_critical("Cannot initialize smf_t.");
+ return (-1);
+ }
+
+ return (-2);
+ }
+
+ g_message("File '%s' loaded.", file_name);
+ decoded = smf_decode(smf);
+ g_message("%s.", decoded);
+ free(decoded);
+
+ cmd_track("1");
+
+ free(file_name);
+
+ return (0);
+}
+
+static int
+cmd_save(char *file_name)
+{
+ int ret;
+
+ if (file_name == NULL) {
+ if (last_file_name == NULL) {
+ g_critical("Please specify file name.");
+ return (-1);
+ }
+
+ file_name = strdup(last_file_name);
+ } else {
+ file_name = strdup(file_name);
+ }
+
+ if (last_file_name != NULL)
+ free(last_file_name);
+ last_file_name = strdup(file_name);
+
+ ret = smf_save(smf, file_name);
+ if (ret) {
+ g_critical("Couldn't save '%s'", file_name);
+ return (-1);
+ }
+
+ g_message("File '%s' saved.", file_name);
+
+ free(file_name);
+
+ return (0);
+}
+
+static int
+cmd_ppqn(char *new_ppqn)
+{
+ int tmp;
+ char *end;
+
+ if (new_ppqn == NULL) {
+ g_message("Pulses Per Quarter Note (aka Division) is %d.", smf->ppqn);
+ } else {
+ tmp = strtol(new_ppqn, &end, 10);
+ if (end - new_ppqn != strlen(new_ppqn)) {
+ g_critical("Invalid PPQN, garbage characters after the number.");
+ return (-1);
+ }
+
+ if (tmp <= 0) {
+ g_critical("Invalid PPQN, valid values are greater than zero.");
+ return (-2);
+ }
+
+ if (smf_set_ppqn(smf, tmp)) {
+ g_message("smf_set_ppqn failed.");
+ return (-3);
+ }
+
+ g_message("Pulses Per Quarter Note changed to %d.", smf->ppqn);
+ }
+
+ return (0);
+}
+
+static int
+cmd_format(char *new_format)
+{
+ int tmp;
+ char *end;
+
+ if (new_format == NULL) {
+ g_message("Format is %d.", smf->format);
+ } else {
+ tmp = strtol(new_format, &end, 10);
+ if (end - new_format != strlen(new_format)) {
+ g_critical("Invalid format value, garbage characters after the number.");
+ return (-1);
+ }
+
+ if (tmp < 0 || tmp > 2) {
+ g_critical("Invalid format value, valid values are in range 0 - 2, inclusive.");
+ return (-2);
+ }
+
+ if (smf_set_format(smf, tmp)) {
+ g_critical("smf_set_format failed.");
+ return (-3);
+ }
+
+ g_message("Forma changed to %d.", smf->format);
+ }
+
+ return (0);
+}
+
+static int
+cmd_tracks(char *notused)
+{
+ if (smf->number_of_tracks > 0)
+ g_message("There are %d tracks, numbered from 1 to %d.", smf->number_of_tracks, smf->number_of_tracks);
+ else
+ g_message("There are no tracks.");
+
+ return (0);
+}
+
+static int
+parse_track_number(const char *arg)
+{
+ int num;
+ char *end;
+
+ if (arg == NULL) {
+ if (selected_track == NULL) {
+ g_message("No track currently selected and no track number given.");
+ return (-1);
+ } else {
+ return (selected_track->track_number);
+ }
+ }
+
+ num = strtol(arg, &end, 10);
+ if (end - arg != strlen(arg)) {
+ g_critical("Invalid track number, garbage characters after the number.");
+ return (-1);
+ }
+
+ if (num < 1 || num > smf->number_of_tracks) {
+ if (smf->number_of_tracks > 0) {
+ g_critical("Invalid track number specified; valid choices are 1 - %d.", smf->number_of_tracks);
+ } else {
+ g_critical("There are no tracks.");
+ }
+
+ return (-1);
+ }
+
+ return (num);
+}
+
+static int
+cmd_track(char *arg)
+{
+ int num;
+
+ if (arg == NULL) {
+ if (selected_track == NULL)
+ g_message("No track currently selected.");
+ else
+ g_message("Currently selected is track number %d, containing %d events.",
+ selected_track->track_number, selected_track->number_of_events);
+ } else {
+ if (smf->number_of_tracks == 0) {
+ g_message("There are no tracks.");
+ return (-1);
+ }
+
+ num = parse_track_number(arg);
+ if (num < 0)
+ return (-1);
+
+ selected_track = smf_get_track_by_number(smf, num);
+ if (selected_track == NULL) {
+ g_critical("smf_get_track_by_number() failed, track not selected.");
+ return (-3);
+ }
+
+ selected_event = NULL;
+
+ g_message("Track number %d selected; it contains %d events.",
+ selected_track->track_number, selected_track->number_of_events);
+ }
+
+ return (0);
+}
+
+static int
+cmd_trackadd(char *notused)
+{
+ selected_track = smf_track_new();
+ if (selected_track == NULL) {
+ g_critical("smf_track_new() failed, track not created.");
+ return (-1);
+ }
+
+ smf_add_track(smf, selected_track);
+
+ selected_event = NULL;
+
+ g_message("Created new track; track number %d selected.", selected_track->track_number);
+
+ return (0);
+}
+
+static int
+cmd_trackrm(char *arg)
+{
+ int num = parse_track_number(arg);
+
+ if (num < 0)
+ return (-1);
+
+ if (selected_track != NULL && num == selected_track->track_number) {
+ selected_track = NULL;
+ selected_event = NULL;
+ }
+
+ smf_track_delete(smf_get_track_by_number(smf, num));
+
+ g_message("Track %d removed.", num);
+
+ return (0);
+}
+
+#define BUFFER_SIZE 1024
+
+static int
+show_event(smf_event_t *event)
+{
+ int off = 0, i;
+ char *decoded, *type;
+
+ if (smf_event_is_metadata(event))
+ type = "Metadata";
+ else
+ type = "Event";
+
+ decoded = smf_event_decode(event);
+
+ if (decoded == NULL) {
+ decoded = malloc(BUFFER_SIZE);
+ if (decoded == NULL) {
+ g_critical("show_event: malloc failed.");
+ return (-1);
+ }
+
+ off += snprintf(decoded + off, BUFFER_SIZE - off, "Unknown event:");
+
+ for (i = 0; i < event->midi_buffer_length && i < 5; i++)
+ off += snprintf(decoded + off, BUFFER_SIZE - off, " 0x%x", event->midi_buffer[i]);
+ }
+
+ g_message("%d: %s: %s, %f seconds, %d pulses, %d delta pulses", event->event_number, type, decoded,
+ event->time_seconds, event->time_pulses, event->delta_time_pulses);
+
+ free(decoded);
+
+ return (0);
+}
+
+static int
+cmd_events(char *notused)
+{
+ smf_event_t *event;
+
+ if (selected_track == NULL) {
+ g_critical("No track selected - please use 'track <number>' command first.");
+ return (-1);
+ }
+
+ if (selected_track->number_of_events == 0) {
+ g_message("Selected track is empty.");
+ return (0);
+ }
+
+ g_message("List of events in track %d follows:", selected_track->track_number);
+
+ smf_rewind(smf);
+
+ while ((event = smf_track_get_next_event(selected_track)) != NULL)
+ show_event(event);
+
+ smf_rewind(smf);
+
+ return (0);
+}
+
+static int
+parse_event_number(const char *arg)
+{
+ int num;
+ char *end;
+
+ if (selected_track == NULL) {
+ g_critical("You need to select track first (using 'track <number>').");
+ return (-1);
+ }
+
+ if (arg == NULL) {
+ if (selected_event == NULL) {
+ g_message("No event currently selected and no event number given.");
+ return (-1);
+ } else {
+ return (selected_event->event_number);
+ }
+ }
+
+ num = strtol(arg, &end, 10);
+ if (end - arg != strlen(arg)) {
+ g_critical("Invalid event number, garbage characters after the number.");
+ return (-1);
+ }
+
+ if (num < 1 || num > selected_track->number_of_events) {
+ if (selected_track->number_of_events > 0)
+ g_critical("Invalid event number specified; valid choices are 1 - %d.", selected_track->number_of_events);
+ else
+ g_critical("There are no events in currently selected track.");
+
+ return (-1);
+ }
+
+ return (num);
+}
+
+static int
+cmd_event(char *arg)
+{
+ int num;
+
+ if (arg == NULL) {
+ if (selected_event == NULL) {
+ g_message("No event currently selected.");
+ } else {
+ g_message("Currently selected is event %d, track %d.", selected_event->event_number, selected_track->track_number);
+ show_event(selected_event);
+ }
+ } else {
+ num = parse_event_number(arg);
+ if (num < 0)
+ return (-1);
+
+ selected_event = smf_track_get_event_by_number(selected_track, num);
+ if (selected_event == NULL) {
+ g_critical("smf_get_event_by_number() failed, event not selected.");
+ return (-2);
+ }
+
+ g_message("Event number %d selected.", selected_event->event_number);
+ show_event(selected_event);
+ }
+
+ return (0);
+}
+
+static int
+decode_hex(char *str, unsigned char **buffer, int *length)
+{
+ int i, value, midi_buffer_length;
+ char buf[3];
+ unsigned char *midi_buffer = NULL;
+ char *end = NULL;
+
+ if ((strlen(str) % 2) != 0) {
+ g_critical("Hex value should have even number of characters, you know.");
+ goto error;
+ }
+
+ midi_buffer_length = strlen(str) / 2;
+ midi_buffer = malloc(midi_buffer_length);
+ if (midi_buffer == NULL) {
+ g_critical("malloc() failed.");
+ goto error;
+ }
+
+ for (i = 0; i < midi_buffer_length; i++) {
+ buf[0] = str[i * 2];
+ buf[1] = str[i * 2 + 1];
+ buf[2] = '\0';
+ value = strtoll(buf, &end, 16);
+
+ if (end - buf != 2) {
+ g_critical("Garbage characters detected after hex.");
+ goto error;
+ }
+
+ midi_buffer[i] = value;
+ }
+
+ *buffer = midi_buffer;
+ *length = midi_buffer_length;
+
+ return (0);
+
+error:
+ if (midi_buffer != NULL)
+ free(midi_buffer);
+
+ return (-1);
+}
+
+static void
+eventadd_usage(void)
+{
+ g_message("Usage: add <time-in-seconds> <midi-in-hex> - for example, 'add 1 903C7F' will add");
+ g_message("Note On event, note C4, velocity 127, channel 1, one second from the start of song, channel 1.");
+}
+
+static int
+cmd_eventadd(char *str)
+{
+ int midi_buffer_length;
+ double seconds;
+ unsigned char *midi_buffer;
+ char *time, *endtime;
+
+ if (selected_track == NULL) {
+ g_critical("Please select a track first, using 'track <number>' command.");
+ return (-1);
+ }
+
+ if (str == NULL) {
+ eventadd_usage();
+ return (-2);
+ }
+
+ /* Extract the time. Don't use strsep(3), it doesn't work on SunOS. */
+ time = str;
+ str = strchr(str, ' ');
+ if (str != NULL) {
+ *str = '\0';
+ str++;
+ }
+
+ seconds = strtod(time, &endtime);
+ if (endtime - time != strlen(time)) {
+ g_critical("Time is supposed to be a number, without trailing characters.");
+ return (-3);
+ }
+
+ /* Called with one parameter? */
+ if (str == NULL) {
+ eventadd_usage();
+ return (-4);
+ }
+
+ if (decode_hex(str, &midi_buffer, &midi_buffer_length)) {
+ eventadd_usage();
+ return (-5);
+ }
+
+ selected_event = smf_event_new();
+ if (selected_event == NULL) {
+ g_critical("smf_event_new() failed, event not created.");
+ return (-6);
+ }
+
+ selected_event->midi_buffer = midi_buffer;
+ selected_event->midi_buffer_length = midi_buffer_length;
+
+ if (smf_event_is_valid(selected_event) == 0) {
+ g_critical("Event is invalid from the MIDI specification point of view, not created.");
+ smf_event_delete(selected_event);
+ selected_event = NULL;
+ return (-7);
+ }
+
+ smf_track_add_event_seconds(selected_track, selected_event, seconds);
+
+ g_message("Event created.");
+
+ return (0);
+}
+
+static int
+cmd_text(char *str)
+{
+ double seconds, type;
+ char *time, *typestr, *end;
+
+ if (selected_track == NULL) {
+ g_critical("Please select a track first, using 'track <number>' command.");
+ return (-1);
+ }
+
+ if (str == NULL) {
+ g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
+ return (-2);
+ }
+
+ /* Extract the time. Don't use strsep(3), it doesn't work on SunOS. */
+ time = str;
+ str = strchr(str, ' ');
+ if (str != NULL) {
+ *str = '\0';
+ str++;
+ }
+
+ seconds = strtod(time, &end);
+ if (end - time != strlen(time)) {
+ g_critical("Time is supposed to be a number, without trailing characters.");
+ return (-3);
+ }
+
+ /* Called with one parameter? */
+ if (str == NULL) {
+ g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
+ return (-4);
+ }
+
+ /* Extract the event type. */
+ typestr = str;
+ str = strchr(str, ' ');
+ if (str != NULL) {
+ *str = '\0';
+ str++;
+ }
+
+ type = strtod(typestr, &end);
+ if (end - typestr != strlen(typestr)) {
+ g_critical("Type is supposed to be a number, without trailing characters.");
+ return (-4);
+ }
+
+ if (type < 1 || type > 9) {
+ g_critical("Valid values for type are 1 - 9, inclusive.");
+ return (-5);
+ }
+
+ /* Called with one parameter? */
+ if (str == NULL) {
+ g_critical("Usage: text <time-in-seconds> <event-type> <text-itself>");
+ return (-4);
+ }
+
+ selected_event = smf_event_new_textual(type, str);
+ if (selected_event == NULL) {
+ g_critical("smf_event_new_textual() failed, event not created.");
+ return (-6);
+ }
+
+ assert(smf_event_is_valid(selected_event));
+
+ smf_track_add_event_seconds(selected_track, selected_event, seconds);
+
+ g_message("Event created.");
+
+ return (0);
+}
+
+
+static int
+cmd_eventaddeot(char *time)
+{
+ double seconds;
+ char *end;
+
+ if (selected_track == NULL) {
+ g_critical("Please select a track first, using 'track <number>' command.");
+ return (-1);
+ }
+
+ if (time == NULL) {
+ g_critical("Please specify the time, in seconds.");
+ return (-2);
+ }
+
+ seconds = strtod(time, &end);
+ if (end - time != strlen(time)) {
+ g_critical("Time is supposed to be a number, without trailing characters.");
+ return (-3);
+ }
+
+ if (smf_track_add_eot_seconds(selected_track, seconds)) {
+ g_critical("smf_track_add_eot() failed.");
+ return (-4);
+ }
+
+ g_message("Event created.");
+
+ return (0);
+}
+
+static int
+cmd_eventrm(char *number)
+{
+ int num = parse_event_number(number);
+
+ if (num < 0)
+ return (-1);
+
+ if (selected_event != NULL && num == selected_event->event_number)
+ selected_event = NULL;
+
+ smf_event_delete(smf_track_get_event_by_number(selected_track, num));
+
+ g_message("Event #%d removed.", num);
+
+ return (0);
+}
+
+static int
+cmd_tempo(char *notused)
+{
+ int i;
+ smf_tempo_t *tempo;
+
+ for (i = 0;; i++) {
+ tempo = smf_get_tempo_by_number(smf, i);
+ if (tempo == NULL)
+ break;
+
+ g_message("Tempo #%d: Starts at %d pulses, %f seconds, setting %d microseconds per quarter note, %.2f BPM.",
+ i, tempo->time_pulses, tempo->time_seconds, tempo->microseconds_per_quarter_note,
+ 60000000.0 / (double)tempo->microseconds_per_quarter_note);
+ g_message("Time signature: %d/%d, %d clocks per click, %d 32nd notes per quarter note.",
+ tempo->numerator, tempo->denominator, tempo->clocks_per_click, tempo->notes_per_note);
+ }
+
+ return (0);
+}
+
+static int
+cmd_length(char *notused)
+{
+ g_message("Length: %d pulses, %f seconds.", smf_get_length_pulses(smf), smf_get_length_seconds(smf));
+
+ return (0);
+}
+
+static int
+cmd_version(char *notused)
+{
+ g_message("libsmf version %s.", smf_get_version());
+
+ return (0);
+}
+
+static int
+cmd_exit(char *notused)
+{
+ g_debug("Good bye.");
+ exit(0);
+}
+
+static int cmd_help(char *notused);
+
+static struct command_struct {
+ char *name;
+ int (*function)(char *command);
+ char *help;
+} commands[] = {{"help", cmd_help, "Show this help."},
+ {"?", cmd_help, NULL},
+ {"load", cmd_load, "Load named file."},
+ {"open", cmd_load},
+ {"save", cmd_save, "Save to named file."},
+ {"ppqn", cmd_ppqn, "Show ppqn (aka division), or set ppqn if used with parameter."},
+ {"format", cmd_format, "Show format, or set format if used with parameter."},
+ {"tracks", cmd_tracks, "Show number of tracks."},
+ {"track", cmd_track, "Show number of currently selected track, or select a track."},
+ {"trackadd", cmd_trackadd, "Add a track and select it."},
+ {"trackrm", cmd_trackrm, "Remove currently selected track."},
+ {"events", cmd_events, "Show events in the currently selected track."},
+ {"event", cmd_event, "Show number of currently selected event, or select an event."},
+ {"add", cmd_eventadd, "Add an event and select it."},
+ {"text", cmd_text, "Add textual event and select it."},
+ {"eventadd", cmd_eventadd, NULL},
+ {"eot", cmd_eventaddeot, "Add an End Of Track event."},
+ {"eventaddeot", cmd_eventaddeot, NULL},
+ {"eventrm", cmd_eventrm, NULL},
+ {"rm", cmd_eventrm, "Remove currently selected event."},
+ {"tempo", cmd_tempo, "Show tempo map."},
+ {"length", cmd_length, "Show length of the song."},
+ {"version", cmd_version, "Show libsmf version."},
+ {"exit", cmd_exit, "Exit to shell."},
+ {"quit", cmd_exit, NULL},
+ {"bye", cmd_exit, NULL},
+ {NULL, NULL, NULL}};
+
+static int
+cmd_help(char *notused)
+{
+ int i, padding_length;
+ char padding[COMMAND_LENGTH + 1];
+ struct command_struct *tmp;
+
+ g_message("Available commands:");
+
+ for (tmp = commands; tmp->name != NULL; tmp++) {
+ /* Skip commands with no help string. */
+ if (tmp->help == NULL)
+ continue;
+
+ padding_length = COMMAND_LENGTH - strlen(tmp->name);
+ assert(padding_length >= 0);
+ for (i = 0; i < padding_length; i++)
+ padding[i] = ' ';
+ padding[i] = '\0';
+
+ g_message("%s:%s%s", tmp->name, padding, tmp->help);
+ }
+
+ return (0);
+}
+
+/**
+ * Removes (in place) all whitespace characters before the first
+ * non-whitespace and all trailing whitespace characters. Replaces
+ * more than one consecutive whitespace characters with one.
+ */
+static void
+strip_unneeded_whitespace(char *str, int len)
+{
+ char *src, *dest;
+ int skip_white = 1;
+
+ for (src = str, dest = str; src < dest + len; src++) {
+ if (*src == '\n' || *src == '\0') {
+ *dest = '\0';
+ break;
+ }
+
+ if (isspace(*src)) {
+ if (skip_white)
+ continue;
+
+ skip_white = 1;
+ } else {
+ skip_white = 0;
+ }
+
+ *dest = *src;
+ dest++;
+ }
+
+ /* Remove trailing whitespace. */
+ len = strlen(dest);
+ if (isspace(dest[len - 1]))
+ dest[len - 1] = '\0';
+}
+
+static char *
+read_command(void)
+{
+ char *buf;
+ int len;
+
+#ifdef HAVE_LIBREADLINE
+ buf = readline("smfsh> ");
+#else
+ buf = malloc(1024);
+ if (buf == NULL) {
+ g_critical("Malloc failed.");
+ return (NULL);
+ }
+
+ fprintf(stdout, "smfsh> ");
+ fflush(stdout);
+
+ buf = fgets(buf, 1024, stdin);
+#endif
+
+ if (buf == NULL) {
+ fprintf(stdout, "exit\n");
+ return (strdup("exit"));
+ }
+
+ strip_unneeded_whitespace(buf, 1024);
+
+ len = strlen(buf);
+
+ if (len == 0)
+ return (read_command());
+
+#ifdef HAVE_LIBREADLINE
+ add_history(buf);
+#endif
+
+ return (buf);
+}
+
+static int
+execute_command(char *line)
+{
+ char *command, *args;
+ struct command_struct *tmp;
+
+ command = line;
+ args = strchr(line, ' ');
+ if (args != NULL) {
+ *args = '\0';
+ args++;
+ }
+
+ for (tmp = commands; tmp->name != NULL; tmp++) {
+ if (strcmp(tmp->name, command) == 0)
+ return ((tmp->function)(args));
+ }
+
+ g_warning("No such command: '%s'. Type 'help' to see available commands.", command);
+
+ return (-1);
+}
+
+static void
+read_and_execute_command(void)
+{
+ int ret;
+ char *command_line, *command, *next_command;
+
+ command = command_line = read_command();
+
+ do {
+ next_command = strchr(command, ';');
+ if (next_command != NULL) {
+ *next_command = '\0';
+ next_command++;
+ }
+
+ strip_unneeded_whitespace(command, 1024);
+ if (strlen(command) > 0) {
+ ret = execute_command(command);
+ if (ret)
+ g_warning("Command finished with error.");
+ }
+
+ command = next_command;
+
+ } while (command);
+
+ free(command_line);
+}
+
+#ifdef HAVE_LIBREADLINE
+
+static char *
+smfsh_command_generator(const char *text, int state)
+{
+ static struct command_struct *command = commands;
+ char *tmp;
+
+ if (state == 0)
+ command = commands;
+
+ while (command->name != NULL) {
+ tmp = command->name;
+ command++;
+
+ if (strncmp(tmp, text, strlen(text)) == 0)
+ return (strdup(tmp));
+ }
+
+ return (NULL);
+}
+
+static char **
+smfsh_completion(const char *text, int start, int end)
+{
+ int i;
+
+ /* Return NULL if "text" is not the first word in the input line. */
+ if (start != 0) {
+ for (i = 0; i < start; i++) {
+ if (!isspace(rl_line_buffer[i]))
+ return (NULL);
+ }
+ }
+
+ return (rl_completion_matches(text, smfsh_command_generator));
+}
+
+#endif
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: smfsh [-V | file]\n");
+
+ exit(EX_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "V")) != -1) {
+ switch (ch) {
+ case 'V':
+ cmd_version(NULL);
+ exit(EX_OK);
+
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if (argc > 2)
+ usage();
+
+ g_log_set_default_handler(log_handler, NULL);
+
+ smf = smf_new();
+ if (smf == NULL) {
+ g_critical("Cannot initialize smf_t.");
+ return (-1);
+ }
+
+ if (argc == 2)
+ cmd_load(argv[1]);
+ else
+ cmd_trackadd(NULL);
+
+#ifdef HAVE_LIBREADLINE
+ rl_readline_name = "smfsh";
+ rl_attempted_completion_function = smfsh_completion;
+#endif
+
+ for (;;)
+ read_and_execute_command();
+
+ return (0);
+}
+