Include stripped down libsmf code internally.
authorDavid Robillard <d@drobilla.net>
Wed, 11 Feb 2009 17:38:40 +0000 (17:38 +0000)
committerDavid Robillard <d@drobilla.net>
Wed, 11 Feb 2009 17:38:40 +0000 (17:38 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@4531 d708f5d6-7413-0410-9779-e7cbd77b26cf

13 files changed:
libs/evoral/evoral/LibSMF.hpp
libs/evoral/src/LibSMF.cpp
libs/evoral/src/libsmf/COPYING [new file with mode: 0644]
libs/evoral/src/libsmf/README [new file with mode: 0644]
libs/evoral/src/libsmf/smf.c [new file with mode: 0644]
libs/evoral/src/libsmf/smf.h [new file with mode: 0644]
libs/evoral/src/libsmf/smf_decode.c [new file with mode: 0644]
libs/evoral/src/libsmf/smf_load.c [new file with mode: 0644]
libs/evoral/src/libsmf/smf_private.h [new file with mode: 0644]
libs/evoral/src/libsmf/smf_save.c [new file with mode: 0644]
libs/evoral/src/libsmf/smf_tempo.c [new file with mode: 0644]
libs/evoral/src/libsmf/smfsh.c [new file with mode: 0644]
libs/evoral/wscript

index 1bc50c82597f0e9775c550375ab9f536a115107c..f2c5e318adb01694c54418e8d5c1a6a0deac8f2c 100644 (file)
 #ifndef EVORAL_LIB_SMF_HPP
 #define EVORAL_LIB_SMF_HPP
 
+#include <cassert>
 #include "evoral/StandardMIDIFile.hpp"
 
-#include <smf.h>
-#include <cassert>
+struct smf_struct;
+struct smf_track_struct;
+typedef smf_struct smf_t;
+typedef smf_track_struct smf_track_t;
 
 namespace Evoral {
        
@@ -36,13 +39,7 @@ template<typename Time>
 class LibSMF : public StandardMIDIFile<Time> {
 public:
        LibSMF() : _last_ev_time(0), _smf(0), _smf_track(0), _empty(true) {};
-       virtual ~LibSMF() {     
-               if (_smf) {
-                       smf_delete(_smf);
-                       _smf = 0;
-                       _smf_track = 0;
-               } 
-       }
+       virtual ~LibSMF();
 
        void seek_to_start() const;
        
@@ -69,7 +66,7 @@ protected:
 private:
        static const uint16_t _ppqn = 19200;
        
-       Time     _last_ev_time; ///< last frame time written, relative to source start
+       Time _last_ev_time; ///< last frame time written, relative to source start
        
        std::string  _path;
        smf_t*       _smf;
index c0c59fa3f0191fcabb46a398ec54d3dfb910e6f4..ca00757f478ee999f1a57579242c85642d2161a0 100644 (file)
@@ -1,13 +1,41 @@
-#include "evoral/LibSMF.hpp"
-#include "evoral/Event.hpp"
-#include <cassert>
+/* This file is part of Evoral.
+ * Copyright (C) 2008 Dave Robillard <http://drobilla.net>
+ * Copyright (C) 2000-2008 Paul Davis
+ * Author: Hans Baier
+ * 
+ * Evoral is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ * 
+ * Evoral is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for details.
+ * 
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
 
+#include <cassert>
 #include <iostream>
+#include "evoral/Event.hpp"
+#include "evoral/LibSMF.hpp"
+#include "libsmf/smf.h"
 
 using namespace std;
 
 namespace Evoral {
 
+template<typename Time>
+LibSMF<Time>::~LibSMF()
+{      
+       if (_smf) {
+               smf_delete(_smf);
+               _smf = 0;
+               _smf_track = 0;
+       } 
+}
 
 /** Attempt to open the SMF file for reading and writing.
  *
diff --git a/libs/evoral/src/libsmf/COPYING b/libs/evoral/src/libsmf/COPYING
new file mode 100644 (file)
index 0000000..79ec85f
--- /dev/null
@@ -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/src/libsmf/README b/libs/evoral/src/libsmf/README
new file mode 100644 (file)
index 0000000..c1a4035
--- /dev/null
@@ -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/src/libsmf/smf.c b/libs/evoral/src/libsmf/smf.c
new file mode 100644 (file)
index 0000000..8820651
--- /dev/null
@@ -0,0 +1,1112 @@
+/*-
+ * 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>
+#include <arpa/inet.h>
+#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 = malloc(sizeof(smf_t));
+       if (smf == NULL) {
+               g_critical("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(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 = malloc(sizeof(smf_track_t));
+       if (track == NULL) {
+               g_critical("Cannot allocate smf_track_t structure: %s", strerror(errno));
+               return (NULL);
+       }
+
+       memset(track, 0, sizeof(smf_track_t));
+       track->next_event_number = -1;
+
+       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, from last to first. */
+       while (track->events_array->len > 0)
+               smf_event_delete(g_ptr_array_index(track->events_array, track->events_array->len - 1));
+
+       if (track->smf)
+               smf_track_remove_from_smf(track);
+
+       assert(track->events_array->len == 0);
+       assert(track->number_of_events == 0);
+       g_ptr_array_free(track->events_array, TRUE);
+
+       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)
+{
+       int cantfail;
+
+       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) {
+               cantfail = smf_set_format(smf, 1);
+               assert(!cantfail);
+       }
+}
+
+/**
+ * Detaches track from the smf.
+ */
+void
+smf_track_remove_from_smf(smf_track_t *track)
+{
+       int i, 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 = malloc(sizeof(smf_event_t));
+       if (event == NULL) {
+               g_critical("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(void *midi_data, int len)
+{
+       smf_event_t *event;
+
+       event = smf_event_new();
+       if (event == NULL)
+               return (NULL);
+
+       event->midi_buffer_length = len;
+       event->midi_buffer = malloc(event->midi_buffer_length);
+       if (event->midi_buffer == NULL) {
+               g_critical("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)
+{
+       int len;
+
+       smf_event_t *event;
+
+       event = smf_event_new();
+       if (event == NULL)
+               return (NULL);
+
+       if (first_byte < 0) {
+               g_critical("First byte of MIDI message cannot be < 0");
+               smf_event_delete(event);
+
+               return (NULL);
+       }
+
+       if (first_byte > 255) {
+               g_critical("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_critical("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_critical("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_critical("smf_event_new_from_bytes: second byte cannot be a status byte.");
+                       return (NULL);
+               }
+       }
+
+       if (len > 2) {
+               if (third_byte > 255) {
+                       g_critical("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_critical("smf_event_new_from_bytes: third byte cannot be a status byte.");
+                       return (NULL);
+               }
+       }
+
+       event->midi_buffer_length = len;
+       event->midi_buffer = malloc(event->midi_buffer_length);
+       if (event->midi_buffer == NULL) {
+               g_critical("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)
+{
+       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 = (smf_event_t *)*(gpointer *)aa;
+       b = (smf_event_t *)*(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, int 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)
+{
+       int i, last_pulses = 0;
+
+       assert(track->smf != NULL);
+       assert(event->track == NULL);
+       assert(event->delta_time_pulses == -1);
+       assert(event->time_pulses >= 0);
+       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 == -1);
+               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, int 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, int 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)
+{
+       int i, 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 = -1;
+
+       /* 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 = -1;
+       event->delta_time_pulses = -1;
+       event->time_pulses = -1;
+       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_critical("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, int ppqn)
+{
+       assert(ppqn > 0);
+
+       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;
+
+       /* End of track? */
+       if (track->next_event_number == -1)
+               return (NULL);
+
+       assert(track->next_event_number >= 1);
+       assert(track->number_of_events > 0);
+
+       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 = -1;
+       }
+
+       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 == -1)
+               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, int event_number)
+{
+       smf_event_t *event;
+
+       assert(event_number >= 1);
+
+       if (event_number > track->number_of_events)
+               return (NULL);
+
+       event = 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, 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 == -1)
+                       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)
+{
+       void *notused;
+
+       notused = smf_get_next_event(smf);
+}
+
+/**
+  * \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 = -1;
+                       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_critical("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, int pulses)
+{
+       smf_event_t *event;
+
+       assert(pulses >= 0);
+
+       smf_rewind(smf);
+
+#if 0
+       g_debug("Seeking to %d pulses.", pulses);
+#endif
+
+       for (;;) {
+               event = smf_peek_next_event(smf);
+
+               if (event == NULL) {
+                       g_critical("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.
+  */
+int
+smf_get_length_pulses(const smf_t *smf)
+{
+       int pulses = 0, i;
+
+       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/src/libsmf/smf.h b/libs/evoral/src/libsmf/smf.h
new file mode 100644 (file)
index 0000000..e2a0c3b
--- /dev/null
@@ -0,0 +1,404 @@
+/*-
+ * 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.
+ */
+
+/**
+ *
+ * \mainpage 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 Change or Time Signature, libsmf adds that event to the tempo map.  If you remove
+ * Tempo Change 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 Change 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 <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. */
+       int             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;
+       int             file_buffer_length;
+       int             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 {
+       int 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;
+       int             number_of_events;
+
+       /** These are private fields using only by loading and saving routines. */
+       void            *file_buffer;
+       int             file_buffer_length;
+       int             last_status; /* Used for "running status". */
+
+       /** Private, used by smf.c. */
+       /** Offset into buffer, used in parse_next_event(). */
+       int             next_event_offset;
+       int             next_event_number;
+
+       /** Absolute time of next event on events_queue. */
+       int             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 one. */
+       int             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. */
+       int             delta_time_pulses;
+
+       /** Time, in pulses, since the start of the song. */
+       int             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. */
+       unsigned char   *midi_buffer;
+
+       /** Length of the MIDI message in the buffer, in bytes. */
+       int             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, int format) 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, int pulses) WARN_UNUSED_RESULT;
+int smf_seek_to_event(smf_t *smf, const smf_event_t *event) WARN_UNUSED_RESULT;
+
+int 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, int event_number) 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, int pulses);
+void smf_track_add_event_pulses(smf_track_t *track, smf_event_t *event, int 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, int delta) WARN_UNUSED_RESULT;
+int smf_track_add_eot_pulses(smf_track_t *track, int 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(void *midi_data, int len) WARN_UNUSED_RESULT;
+smf_event_t *smf_event_new_from_bytes(int first_byte, int second_byte, int third_byte) 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 loading SMF files. */
+smf_t *smf_load(const char *file_name) WARN_UNUSED_RESULT;
+smf_t *smf_load_from_memory(const void *buffer, const int buffer_length) WARN_UNUSED_RESULT;
+
+/* Routine for writing SMF files. */
+int smf_save(smf_t *smf, const char *file_name) WARN_UNUSED_RESULT;
+
+/* Routines for manipulating smf_tempo_t. */
+smf_tempo_t *smf_get_tempo_by_pulses(const smf_t *smf, int pulses) WARN_UNUSED_RESULT;
+smf_tempo_t *smf_get_tempo_by_seconds(const smf_t *smf, double seconds) WARN_UNUSED_RESULT;
+smf_tempo_t *smf_get_tempo_by_number(const smf_t *smf, int 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/src/libsmf/smf_decode.c b/libs/evoral/src/libsmf/smf_decode.c
new file mode 100644 (file)
index 0000000..1c1b91e
--- /dev/null
@@ -0,0 +1,634 @@
+/*-
+ * 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>
+#include <arpa/inet.h>
+#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 = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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 = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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_critical("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_critical("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_critical("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_critical("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(2, event->midi_buffer[4]), event->midi_buffer[5],
+                               event->midi_buffer[6]);
+                       break;
+
+               case 0x59:
+                       if (event->midi_buffer_length < 5) {
+                               g_critical("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_critical("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 && i < sizeof(minor_keys) / sizeof(*minor_keys));
+                               assert(i >= 0 && 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 %d",
+                               event->midi_buffer_length);
+                       break;
+
+               default:
+                       goto error;
+       }
+
+       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_critical("smf_event_decode_system_realtime: event length is not 1.");
+               return (NULL);
+       }
+
+       buf = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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);
+       }
+
+       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_critical("smf_event_decode_sysex: truncated MIDI message.");
+               return (NULL);
+       }
+
+       buf = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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);
+
+               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");
+
+       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 = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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);
+       }
+
+       return (buf);
+}
+
+static void
+note_from_int(char *buf, int note_number)
+{
+       int note, octave;
+       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_critical("smf_event_decode: incorrect MIDI message length.");
+               return (NULL);
+       }
+
+       buf = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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);
+       }
+
+       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 = malloc(BUFFER_SIZE);
+       if (buf == NULL) {
+               g_critical("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);
+
+       return (buf);
+}
+
diff --git a/libs/evoral/src/libsmf/smf_load.c b/libs/evoral/src/libsmf/smf_load.c
new file mode 100644 (file)
index 0000000..98c7789
--- /dev/null
@@ -0,0 +1,922 @@
+/*-
+ * Copyright (c) 2007, 2008 Edward Tomasz Napierała <trasz@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * ALTHOUGH THIS SOFTWARE IS MADE OF WIN AND SCIENCE, IT IS PROVIDED BY THE
+ * AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
+ * THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/**
+ * \file
+ *
+ * Standard MIDI File format loader.
+ *
+ */
+
+/* Reference: http://www.borg.com/~jglatt/tech/midifile.htm */
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+#include <errno.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+#include "smf.h"
+#include "smf_private.h"
+
+/**
+ * Returns pointer to the next SMF chunk in smf->buffer, based on length of the previous one.
+ * Returns NULL in case of error.
+ */
+static struct chunk_header_struct *
+next_chunk(smf_t *smf)
+{
+       struct chunk_header_struct *chunk;
+       void *next_chunk_ptr;
+
+       assert(smf->file_buffer != NULL);
+       assert(smf->file_buffer_length > 0);
+       assert(smf->next_chunk_offset >= 0);
+
+       if (smf->next_chunk_offset + sizeof(struct chunk_header_struct) >= smf->file_buffer_length) {
+               g_critical("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_critical("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_critical("SMF error: malformed chunk; truncated file?");
+               return (NULL);
+       }
+
+       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_critical("SMF error: file is too short, it cannot be a MIDI file.");
+
+               return (-1);
+       }
+
+       tmp_mthd = smf->file_buffer;
+
+       if (!chunk_signature_matches(tmp_mthd, "MThd")) {
+               g_critical("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_critical("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_critical("SMF error: bad MThd format field value: %d, valid values are 0-2, inclusive.", smf->format);
+               return (-1);
+       }
+
+       if (smf->format == 2) {
+               g_critical("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_critical("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_critical("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.
+ */
+static int
+extract_vlq(const unsigned char *buf, const int buffer_length, int *value, int *len)
+{
+       int val = 0;
+       const unsigned char *c = buf;
+
+       assert(buffer_length > 0);
+
+       for (;;) {
+               if (c >= buf + buffer_length) {
+                       g_critical("End of buffer in extract_vlq().");
+                       return (-1);
+               }
+
+               val = (val << 7) + (*c & 0x7F);
+
+               if (*c & 0x80)
+                       c++;
+               else
+                       break;
+       };
+
+       *value = val;
+       *len = c - buf + 1;
+
+       if (*len > 4) {
+               g_critical("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 int
+expected_sysex_length(const unsigned char status, const unsigned char *second_byte, const int buffer_length, int *consumed_bytes)
+{
+       int sysex_length, len;
+
+       assert(status == 0xF0);
+
+       if (buffer_length < 3) {
+               g_critical("SMF error: end of buffer in expected_sysex_length().");
+               return (-1);
+       }
+
+       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 int
+expected_escaped_length(const unsigned char status, const unsigned char *second_byte, const int buffer_length, int *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 int
+expected_message_length(unsigned char status, const unsigned char *second_byte, const int 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));
+
+       /* Buffer length may be zero, for e.g. realtime messages. */
+       assert(buffer_length >= 0);
+
+       /* Is this a metamessage? */
+       if (status == 0xFF) {
+               if (buffer_length < 2) {
+                       g_critical("SMF error: end of buffer in expected_message_length().");
+                       return (-1);
+               }
+
+               /*
+                * Format of this kind of messages is like this: 0xFF 0xwhatever 0xlength and then "length" bytes.
+                * Second byte points to this:                        ^^^^^^^^^^
+                */
+               return (*(second_byte + 1) + 3);
+       }
+
+       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_critical("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_critical("SMF error: unknown status byte '0x%x'.", status);
+                       return (-3);
+       }
+}
+
+static int
+extract_sysex_event(const unsigned char *buf, const int buffer_length, smf_event_t *event, int *len, int last_status)
+{
+       int status, message_length, vlq_length;
+       const unsigned char *c = buf;
+
+       status = *buf;
+
+       assert(is_sysex_byte(status));
+
+       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 + message_length >= buffer_length) {
+               g_critical("End of buffer in extract_sysex_event().");
+               return (-5);
+       }
+
+       event->midi_buffer_length = message_length;
+       event->midi_buffer = malloc(event->midi_buffer_length);
+       if (event->midi_buffer == NULL) {
+               g_critical("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 int buffer_length, smf_event_t *event, int *len, int last_status)
+{
+       int status, message_length, vlq_length;
+       const unsigned char *c = buf;
+
+       status = *buf;
+
+       assert(is_escape_byte(status));
+
+       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 + message_length >= buffer_length) {
+               g_critical("End of buffer in extract_escaped_event().");
+               return (-5);
+       }
+
+       event->midi_buffer_length = message_length;
+       event->midi_buffer = malloc(event->midi_buffer_length);
+       if (event->midi_buffer == NULL) {
+               g_critical("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_critical("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 int buffer_length, smf_event_t *event, int *len, int last_status)
+{
+       int status, 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_critical("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 (message_length - 1 > buffer_length - (c - buf)) {
+               g_critical("End of buffer in extract_midi_event().");
+               return (-5);
+       }
+
+       event->midi_buffer_length = message_length;
+       event->midi_buffer = malloc(event->midi_buffer_length);
+       if (event->midi_buffer == NULL) {
+               g_critical("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)
+{
+       int time = 0, len, 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;
+       assert(buffer_length > 0);
+
+       /* First, extract time offset from previous event. */
+       if (extract_vlq(c, buffer_length, &time, &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, time);
+
+       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 int buffer_length, int len)
+{
+       char *str;
+
+       assert(buffer_length > 0);
+       assert(len > 0);
+
+       if (len > buffer_length) {
+               g_critical("End of buffer in make_string().");
+
+               len = buffer_length;
+       }
+
+       str = malloc(len + 1);
+       if (str == NULL) {
+               g_critical("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[3] < 1 && event->midi_buffer[3] > 9)
+               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)
+{
+       int string_length = -1, length_length = -1;
+
+       if (!smf_event_is_textual(event))
+               return (NULL);
+
+       if (event->midi_buffer_length < 3) {
+               g_critical("smf_event_extract_text: truncated MIDI message.");
+               return (NULL);
+       }
+
+       extract_vlq((void *)&(event->midi_buffer[2]), event->midi_buffer_length - 2, &string_length, &length_length);
+
+       if (string_length <= 0) {
+               g_critical("smf_event_extract_text: truncated MIDI message.");
+               return (NULL);
+       }
+
+       return (make_string((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);
+
+       if (event->midi_buffer_length < 1)
+               return (0);
+
+       /* We cannot use expected_message_length on sysexes. */
+       if (smf_event_is_sysex(event))
+               return (1);
+
+       if (event->midi_buffer_length != expected_message_length(event->midi_buffer[0],
+               &(event->midi_buffer[1]), event->midi_buffer_length - 1)) {
+
+               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_critical("First byte of MIDI message is not a valid status byte.");
+
+               return (0);
+       }
+
+       if (!smf_event_length_is_valid(event))
+               return (0);
+
+       return (1);
+}
+
+/**
+ * Parse events and put it on the track.
+ */
+static int
+parse_mtrk_chunk(smf_track_t *track)
+{
+       smf_event_t *event;
+
+       if (parse_mtrk_header(track))
+               return (-1);
+
+       for (;;) {
+               event = parse_next_event(track);
+
+               /* Couldn't parse an event? */
+               if (event == NULL)
+                       return (-1);
+
+               assert(smf_event_is_valid(event));
+
+               if (event_is_end_of_track(event))
+                       break;
+       }
+
+       track->file_buffer = NULL;
+       track->file_buffer_length = 0;
+       track->next_event_offset = -1;
+
+       return (0);
+}
+
+/**
+ * Allocate buffer of proper size and read file contents into it.  Close file afterwards.
+ */
+static int
+load_file_into_buffer(void **file_buffer, int *file_buffer_length, const char *file_name)
+{
+       FILE *stream = fopen(file_name, "r");
+
+       if (stream == NULL) {
+               g_critical("Cannot open input file: %s", strerror(errno));
+
+               return (-1);
+       }
+
+       if (fseek(stream, 0, SEEK_END)) {
+               g_critical("fseek(3) failed: %s", strerror(errno));
+
+               return (-2);
+       }
+
+       *file_buffer_length = ftell(stream);
+       if (*file_buffer_length == -1) {
+               g_critical("ftell(3) failed: %s", strerror(errno));
+
+               return (-3);
+       }
+
+       if (fseek(stream, 0, SEEK_SET)) {
+               g_critical("fseek(3) failed: %s", strerror(errno));
+
+               return (-4);
+       }
+
+       *file_buffer = malloc(*file_buffer_length);
+       if (*file_buffer == NULL) {
+               g_critical("malloc(3) failed: %s", strerror(errno));
+
+               return (-5);
+       }
+
+       if (fread(*file_buffer, 1, *file_buffer_length, stream) != *file_buffer_length) {
+               g_critical("fread(3) failed: %s", strerror(errno));
+
+               return (-6);
+       }
+       
+       if (fclose(stream)) {
+               g_critical("fclose(3) failed: %s", strerror(errno));
+
+               return (-7);
+       }
+
+       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(const void *buffer, const int buffer_length)
+{
+       int i;
+
+       smf_t *smf = smf_new();
+
+       smf->file_buffer = (void *)buffer;
+       smf->file_buffer_length = buffer_length;
+       smf->next_chunk_offset = 0;
+
+       if (parse_mthd_chunk(smf))
+               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);
+
+               /* Skip unparseable chunks. */
+               if (parse_mtrk_chunk(track)) {
+                       g_warning("SMF warning: Cannot load track.");
+                       smf_track_delete(track);
+               }
+
+               track->file_buffer = NULL;
+               track->file_buffer_length = 0;
+               track->next_event_offset = -1;
+       }
+
+       if (smf->expected_number_of_tracks != smf->number_of_tracks) {
+               g_warning("SMF warning: MThd header declared %d tracks, but only %d found; continuing anyway.",
+                               smf->expected_number_of_tracks, smf->number_of_tracks);
+
+               smf->expected_number_of_tracks = smf->number_of_tracks;
+       }
+
+       smf->file_buffer = NULL;
+       smf->file_buffer_length = 0;
+       smf->next_chunk_offset = -1;
+
+       return (smf);
+}
+
+/**
+ * Loads SMF file.
+ *
+ * \param file_name Path to the file.
+ * \return SMF or NULL, if loading failed.
+ */
+smf_t *
+smf_load(const char *file_name)
+{
+       int file_buffer_length;
+       void *file_buffer;
+       smf_t *smf;
+
+       if (load_file_into_buffer(&file_buffer, &file_buffer_length, file_name))
+               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/src/libsmf/smf_private.h b/libs/evoral/src/libsmf/smf_private.h
new file mode 100644 (file)
index 0000000..0cd273e
--- /dev/null
@@ -0,0 +1,80 @@
+/*-
+ * 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, int 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;
+
+#endif /* SMF_PRIVATE_H */
+
diff --git a/libs/evoral/src/libsmf/smf_save.c b/libs/evoral/src/libsmf/smf_save.c
new file mode 100644 (file)
index 0000000..95ed14b
--- /dev/null
@@ -0,0 +1,656 @@
+/*-
+ * 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>
+#include <arpa/inet.h>
+#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 = 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_critical("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_critical("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_critical("Cannot extend track buffer.");
+               return (-1);
+       }
+
+       memcpy(dest, buffer, buffer_length);
+
+       return (0);
+}
+
+static int
+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 = malloc(event->midi_buffer_length);
+       if (event->midi_buffer == NULL) {
+               g_critical("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 = 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);
+
+       assert(copied_length == text_length);
+
+       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 = 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);
+
+       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, const char *file_name)
+{
+       FILE *stream;
+
+       stream = fopen(file_name, "w+");
+       if (stream == NULL) {
+               g_critical("Cannot open input file: %s", strerror(errno));
+
+               return (-1);
+       }
+
+       if (fwrite(smf->file_buffer, 1, smf->file_buffer_length, stream) != smf->file_buffer_length) {
+               g_critical("fwrite(3) failed: %s", strerror(errno));
+
+               return (-2);
+       }
+
+       if (fclose(stream)) {
+               g_critical("fclose(3) failed: %s", strerror(errno));
+
+               return (-3);
+       }
+
+       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;
+       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, eventno, eot_found;
+       smf_track_t *track;
+       smf_event_t *event;
+
+       if (smf->format < 0 || smf->format > 2) {
+               g_critical("SMF error: smf->format is less than zero of greater than two.");
+               return (-1);
+       }
+
+       if (smf->number_of_tracks < 1) {
+               g_critical("SMF error: number of tracks is less than one.");
+               return (-2);
+       }
+
+       if (smf->format == 0 && smf->number_of_tracks > 1) {
+               g_critical("SMF error: format is 0, but number of tracks is more than one.");
+               return (-3);
+       }
+
+       if (smf->ppqn <= 0) {
+               g_critical("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_critical("Event #%d on track #%d is invalid.", eventno, trackno);
+                               return (-5);
+                       }
+
+                       if (smf_event_is_eot(event)) {
+                               if (eot_found) {
+                                       g_critical("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_critical("smf_track_add_eot_delta_pulses failed.");
+                               return (-6);
+                       }
+               }
+                               
+       }
+
+       return (0);
+}
+
+#ifndef NDEBUG
+
+static void
+assert_smf_event_is_identical(const smf_event_t *a, const smf_event_t *b)
+{
+       assert(a->event_number == b->event_number);
+       assert(a->delta_time_pulses == b->delta_time_pulses);
+       assert(abs(a->time_pulses - b->time_pulses) <= 2);
+       assert(fabs(a->time_seconds - b->time_seconds) <= 0.01);
+       assert(a->track_number == b->track_number);
+       assert(a->midi_buffer_length == b->midi_buffer_length);
+       assert(memcmp(a->midi_buffer, b->midi_buffer, a->midi_buffer_length) == 0);
+}
+
+static void
+assert_smf_track_is_identical(const smf_track_t *a, const smf_track_t *b)
+{
+       int i;
+
+       assert(a->track_number == b->track_number);
+       assert(a->number_of_events == b->number_of_events);
+
+       for (i = 1; i <= a->number_of_events; i++)
+               assert_smf_event_is_identical(smf_track_get_event_by_number(a, i), smf_track_get_event_by_number(b, i));
+}
+
+static void
+assert_smf_is_identical(const smf_t *a, const smf_t *b)
+{
+       int i;
+
+       assert(a->format == b->format);
+       assert(a->ppqn == b->ppqn);
+       assert(a->frames_per_second == b->frames_per_second);
+       assert(a->resolution == b->resolution);
+       assert(a->number_of_tracks == b->number_of_tracks);
+
+       for (i = 1; i <= a->number_of_tracks; i++)
+               assert_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. */
+}
+
+static void
+assert_smf_saved_correctly(const smf_t *smf, const char *file_name)
+{
+       smf_t *saved;
+
+       saved = smf_load(file_name);
+       assert(saved != NULL);
+
+       assert_smf_is_identical(smf, saved);
+
+       smf_delete(saved);
+}
+
+#endif /* !NDEBUG */
+
+/**
+  * Writes the contents of SMF to the file given.
+  * \param smf SMF.
+  * \param file_name Path to the file.
+  * \return 0, if saving was successfull.
+  */
+int
+smf_save(smf_t *smf, const char *file_name)
+{
+       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_name);
+
+       free_buffer(smf);
+
+       if (error)
+               return (error);
+
+#ifndef NDEBUG
+       assert_smf_saved_correctly(smf, file_name);
+#endif
+
+       return (0);
+}
+
diff --git a/libs/evoral/src/libsmf/smf_tempo.c b/libs/evoral/src/libsmf/smf_tempo.c
new file mode 100644 (file)
index 0000000..a5592f1
--- /dev/null
@@ -0,0 +1,448 @@
+/*-
+ * 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, int 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, int 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 = malloc(sizeof(smf_tempo_t));
+       if (tempo == NULL) {
+               g_critical("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 new_tempo = (event->midi_buffer[3] << 16) + (event->midi_buffer[4] << 8) + event->midi_buffer[5];
+               if (new_tempo <= 0) {
+                       g_critical("Ignoring invalid tempo change.");
+                       return;
+               }
+
+               add_tempo(event->track->smf, event->time_pulses, new_tempo);
+       }
+
+       /* Time Signature? */
+       if (event->midi_buffer[1] == 0x58) {
+               int numerator, denominator, clocks_per_click, notes_per_note;
+
+               if (event->midi_buffer_length < 7) {
+                       g_critical("Time Signature event seems truncated.");
+                       return;
+               }
+
+               numerator = event->midi_buffer[3];
+               denominator = (int)pow(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, int 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, int 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. */
+}
+
+smf_tempo_t *
+smf_get_tempo_by_number(const smf_t *smf, int number)
+{
+       assert(number >= 0);
+
+       if (number >= smf->tempo_array->len)
+               return (NULL);
+
+       return (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, int pulses)
+{
+       int i;
+       smf_tempo_t *tempo;
+
+       assert(pulses >= 0);
+
+       if (pulses == 0)
+               return (smf_get_tempo_by_number(smf, 0));
+
+       assert(smf->tempo_array != NULL);
+       
+       for (i = smf->tempo_array->len - 1; i >= 0; i--) {
+               tempo = smf_get_tempo_by_number(smf, i);
+
+               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)
+{
+       int 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 - 1; i >= 0; i--) {
+               tempo = smf_get_tempo_by_number(smf, i);
+
+               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;
+
+       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 = 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_error) 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_error("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);
+               assert(previous_event->time_pulses >= 0);
+
+               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, int delta)
+{
+       assert(delta >= 0);
+       assert(event->time_pulses == -1);
+       assert(event->time_seconds == -1.0);
+       assert(track->smf != NULL);
+
+       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, int pulses)
+{
+       assert(pulses >= 0);
+       assert(event->time_pulses == -1);
+       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_pulses == -1);
+       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/src/libsmf/smfsh.c b/libs/evoral/src/libsmf/smfsh.c
new file mode 100644 (file)
index 0000000..139b676
--- /dev/null
@@ -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);
+}
+
index 02a71b3f48b451a2328b1b6092579ef5c1631fd6..8c0202ee94f1175d844e971fc1a2626333c8d7f5 100644 (file)
@@ -26,10 +26,10 @@ def set_options(opt):
 def configure(conf):
        autowaf.configure(conf)
        autowaf.check_tool(conf, 'compiler_cxx')
+       autowaf.check_pkg(conf, 'glib-2.0', uselib_store='GLIB', atleast_version='2.2', mandatory=True)
        autowaf.check_pkg(conf, 'glibmm-2.4', uselib_store='GLIBMM', atleast_version='2.14.0', mandatory=True)
        autowaf.check_pkg(conf, 'gthread-2.0', uselib_store='GTHREAD', atleast_version='2.14.0', mandatory=True)
        autowaf.check_pkg(conf, 'cppunit', uselib_store='CPPUNIT', atleast_version='1.12.0', mandatory=False)
-       autowaf.check_pkg(conf, 'smf', uselib_store='SMF', atleast_version='1.2', mandatory=False)
 
 def build(bld):
        # Headers
@@ -39,6 +39,22 @@ def build(bld):
        # Pkgconfig file
        #autowaf.build_pc(bld, 'EVORAL', EVORAL_VERSION, 'GLIBMM GTHREAD')
        
+       libsmf = bld.new_task_gen('cc', 'shlib')
+       libsmf.source = '''
+               src/libsmf/smf.c
+               src/libsmf/smf_decode.c
+               src/libsmf/smf_load.c
+               src/libsmf/smf_save.c
+               src/libsmf/smf_tempo.c
+       '''
+       libsmf.export_incdirs = ['./src/libsmf']
+       libsmf.defines      = 'SMF_VERSION=\\\"1.2\\\"'
+       libsmf.includes     = ['./src']
+       libsmf.name         = 'libsmf'
+       libsmf.target       = 'smf'
+       libsmf.uselib       = 'GLIB'
+       libsmf.install_path = ''
+
        # Library
        obj = bld.new_task_gen('cxx', 'shlib')
        obj.source = '''
@@ -59,6 +75,7 @@ def build(bld):
        obj.name         = 'libevoral'
        obj.target       = 'evoral'
        obj.uselib       = 'GLIBMM GTHREAD SMF'
+       obj.uselib_local = 'libsmf'
        obj.vnum         = EVORAL_LIB_VERSION
        obj.install_path = ''