summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWim Taymans <wim.taymans@collabora.co.uk>2013-03-21 06:50:52 +0100
committerTim-Philipp Müller <tim@centricular.net>2013-07-22 12:03:10 +0100
commit4194c9f655f69629233674eec840fa99e0932626 (patch)
tree5c3f18814ee8776fbec067c5b9eb9c8a17aae30c
parent3a5fa02e27e25c729dc3f5120e2f06348674b563 (diff)
downloadgstreamer-plugins-bad-4194c9f655f69629233674eec840fa99e0932626.tar.gz
midiparse: add basic midi parser
Parses midi files and outputs buffers with midi events. Pick midi parser from master branch. midi: set rank as marginal midiparse: small fixes midiparse: handle midi in RIFF midiparse: Update duration midiparse: fix klass midiparse: duration is max of all track durations midiparse: small cleanups in error paths midiparse: don't leak midi data midiparse: clean up tracks midiparse: simplify the callbacks midiparse: implement seeking midiparse: always seek back to beginning midi: set midi parser rank primary midiparser: ensure variable is initialized and fix debug message access midiparse: Push stream-start event before anything else midiparse: In pull mode drop SEGMENT, CAPS and STREAM_START events gst: Add better support for static plugins https://bugzilla.gnome.org/show_bug.cgi?id=696041
-rw-r--r--configure.ac2
-rw-r--r--gst/midi/Makefile.am30
-rw-r--r--gst/midi/midi.c58
-rw-r--r--gst/midi/midiparse.c1334
-rw-r--r--gst/midi/midiparse.h90
5 files changed, 1514 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 5f5c3b23f..5d1aa9fe3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -540,6 +540,7 @@ AG_GST_CHECK_PLUGIN(jp2kdecimator)
AG_GST_CHECK_PLUGIN(jpegformat)
AG_GST_CHECK_PLUGIN(librfb)
AG_GST_CHECK_PLUGIN(liveadder)
+AG_GST_CHECK_PLUGIN(midi)
AG_GST_CHECK_PLUGIN(mpegdemux)
AG_GST_CHECK_PLUGIN(mpegtsdemux)
AG_GST_CHECK_PLUGIN(mpegtsmux)
@@ -2234,6 +2235,7 @@ gst/jp2kdecimator/Makefile
gst/jpegformat/Makefile
gst/librfb/Makefile
gst/liveadder/Makefile
+gst/midi/Makefile
gst/mpegdemux/Makefile
gst/mpegtsdemux/Makefile
gst/mpegtsmux/Makefile
diff --git a/gst/midi/Makefile.am b/gst/midi/Makefile.am
new file mode 100644
index 000000000..058a778ee
--- /dev/null
+++ b/gst/midi/Makefile.am
@@ -0,0 +1,30 @@
+plugin_LTLIBRARIES = libgstmidi.la
+
+libgstmidi_la_SOURCES = midi.c midiparse.c
+libgstmidi_la_CFLAGS = \
+ $(GST_PLUGINS_BAD_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_BASE_CFLAGS) \
+ $(GST_CFLAGS)
+libgstmidi_la_LIBADD = \
+ $(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_API_VERSION) \
+ $(GST_BASE_LIBS) \
+ $(LIBM)
+libgstmidi_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstmidi_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = midiparse.h
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+ androgenizer \
+ -:PROJECT libgstmidi -:SHARED libgstmidi \
+ -:TAGS eng debug \
+ -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+ -:SOURCES $(libgstmidi_la_SOURCES) \
+ -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstmidi_la_CFLAGS) \
+ -:LDFLAGS $(libgstmidi_la_LDFLAGS) \
+ $(libgstmidi_la_LIBADD) \
+ -ldl \
+ -:PASSTHROUGH LOCAL_ARM_MODE:=arm \
+ LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-1.0' \
+ > $@
diff --git a/gst/midi/midi.c b/gst/midi/midi.c
new file mode 100644
index 000000000..6171359f6
--- /dev/null
+++ b/gst/midi/midi.c
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
+/* GStreamer MIDI plugin initialisation
+ * Copyright (C) <2013> Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/tag/tag.h>
+
+#include <gst/gst-i18n-plugin.h>
+
+#include "midiparse.h"
+
+GST_DEBUG_CATEGORY_STATIC (midi_debug);
+#define GST_CAT_DEFAULT (midi_debug)
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ gboolean ret;
+
+ GST_DEBUG_CATEGORY_INIT (midi_debug, "midi", 0, "MIDI plugin");
+
+#ifdef ENABLE_NLS
+ GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
+ LOCALEDIR);
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
+
+ ret = gst_element_register (plugin, "midiparse", GST_RANK_PRIMARY,
+ GST_TYPE_MIDI_PARSE);
+
+ return ret;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ midi,
+ "Parse MIDI files",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/gst/midi/midiparse.c b/gst/midi/midiparse.c
new file mode 100644
index 000000000..609d55fc2
--- /dev/null
+++ b/gst/midi/midiparse.c
@@ -0,0 +1,1334 @@
+/*
+ * midiparse - midi parser plugin for gstreamer
+ *
+ * Copyright 2013 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-midiparse
+ * @see_also: fluidsynth
+ *
+ * This element parses midi-files into midi events. You would need a midi
+ * renderer such as fluidsynth to convert the events into raw samples.
+ *
+ * <refsect2>
+ * <title>Example pipeline</title>
+ * |[
+ * gst-launch-1.0 filesrc location=song.mid ! midiparse ! fluidsynth ! pulsesink
+ * ]| This example pipeline will parse the midi and render to raw audio which is
+ * played via pulseaudio.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <gst/gst.h>
+#include <string.h>
+#include <glib.h>
+
+#include "midiparse.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_midi_parse_debug);
+#define GST_CAT_DEFAULT gst_midi_parse_debug
+
+enum
+{
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ /* FILL ME */
+};
+
+#define DEFAULT_TEMPO 500000 /* 120 BPM is the default */
+
+typedef struct
+{
+ guint8 *data;
+ guint size;
+ guint offset;
+
+ guint8 running_status;
+ guint64 pulse;
+ gboolean eot;
+
+} GstMidiTrack;
+
+typedef GstFlowReturn (*GstMidiPushFunc) (GstMidiParse * parse,
+ GstMidiTrack * track, guint8 event, guint8 * data, guint length,
+ gpointer user_data);
+
+static void gst_midi_parse_finalize (GObject * object);
+
+static gboolean gst_midi_parse_sink_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+static gboolean gst_midi_parse_src_event (GstPad * pad, GstObject * parent,
+ GstEvent * event);
+
+static GstStateChangeReturn gst_midi_parse_change_state (GstElement * element,
+ GstStateChange transition);
+static gboolean gst_midi_parse_activate (GstPad * pad, GstObject * parent);
+static gboolean gst_midi_parse_activatemode (GstPad * pad, GstObject * parent,
+ GstPadMode mode, gboolean active);
+
+static void gst_midi_parse_loop (GstPad * sinkpad);
+static GstFlowReturn gst_midi_parse_chain (GstPad * sinkpad, GstObject * parent,
+ GstBuffer * buffer);
+
+static gboolean gst_midi_parse_src_query (GstPad * pad, GstObject * parent,
+ GstQuery * query);
+
+static void gst_midi_parse_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_midi_parse_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static void reset_track (GstMidiTrack * track, GstMidiParse * midiparse);
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/midi; audio/riff-midi")
+ );
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("audio/x-midi-event"));
+
+#define parent_class gst_midi_parse_parent_class
+G_DEFINE_TYPE (GstMidiParse, gst_midi_parse, GST_TYPE_ELEMENT);
+
+/* initialize the plugin's class */
+static void
+gst_midi_parse_class_init (GstMidiParseClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->finalize = gst_midi_parse_finalize;
+ gobject_class->set_property = gst_midi_parse_set_property;
+ gobject_class->get_property = gst_midi_parse_get_property;
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&src_factory));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&sink_factory));
+ gst_element_class_set_static_metadata (gstelement_class, "MidiParse",
+ "Codec/Demuxer/Audio",
+ "Midi Parser Element", "Wim Taymans <wim.taymans@gmail.com>");
+
+ GST_DEBUG_CATEGORY_INIT (gst_midi_parse_debug, "midiparse",
+ 0, "MIDI parser plugin");
+
+ gstelement_class->change_state = gst_midi_parse_change_state;
+}
+
+/* initialize the new element
+ * instantiate pads and add them to element
+ * set functions
+ * initialize structure
+ */
+static void
+gst_midi_parse_init (GstMidiParse * filter)
+{
+ filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
+
+ gst_pad_set_activatemode_function (filter->sinkpad,
+ gst_midi_parse_activatemode);
+ gst_pad_set_activate_function (filter->sinkpad, gst_midi_parse_activate);
+ gst_pad_set_event_function (filter->sinkpad, gst_midi_parse_sink_event);
+ gst_pad_set_chain_function (filter->sinkpad, gst_midi_parse_chain);
+ gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
+
+ filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
+
+ gst_pad_set_query_function (filter->srcpad, gst_midi_parse_src_query);
+ gst_pad_set_event_function (filter->srcpad, gst_midi_parse_src_event);
+ gst_pad_use_fixed_caps (filter->srcpad);
+
+ gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
+
+ gst_segment_init (&filter->segment, GST_FORMAT_TIME);
+
+ filter->adapter = gst_adapter_new ();
+}
+
+static void
+gst_midi_parse_finalize (GObject * object)
+{
+ GstMidiParse *midiparse;
+
+ midiparse = GST_MIDI_PARSE (object);
+
+ g_object_unref (midiparse->adapter);
+ g_free (midiparse->data);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_midi_parse_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+ gboolean res = TRUE;
+ GstMidiParse *midiparse = GST_MIDI_PARSE (parent);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_DURATION:
+ gst_query_set_duration (query, GST_FORMAT_TIME,
+ midiparse->segment.duration);
+ break;
+ case GST_QUERY_POSITION:
+ gst_query_set_position (query, GST_FORMAT_TIME,
+ midiparse->segment.position);
+ break;
+ case GST_QUERY_FORMATS:
+ gst_query_set_formats (query, 1, GST_FORMAT_TIME);
+ break;
+ case GST_QUERY_SEGMENT:
+ gst_query_set_segment (query, midiparse->segment.rate,
+ midiparse->segment.format, midiparse->segment.start,
+ midiparse->segment.stop);
+ break;
+ case GST_QUERY_SEEKING:
+ gst_query_set_seeking (query, midiparse->segment.format,
+ FALSE, 0, midiparse->segment.duration);
+ break;
+ default:
+ res = gst_pad_query_default (pad, parent, query);
+ break;
+ }
+
+ return res;
+}
+
+static gboolean
+gst_midi_parse_do_seek (GstMidiParse * midiparse, GstSegment * segment)
+{
+ /* if seeking backwards, start from 0 else we just let things run and
+ * have it clip downstream */
+ GST_DEBUG_OBJECT (midiparse, "seeking back to 0");
+ segment->position = 0;
+ g_list_foreach (midiparse->tracks, (GFunc) reset_track, midiparse);
+ midiparse->pulse = 0;
+
+ return TRUE;
+}
+
+static gboolean
+gst_midi_parse_perform_seek (GstMidiParse * midiparse, GstEvent * event)
+{
+ gboolean res = TRUE, tres;
+ gdouble rate;
+ GstFormat seek_format;
+ GstSeekFlags flags;
+ GstSeekType start_type, stop_type;
+ gint64 start, stop;
+ gboolean flush;
+ gboolean update;
+ GstSegment seeksegment;
+ guint32 seqnum;
+ GstEvent *tevent;
+
+ GST_DEBUG_OBJECT (midiparse, "doing seek: %" GST_PTR_FORMAT, event);
+
+ if (event) {
+ gst_event_parse_seek (event, &rate, &seek_format, &flags,
+ &start_type, &start, &stop_type, &stop);
+
+ if (seek_format != GST_FORMAT_TIME)
+ goto invalid_format;
+
+ flush = flags & GST_SEEK_FLAG_FLUSH;
+ seqnum = gst_event_get_seqnum (event);
+ } else {
+ flush = FALSE;
+ /* get next seqnum */
+ seqnum = gst_util_seqnum_next ();
+ }
+
+ /* send flush start */
+ if (flush) {
+ tevent = gst_event_new_flush_start ();
+ gst_event_set_seqnum (tevent, seqnum);
+ gst_pad_push_event (midiparse->srcpad, tevent);
+ } else
+ gst_pad_pause_task (midiparse->srcpad);
+
+ /* grab streaming lock, this should eventually be possible, either
+ * because the task is paused, our streaming thread stopped
+ * or because our peer is flushing. */
+ GST_PAD_STREAM_LOCK (midiparse->sinkpad);
+ if (G_UNLIKELY (midiparse->seqnum == seqnum)) {
+ /* we have seen this event before, issue a warning for now */
+ GST_WARNING_OBJECT (midiparse, "duplicate event found %" G_GUINT32_FORMAT,
+ seqnum);
+ } else {
+ midiparse->seqnum = seqnum;
+ GST_DEBUG_OBJECT (midiparse, "seek with seqnum %" G_GUINT32_FORMAT, seqnum);
+ }
+
+ /* Copy the current segment info into the temp segment that we can actually
+ * attempt the seek with. We only update the real segment if the seek succeeds. */
+ memcpy (&seeksegment, &midiparse->segment, sizeof (GstSegment));
+
+ /* now configure the final seek segment */
+ if (event) {
+ gst_segment_do_seek (&seeksegment, rate, seek_format, flags,
+ start_type, start, stop_type, stop, &update);
+ }
+
+ /* Else, no seek event passed, so we're just (re)starting the
+ current segment. */
+ GST_DEBUG_OBJECT (midiparse, "segment configured from %" G_GINT64_FORMAT
+ " to %" G_GINT64_FORMAT ", position %" G_GINT64_FORMAT,
+ seeksegment.start, seeksegment.stop, seeksegment.position);
+
+ /* do the seek, segment.position contains the new position. */
+ res = gst_midi_parse_do_seek (midiparse, &seeksegment);
+
+ /* and prepare to continue streaming */
+ if (flush) {
+ tevent = gst_event_new_flush_stop (TRUE);
+ gst_event_set_seqnum (tevent, seqnum);
+ /* send flush stop, peer will accept data and events again. We
+ * are not yet providing data as we still have the STREAM_LOCK. */
+ gst_pad_push_event (midiparse->srcpad, tevent);
+ }
+
+ /* if the seek was successful, we update our real segment and push
+ * out the new segment. */
+ if (res) {
+ GST_OBJECT_LOCK (midiparse);
+ memcpy (&midiparse->segment, &seeksegment, sizeof (GstSegment));
+ GST_OBJECT_UNLOCK (midiparse);
+
+ if (seeksegment.flags & GST_SEGMENT_FLAG_SEGMENT) {
+ GstMessage *message;
+
+ message = gst_message_new_segment_start (GST_OBJECT (midiparse),
+ seeksegment.format, seeksegment.position);
+ gst_message_set_seqnum (message, seqnum);
+
+ gst_element_post_message (GST_ELEMENT (midiparse), message);
+ }
+ /* for deriving a stop position for the playback segment from the seek
+ * segment, we must take the duration when the stop is not set */
+ if ((stop = seeksegment.stop) == -1)
+ stop = seeksegment.duration;
+
+ midiparse->segment_pending = TRUE;
+ midiparse->discont = TRUE;
+ }
+
+ /* and restart the task in case it got paused explicitly or by
+ * the FLUSH_START event we pushed out. */
+ tres =
+ gst_pad_start_task (midiparse->sinkpad,
+ (GstTaskFunction) gst_midi_parse_loop, midiparse->sinkpad, NULL);
+ if (res && !tres)
+ res = FALSE;
+
+ /* and release the lock again so we can continue streaming */
+ GST_PAD_STREAM_UNLOCK (midiparse->sinkpad);
+
+ return res;
+
+ /* ERROR */
+invalid_format:
+ {
+ GST_DEBUG_OBJECT (midiparse, "Unsupported seek format %s",
+ gst_format_get_name (seek_format));
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_midi_parse_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ gboolean res = FALSE;
+ GstMidiParse *midiparse = GST_MIDI_PARSE (parent);
+
+ GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SEEK:
+ res = gst_midi_parse_perform_seek (midiparse, event);
+ break;
+ default:
+ break;
+ }
+ gst_event_unref (event);
+
+ return res;
+}
+
+static gboolean
+gst_midi_parse_activate (GstPad * sinkpad, GstObject * parent)
+{
+ GstQuery *query;
+ gboolean pull_mode;
+
+ query = gst_query_new_scheduling ();
+
+ if (!gst_pad_peer_query (sinkpad, query)) {
+ gst_query_unref (query);
+ goto activate_push;
+ }
+
+ pull_mode = gst_query_has_scheduling_mode_with_flags (query,
+ GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
+ gst_query_unref (query);
+
+ if (!pull_mode)
+ goto activate_push;
+
+ GST_DEBUG_OBJECT (sinkpad, "activating pull");
+ return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE);
+
+activate_push:
+ {
+ GST_DEBUG_OBJECT (sinkpad, "activating push");
+ return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE);
+ }
+}
+
+static gboolean
+gst_midi_parse_activatemode (GstPad * pad, GstObject * parent,
+ GstPadMode mode, gboolean active)
+{
+ gboolean res;
+
+ switch (mode) {
+ case GST_PAD_MODE_PUSH:
+ res = TRUE;
+ break;
+ case GST_PAD_MODE_PULL:
+ if (active) {
+ res = gst_pad_start_task (pad, (GstTaskFunction) gst_midi_parse_loop,
+ pad, NULL);
+ } else {
+ res = gst_pad_stop_task (pad);
+ }
+ break;
+ default:
+ res = FALSE;
+ break;
+ }
+ return res;
+}
+
+static gboolean
+parse_MThd (GstMidiParse * midiparse, guint8 * data, guint size)
+{
+ guint16 format, ntracks, division;
+ gboolean multitrack;
+
+ format = GST_READ_UINT16_BE (data);
+ switch (format) {
+ case 0:
+ multitrack = FALSE;
+ break;
+ case 1:
+ multitrack = TRUE;
+ break;
+ default:
+ case 2:
+ goto invalid_format;
+ }
+ ntracks = GST_READ_UINT16_BE (data + 2);
+ if (ntracks > 1 && !multitrack)
+ goto invalid_tracks;
+
+ division = GST_READ_UINT16_BE (data + 4);
+ if (division & 0x8000)
+ goto invalid_division;
+
+ GST_DEBUG_OBJECT (midiparse, "format %u, tracks %u, division %u",
+ format, ntracks, division);
+
+ midiparse->ntracks = ntracks;
+ midiparse->division = division;
+
+ return TRUE;
+
+invalid_format:
+ {
+ GST_ERROR_OBJECT (midiparse, "unsupported midi format %u", format);
+ return FALSE;
+ }
+invalid_tracks:
+ {
+ GST_ERROR_OBJECT (midiparse, "invalid number of tracks %u for format %u",
+ ntracks, format);
+ return FALSE;
+ }
+invalid_division:
+ {
+ GST_ERROR_OBJECT (midiparse, "unsupported division");
+ return FALSE;
+ }
+}
+
+static guint
+parse_varlen (GstMidiParse * midiparse, guint8 * data, guint size,
+ gint32 * result)
+{
+ gint32 res;
+ gint i;
+
+ res = 0;
+ for (i = 0; i < 4; i++) {
+ if (size == 0)
+ return 0;
+
+ res = (res << 7) | ((data[i]) & 0x7f);
+ if ((data[i] & 0x80) == 0) {
+ *result = res;
+ return i + 1;
+ }
+ }
+ return 0;
+}
+
+static GstFlowReturn
+handle_meta_event (GstMidiParse * midiparse, GstMidiTrack * track, guint8 event)
+{
+ guint8 type;
+ guint8 *data;
+ gchar *bytes;
+ guint size, consumed;
+ gint32 length;
+
+ track->offset += 1;
+
+ data = track->data + track->offset;
+ size = track->size - track->offset;
+
+ if (size < 1)
+ goto short_file;
+
+ type = data[0];
+
+ consumed = parse_varlen (midiparse, data + 1, size - 1, &length);
+ if (consumed == 0)
+ goto short_file;
+
+ data += consumed + 1;
+ size -= consumed + 1;
+
+ if (size < length)
+ goto short_file;
+
+ GST_DEBUG_OBJECT (midiparse, "handle meta event type 0x%02x, length %u",
+ type, length);
+
+ bytes = g_strndup ((const gchar *) data, length);
+
+ switch (type) {
+ case 0x01:
+ GST_DEBUG_OBJECT (midiparse, "Text: %s", bytes);
+ break;
+ case 0x02:
+ GST_DEBUG_OBJECT (midiparse, "Copyright: %s", bytes);
+ break;
+ case 0x03:
+ GST_DEBUG_OBJECT (midiparse, "Track Name: %s", bytes);
+ break;
+ case 0x04:
+ GST_DEBUG_OBJECT (midiparse, "Instrument: %s", bytes);
+ break;
+ case 0x05:
+ GST_DEBUG_OBJECT (midiparse, "Lyric: %s", bytes);
+ break;
+ case 0x06:
+ GST_DEBUG_OBJECT (midiparse, "Marker: %s", bytes);
+ break;
+ case 0x07:
+ GST_DEBUG_OBJECT (midiparse, "Cue point: %s", bytes);
+ break;
+ case 0x08:
+ GST_DEBUG_OBJECT (midiparse, "Patch name: %s", bytes);
+ break;
+ case 0x09:
+ GST_DEBUG_OBJECT (midiparse, "MIDI port: %s", bytes);
+ break;
+ case 0x2f:
+ GST_DEBUG_OBJECT (midiparse, "End of track");
+ break;
+ case 0x51:
+ {
+ guint32 uspqn = (data[0] << 16) | (data[1] << 8) | data[2];
+ midiparse->tempo = (uspqn ? uspqn : DEFAULT_TEMPO);
+ GST_DEBUG_OBJECT (midiparse, "tempo %u", midiparse->tempo);
+ break;
+ }
+ case 0x54:
+ GST_DEBUG_OBJECT (midiparse, "SMPTE offset");
+ break;
+ case 0x58:
+ GST_DEBUG_OBJECT (midiparse, "Time signature");
+ break;
+ case 0x59:
+ GST_DEBUG_OBJECT (midiparse, "Key signature");
+ break;
+ case 0x7f:
+ GST_DEBUG_OBJECT (midiparse, "Proprietary event");
+ break;
+ default:
+ GST_DEBUG_OBJECT (midiparse, "unknown event 0x%02x length %d", type,
+ length);
+ break;
+ }
+ g_free (bytes);
+
+ track->offset += consumed + length + 1;
+
+ return GST_FLOW_OK;
+
+ /* ERRORS */
+short_file:
+ {
+ GST_DEBUG_OBJECT (midiparse, "not enough data");
+ return GST_FLOW_ERROR;
+ }
+}
+
+static GstFlowReturn
+handle_sysex_event (GstMidiParse * midiparse, GstMidiTrack * track,
+ guint8 event, GstMidiPushFunc pushfunc, gpointer user_data)
+{
+ GstFlowReturn ret;
+ guint8 *data;
+ guint size, consumed;
+ gint32 length;
+
+ track->offset += 1;
+
+ data = track->data + track->offset;
+ size = track->size - track->offset;
+
+ consumed = parse_varlen (midiparse, data, size, &length);
+ if (consumed == 0)
+ goto short_file;
+
+ data += consumed;
+ size -= consumed;
+
+ if (size < length)
+ goto short_file;
+
+ GST_DEBUG_OBJECT (midiparse, "handle sysex event 0x%02x, length %u",
+ event, length);
+
+ if (pushfunc)
+ ret = pushfunc (midiparse, track, event, data, length, user_data);
+ else
+ ret = GST_FLOW_OK;
+
+ track->offset += consumed + length;
+
+ return ret;
+
+ /* ERRORS */
+short_file:
+ {
+ GST_DEBUG_OBJECT (midiparse, "not enough data");
+ return GST_FLOW_ERROR;
+ }
+}
+
+
+static guint8
+event_from_status (GstMidiParse * midiparse, GstMidiTrack * track,
+ guint8 status)
+{
+ if ((status & 0x80) == 0) {
+ if ((track->running_status & 0x80) == 0)
+ return 0;
+
+ return track->running_status;
+ } else {
+ return status;
+ }
+}
+
+static gboolean
+update_track_position (GstMidiParse * midiparse, GstMidiTrack * track)
+{
+ gint32 delta_time;
+ guint8 *data;
+ guint size, consumed;
+
+ if (track->offset >= track->size)
+ goto eot;
+
+ data = track->data + track->offset;
+ size = track->size - track->offset;
+
+ consumed = parse_varlen (midiparse, data, size, &delta_time);
+ if (consumed == 0)
+ goto eot;
+
+ track->pulse += delta_time;
+ track->offset += consumed;
+
+ GST_LOG_OBJECT (midiparse, "updated track to pulse %" G_GUINT64_FORMAT,
+ track->pulse);
+
+ return TRUE;
+
+ /* ERRORS */
+eot:
+ {
+ GST_DEBUG_OBJECT (midiparse, "track ended");
+ track->eot = TRUE;
+ return FALSE;
+ }
+}
+
+static GstFlowReturn
+handle_next_event (GstMidiParse * midiparse, GstMidiTrack * track,
+ GstMidiPushFunc pushfunc, gpointer user_data)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ guint8 status, event;
+ guint length;
+ guint8 *data;
+
+ data = &track->data[track->offset];
+
+ status = data[0];
+ event = event_from_status (midiparse, track, status);
+
+ GST_LOG_OBJECT (midiparse, "track %p, status 0x%02x, event 0x%02x", track,
+ status, event);
+
+ switch (event & 0xf0) {
+ case 0xf0:
+ switch (event) {
+ case 0xff:
+ ret = handle_meta_event (midiparse, track, event);
+ break;
+ case 0xf0:
+ case 0xf7:
+ ret =
+ handle_sysex_event (midiparse, track, event, pushfunc, user_data);
+ break;
+ default:
+ goto unhandled_event;
+ }
+ length = 0;
+ break;
+ case 0xc0:
+ case 0xd0:
+ length = 1;
+ break;
+ case 0x80:
+ case 0x90:
+ case 0xa0:
+ case 0xb0:
+ case 0xe0:
+ length = 2;
+ break;
+ default:
+ goto undefined_status;
+ }
+ if (length > 0) {
+ if (status & 0x80) {
+ if (pushfunc)
+ ret = pushfunc (midiparse, track, event, data + 1, length, user_data);
+ track->offset += length + 1;
+ } else {
+ if (pushfunc)
+ ret = pushfunc (midiparse, track, event, data, length + 1, user_data);
+ track->offset += length;
+ }
+ }
+
+ if (ret == GST_FLOW_OK) {
+ if (event < 0xF8)
+ track->running_status = event;
+
+ update_track_position (midiparse, track);
+ }
+ return ret;
+
+ /* ERRORS */
+undefined_status:
+ {
+ GST_ERROR_OBJECT (midiparse, "Undefined status and invalid running status");
+ return GST_FLOW_ERROR;
+ }
+unhandled_event:
+ {
+ /* we don't know the size so we can't continue parsing */
+ GST_ERROR_OBJECT (midiparse, "unhandled event 0x%08x", event);
+ return GST_FLOW_ERROR;
+ }
+}
+
+static void
+reset_track (GstMidiTrack * track, GstMidiParse * midiparse)
+{
+ GST_DEBUG_OBJECT (midiparse, "reset track");
+ track->offset = 0;
+ track->pulse = 0;
+ track->eot = FALSE;
+ track->running_status = 0xff;
+ update_track_position (midiparse, track);
+}
+
+static gboolean
+parse_MTrk (GstMidiParse * midiparse, guint8 * data, guint size)
+{
+ GstMidiTrack *track;
+ GstClockTime duration;
+
+ /* ignore excess tracks */
+ if (midiparse->track_count >= midiparse->ntracks)
+ return TRUE;
+
+ track = g_slice_new (GstMidiTrack);
+ track->data = data;
+ track->size = size;
+ reset_track (track, midiparse);
+
+ midiparse->tracks = g_list_append (midiparse->tracks, track);
+ midiparse->track_count++;
+
+ /* now loop over all events and calculate the duration */
+ while (!track->eot) {
+ handle_next_event (midiparse, track, NULL, NULL);
+ }
+
+ duration = gst_util_uint64_scale (track->pulse,
+ 1000 * midiparse->tempo, midiparse->division);
+
+ GST_DEBUG_OBJECT (midiparse, "duration %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (duration));
+
+ if (duration > midiparse->segment.duration)
+ midiparse->segment.duration = duration;
+
+ reset_track (track, midiparse);
+
+ return TRUE;
+}
+
+static gboolean
+find_midi_chunk (GstMidiParse * midiparse, guint8 * data, guint size,
+ guint * offset, guint * length)
+{
+ guint32 type;
+
+ *length = 0;
+
+ if (size < 8)
+ goto short_chunk;
+
+ type = GST_STR_FOURCC (data);
+
+ if (type == GST_MAKE_FOURCC ('R', 'I', 'F', 'F')) {
+ guint32 riff_len;
+
+ GST_DEBUG_OBJECT (midiparse, "found RIFF");
+
+ if (size < 12)
+ goto short_chunk;
+
+ if (GST_STR_FOURCC (data + 8) != GST_MAKE_FOURCC ('R', 'M', 'I', 'D'))
+ goto invalid_format;
+
+ riff_len = GST_READ_UINT32_LE (data + 4);
+
+ if (size < riff_len)
+ goto short_chunk;
+
+ data += 12;
+ size -= 12;
+ *offset = 12;
+
+ GST_DEBUG_OBJECT (midiparse, "found RIFF RMID of size %u", riff_len);
+
+ while (TRUE) {
+ guint32 chunk_type;
+ guint32 chunk_len;
+
+ if (riff_len < 8)
+ goto short_chunk;
+
+ chunk_type = GST_STR_FOURCC (data);
+ chunk_len = GST_READ_UINT32_LE (data + 4);
+
+ riff_len -= 8;
+ if (riff_len < chunk_len)
+ goto short_chunk;
+
+ data += 8;
+ size -= 8;
+ *offset += 8;
+ riff_len -= chunk_len;
+
+ if (chunk_type == GST_MAKE_FOURCC ('d', 'a', 't', 'a')) {
+ *length = chunk_len;
+ break;
+ }
+
+ data += chunk_len;
+ size -= chunk_len;
+ }
+ } else {
+ *offset = 0;
+ *length = size;
+ }
+ return TRUE;
+
+ /* ERRORS */
+short_chunk:
+ {
+ GST_LOG_OBJECT (midiparse, "not enough data %u < %u", *length + 8, size);
+ return FALSE;
+ }
+invalid_format:
+ {
+ GST_ERROR_OBJECT (midiparse, "invalid format");
+ return FALSE;
+ }
+}
+
+static guint
+gst_midi_parse_chunk (GstMidiParse * midiparse, guint8 * data, guint size)
+{
+ guint32 type, length = 0;
+
+ if (size < 8)
+ goto short_chunk;
+
+ length = GST_READ_UINT32_BE (data + 4);
+
+ GST_DEBUG_OBJECT (midiparse, "have type %c%c%c%c, length %u",
+ data[0], data[1], data[2], data[3], length);
+
+ if (size < length + 8)
+ goto short_chunk;
+
+ type = GST_STR_FOURCC (data);
+
+ switch (type) {
+ case GST_MAKE_FOURCC ('M', 'T', 'h', 'd'):
+ if (!parse_MThd (midiparse, data + 8, length))
+ goto invalid_format;
+ break;
+ case GST_MAKE_FOURCC ('M', 'T', 'r', 'k'):
+ if (!parse_MTrk (midiparse, data + 8, length))
+ goto invalid_format;
+ break;
+ default:
+ GST_LOG_OBJECT (midiparse, "ignore chunk");
+ break;
+ }
+
+ return length + 8;
+
+ /* ERRORS */
+short_chunk:
+ {
+ GST_LOG_OBJECT (midiparse, "not enough data %u < %u", size, length + 8);
+ return 0;
+ }
+invalid_format:
+ {
+ GST_ERROR_OBJECT (midiparse, "invalid format");
+ return 0;
+ }
+}
+
+static GstFlowReturn
+gst_midi_parse_parse_song (GstMidiParse * midiparse)
+{
+ GstCaps *outcaps;
+ guint8 *data;
+ guint size, offset, length;
+ gchar *stream_id;
+
+ GST_DEBUG_OBJECT (midiparse, "Parsing song");
+
+ gst_segment_init (&midiparse->segment, GST_FORMAT_TIME);
+ midiparse->segment.duration = 0;
+ midiparse->pulse = 0;
+
+ size = gst_adapter_available (midiparse->adapter);
+ data = gst_adapter_take (midiparse->adapter, size);
+
+ midiparse->data = data;
+ midiparse->tempo = DEFAULT_TEMPO;
+
+ if (!find_midi_chunk (midiparse, data, size, &offset, &length))
+ goto invalid_format;
+
+ while (length) {
+ guint consumed;
+
+ consumed = gst_midi_parse_chunk (midiparse, &data[offset], length);
+ if (consumed == 0)
+ goto short_file;
+
+ offset += consumed;
+ length -= consumed;
+ }
+
+ GST_DEBUG_OBJECT (midiparse, "song duration %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (midiparse->segment.duration));
+
+ stream_id = gst_pad_create_stream_id (midiparse->srcpad, GST_ELEMENT_CAST (midiparse), NULL);
+ gst_pad_push_event (midiparse->srcpad, gst_event_new_stream_start (stream_id));
+ g_free (stream_id);
+
+ outcaps = gst_pad_get_pad_template_caps (midiparse->srcpad);
+ gst_pad_set_caps (midiparse->srcpad, outcaps);
+ gst_caps_unref (outcaps);
+
+ midiparse->segment_pending = TRUE;
+ midiparse->discont = TRUE;
+
+ GST_DEBUG_OBJECT (midiparse, "Parsing song done");
+
+ return GST_FLOW_OK;
+
+ /* ERRORS */
+short_file:
+ {
+ GST_ERROR_OBJECT (midiparse, "not enough data");
+ return GST_FLOW_ERROR;
+ }
+invalid_format:
+ {
+ GST_ERROR_OBJECT (midiparse, "invalid format");
+ return GST_FLOW_ERROR;
+ }
+}
+
+static GstFlowReturn
+play_push_func (GstMidiParse * midiparse, GstMidiTrack * track,
+ guint8 event, guint8 * data, guint length, gpointer user_data)
+{
+ GstBuffer *outbuf;
+ GstMapInfo info;
+ GstClockTime position;
+
+ outbuf = gst_buffer_new_allocate (NULL, length + 1, NULL);
+
+ gst_buffer_map (outbuf, &info, GST_MAP_WRITE);
+ info.data[0] = event;
+ if (length)
+ memcpy (&info.data[1], data, length);
+ gst_buffer_unmap (outbuf, &info);
+
+ position = midiparse->segment.position;
+ GST_BUFFER_PTS (outbuf) = position;
+ GST_BUFFER_DTS (outbuf) = position;
+
+ GST_DEBUG_OBJECT (midiparse, "pushing %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (position));
+
+ if (midiparse->discont) {
+ GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
+ midiparse->discont = FALSE;
+ }
+
+ return gst_pad_push (midiparse->srcpad, outbuf);
+}
+
+static GstFlowReturn
+gst_midi_parse_do_play (GstMidiParse * midiparse)
+{
+ GstFlowReturn res;
+ GList *walk;
+ guint64 pulse, next_pulse = G_MAXUINT64;
+ GstClockTime position, next_position;
+ guint64 tick;
+
+ pulse = midiparse->pulse;
+ position = midiparse->segment.position;
+
+ if (midiparse->segment_pending) {
+ gst_pad_push_event (midiparse->srcpad,
+ gst_event_new_segment (&midiparse->segment));
+ midiparse->segment_pending = FALSE;
+ }
+
+ GST_DEBUG_OBJECT (midiparse, "pulse %" G_GUINT64_FORMAT ", position %"
+ GST_TIME_FORMAT, pulse, GST_TIME_ARGS (position));
+
+ for (walk = midiparse->tracks; walk; walk = g_list_next (walk)) {
+ GstMidiTrack *track = walk->data;
+
+ while (!track->eot && track->pulse == pulse) {
+ res = handle_next_event (midiparse, track, play_push_func, NULL);
+ if (res != GST_FLOW_OK)
+ goto error;
+ }
+
+ if (!track->eot && track->pulse < next_pulse)
+ next_pulse = track->pulse;
+ }
+
+ if (next_pulse == G_MAXUINT64)
+ goto eos;
+
+ tick = position / (10 * GST_MSECOND);
+ GST_DEBUG_OBJECT (midiparse, "current tick %" G_GUINT64_FORMAT, tick);
+
+ next_position = gst_util_uint64_scale (next_pulse,
+ 1000 * midiparse->tempo, midiparse->division);
+ GST_DEBUG_OBJECT (midiparse, "next position %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (next_position));
+
+ /* send 10ms ticks to advance the downstream element */
+ while (TRUE) {
+ /* get position of next tick */
+ position = ++tick * (10 * GST_MSECOND);
+ GST_DEBUG_OBJECT (midiparse, "tick %" G_GUINT64_FORMAT
+ ", position %" GST_TIME_FORMAT, tick, GST_TIME_ARGS (position));
+
+ if (position >= next_position)
+ break;
+
+ midiparse->segment.position = position;
+ res = play_push_func (midiparse, NULL, 0xf9, NULL, 0, NULL);
+ if (res != GST_FLOW_OK)
+ goto error;
+ }
+
+ midiparse->pulse = next_pulse;
+ midiparse->segment.position = next_position;
+
+ return GST_FLOW_OK;
+
+ /* ERRORS */
+eos:
+ {
+ GST_DEBUG_OBJECT (midiparse, "we are EOS");
+ return GST_FLOW_EOS;
+ }
+error:
+ {
+ GST_DEBUG_OBJECT (midiparse, "have flow result %s",
+ gst_flow_get_name (res));
+ return res;
+ }
+}
+
+static gboolean
+gst_midi_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+ gboolean res;
+ GstMidiParse *midiparse = GST_MIDI_PARSE (parent);
+
+ GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_EOS:
+ midiparse->state = GST_MIDI_PARSE_STATE_PARSE;
+ /* now start the parsing task */
+ res = gst_pad_start_task (midiparse->sinkpad,
+ (GstTaskFunction) gst_midi_parse_loop, midiparse->sinkpad, NULL);
+ /* don't forward the event */
+ gst_event_unref (event);
+ break;
+ case GST_EVENT_CAPS:
+ case GST_EVENT_STREAM_START:
+ case GST_EVENT_SEGMENT:
+ res = TRUE;
+ gst_event_unref (event);
+ break;
+ default:
+ res = gst_pad_event_default (pad, parent, event);
+ break;
+ }
+ return res;
+}
+
+static GstFlowReturn
+gst_midi_parse_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer)
+{
+ GstMidiParse *midiparse;
+
+ midiparse = GST_MIDI_PARSE (parent);
+
+ /* push stuff in the adapter, we will start doing something in the sink event
+ * handler when we get EOS */
+ gst_adapter_push (midiparse->adapter, buffer);
+
+ return GST_FLOW_OK;
+}
+
+static void
+gst_midi_parse_loop (GstPad * sinkpad)
+{
+ GstMidiParse *midiparse = GST_MIDI_PARSE (GST_PAD_PARENT (sinkpad));
+ GstFlowReturn ret;
+
+ switch (midiparse->state) {
+ case GST_MIDI_PARSE_STATE_LOAD:
+ {
+ GstBuffer *buffer = NULL;
+
+ GST_DEBUG_OBJECT (midiparse, "loading song");
+
+ ret =
+ gst_pad_pull_range (midiparse->sinkpad, midiparse->offset, -1,
+ &buffer);
+
+ if (ret == GST_FLOW_EOS) {
+ GST_DEBUG_OBJECT (midiparse, "Song loaded");
+ midiparse->state = GST_MIDI_PARSE_STATE_PARSE;
+ } else if (ret != GST_FLOW_OK) {
+ GST_ELEMENT_ERROR (midiparse, STREAM, DECODE, (NULL),
+ ("Unable to read song"));
+ goto pause;
+ } else {
+ GST_DEBUG_OBJECT (midiparse, "pushing buffer");
+ gst_adapter_push (midiparse->adapter, buffer);
+ midiparse->offset += gst_buffer_get_size (buffer);
+ }
+ break;
+ }
+ case GST_MIDI_PARSE_STATE_PARSE:
+ ret = gst_midi_parse_parse_song (midiparse);
+ if (ret != GST_FLOW_OK)
+ goto pause;
+ midiparse->state = GST_MIDI_PARSE_STATE_PLAY;
+ break;
+ case GST_MIDI_PARSE_STATE_PLAY:
+ ret = gst_midi_parse_do_play (midiparse);
+ if (ret != GST_FLOW_OK)
+ goto pause;
+ break;
+ default:
+ break;
+ }
+ return;
+
+pause:
+ {
+ const gchar *reason = gst_flow_get_name (ret);
+ GstEvent *event;
+
+ GST_DEBUG_OBJECT (midiparse, "pausing task, reason %s", reason);
+ gst_pad_pause_task (sinkpad);
+ if (ret == GST_FLOW_EOS) {
+ /* perform EOS logic */
+ event = gst_event_new_eos ();
+ gst_pad_push_event (midiparse->srcpad, event);
+ } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
+ event = gst_event_new_eos ();
+ /* for fatal errors we post an error message, post the error
+ * first so the app knows about the error first. */
+ GST_ELEMENT_ERROR (midiparse, STREAM, FAILED,
+ ("Internal data flow error."),
+ ("streaming task paused, reason %s (%d)", reason, ret));
+ gst_pad_push_event (midiparse->srcpad, event);
+ }
+ }
+}
+
+static void
+free_track (GstMidiTrack * track, GstMidiParse * midiparse)
+{
+ g_slice_free (GstMidiTrack, track);
+}
+
+static void
+gst_midi_parse_reset (GstMidiParse * midiparse)
+{
+ gst_adapter_clear (midiparse->adapter);
+ g_free (midiparse->data);
+ midiparse->data = NULL;
+ g_list_foreach (midiparse->tracks, (GFunc) free_track, midiparse);
+ g_list_free (midiparse->tracks);
+ midiparse->tracks = NULL;
+ midiparse->track_count = 0;
+}
+
+static GstStateChangeReturn
+gst_midi_parse_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+ GstMidiParse *midiparse = GST_MIDI_PARSE (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ midiparse->offset = 0;
+ midiparse->state = GST_MIDI_PARSE_STATE_LOAD;
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ gst_midi_parse_reset (midiparse);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_midi_parse_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_midi_parse_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (prop_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
diff --git a/gst/midi/midiparse.h b/gst/midi/midiparse.h
new file mode 100644
index 000000000..ba3a25ea1
--- /dev/null
+++ b/gst/midi/midiparse.h
@@ -0,0 +1,90 @@
+/*
+ * gstmidiparse - midiparse plugin for gstreamer
+ *
+ * Copyright 2007 Wouter Paesen <wouter@blue-gate.be>
+ * Copyright 2013 Wim Taymans <wim.taymans@gmail.be>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_MIDIPARSE_H__
+#define __GST_MIDIPARSE_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+#include <midiparse.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MIDI_PARSE \
+ (gst_midi_parse_get_type())
+#define GST_MIDI_PARSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MIDI_PARSE,GstMidiParse))
+#define GST_MIDI_PARSE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MIDI_PARSE,GstMidiParseClass))
+#define GST_IS_MIDI_PARSE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MIDI_PARSE))
+#define GST_IS_MIDI_PARSE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MIDI_PARSE))
+
+typedef struct _GstMidiParse GstMidiParse;
+typedef struct _GstMidiParseClass GstMidiParseClass;
+
+typedef enum {
+ GST_MIDI_PARSE_STATE_LOAD,
+ GST_MIDI_PARSE_STATE_PARSE,
+ GST_MIDI_PARSE_STATE_PLAY
+} GstMidiParseState;
+
+struct _GstMidiParse
+{
+ GstElement element;
+
+ GstPad *sinkpad, *srcpad;
+
+ /* input stream properties */
+ GstMidiParseState state;
+
+ guint tempo;
+ guint16 ntracks;
+ guint16 division;
+
+ GList *tracks;
+ guint track_count;
+
+ guint64 offset;
+ GstAdapter *adapter;
+ guint8 *data;
+
+ /* output data */
+ gboolean discont;
+ GstSegment segment;
+ gboolean segment_pending;
+ guint32 seqnum;
+
+ guint64 pulse;
+};
+
+struct _GstMidiParseClass
+{
+ GstElementClass parent_class;
+};
+
+GType gst_midi_parse_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_MIDI_PARSE_H__ */