midi: add an ALSA MIDI sequencer source
authorAntonio Ospite <ao2@ao2.it>
Fri, 7 Aug 2015 13:28:54 +0000 (15:28 +0200)
committerStefan Sauer <ensonic@users.sf.net>
Mon, 28 Sep 2015 11:21:24 +0000 (13:21 +0200)
The alsamidisrc element allows to get input event from ALSA MIDI
sequencer devices, and possibly convert them to sound using some
downstream element like fluiddec.

https://bugzilla.gnome.org/show_bug.cgi?id=738687

configure.ac
gst/midi/Makefile.am
gst/midi/alsamidisrc.c [new file with mode: 0644]
gst/midi/alsamidisrc.h [new file with mode: 0644]
gst/midi/midi.c

index c8d3f81..af3d38d 100644 (file)
@@ -1403,6 +1403,18 @@ AM_CONDITIONAL(HAVE_JPEG, test "x$HAVE_JPEG" = "xyes")
 
 dnl *** sys plug-ins ***
 
+dnl *** alsa ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_ALSA, true)
+AG_GST_CHECK_FEATURE(ALSA, [ALSA], alsa, [
+  PKG_CHECK_MODULES(ALSA, alsa >= 0.9.1, [
+    HAVE_ALSA="yes"
+    AC_SUBST(ALSA_CFLAGS)
+    AC_SUBST(ALSA_LIBS)
+  ], [
+    AM_PATH_ALSA(0.9.1, HAVE_ALSA="yes", HAVE_ALSA="no")
+  ])
+])
+
 dnl DirectSound
 translit(dnm, m, l) AM_CONDITIONAL(USE_DIRECTSOUND, true)
 AG_GST_CHECK_FEATURE(DIRECTSOUND, [DirectSound], directsoundsrc, [
index 55f12df..44ae2bf 100644 (file)
@@ -14,3 +14,10 @@ libgstmidi_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 libgstmidi_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
 
 noinst_HEADERS = midiparse.h
+
+if USE_ALSA
+libgstmidi_la_SOURCES += alsamidisrc.c
+libgstmidi_la_CFLAGS += $(ALSA_FLAGS)
+libgstmidi_la_LIBADD += $(ALSA_LIBS)
+noinst_HEADERS += alsamidisrc.h
+endif
diff --git a/gst/midi/alsamidisrc.c b/gst/midi/alsamidisrc.c
new file mode 100644 (file)
index 0000000..58cd27e
--- /dev/null
@@ -0,0 +1,525 @@
+/* GStreamer
+ * Copyright (C) 2014  Antonio Ospite <ao2@ao2.it>
+ *
+ * alsamidisrc.c: Source element for ALSA MIDI sequencer events
+ *
+ * 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-alsamidisrc
+ * @see_also: #GstPushSrc
+ *
+ * The alsamidisrc element is an element that fetches ALSA MIDI sequencer
+ * events and makes them available to elements understanding
+ * audio/x-midi-events in their sink pads.
+ *
+ * It can be used to generate notes from a MIDI input device.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch-1.0 -v alsamidisrc ports=129:0 ! fluiddec ! audioconvert ! autoaudiosink
+ * ]| This pipeline will listen for events from the sequencer device at port 129:0,
+ * and generate notes using the fluiddec element.
+ *
+ * |[
+ * vkeybd & gst-launch-1.0 -v alsamidisrc ports=128:0 ! fluiddec ! audioconvert ! autoaudiosink
+ * ]| Use a software midi keyboard to play fluiddec.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "alsamidisrc.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_alsa_midi_src_debug);
+#define GST_CAT_DEFAULT gst_alsa_midi_src_debug
+
+/*
+ * The MIDI specification declares some status bytes undefined:
+ *
+ *  - 0xF4 System common - Undefined (Reserved)
+ *  - 0xF5 System common - Undefined (Reserved)
+ *  - 0xF9 System real-time - Undefined (Reserved)
+ *  - 0xFD System real-time - Undefined (Reserved)
+ *
+ * See: http://www.midi.org/techspecs/midimessages.php#2
+ *
+ * Some other documents define status 0xf9 as a tick message with a period of
+ * 10ms:
+ *
+ *  - http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/tick.htm
+ *  - http://www.sequencer.de/synth/index.php/MIDI_Format#0xf9_-_MIDI_Tick
+ *
+ * Even if non-standard it looks like this convention is quite widespread.
+ *
+ * For instance Fluidsynth uses 0xF9 as a "midi tick" message:
+ * http://sourceforge.net/p/fluidsynth/code-git/ci/master/tree/fluidsynth/src/midi/fluid_midi.h#l62
+ *
+ * And then so does the midiparse element in order to be compatible with
+ * Fluidsynth and the fluiddec element.
+ *
+ * Do the same to behave like midiparse.
+ */
+#define MIDI_TICK 0xf9
+#define MIDI_TICK_PERIOD_MS 10
+
+/* Functions specific to the Alsa MIDI sequencer API */
+
+#define DEFAULT_BUFSIZE 65536
+#define DEFAULT_CLIENT_NAME "alsamidisrc"
+#define DEFAULT_POLL_TIMEOUT_MS (MIDI_TICK_PERIOD_MS / 2)
+
+static int
+init_seq (GstAlsaMidiSrc * alsamidisrc)
+{
+  int ret;
+
+  ret = snd_seq_open (&alsamidisrc->seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+  if (ret < 0) {
+    GST_ERROR_OBJECT (alsamidisrc, "Cannot open sequencer - %s",
+        snd_strerror (ret));
+    goto error;
+  }
+
+  ret = snd_seq_set_client_name (alsamidisrc->seq, DEFAULT_CLIENT_NAME);
+  if (ret < 0) {
+    GST_ERROR_OBJECT (alsamidisrc, "Cannot set client name - %s",
+        snd_strerror (ret));
+    goto error_seq_close;
+  }
+
+  return 0;
+
+error_seq_close:
+  snd_seq_close (alsamidisrc->seq);
+error:
+  return ret;
+}
+
+/* Parses one or more port addresses from the string */
+static int
+parse_ports (const char *arg, GstAlsaMidiSrc * alsamidisrc)
+{
+  gchar **ports_list;
+  guint i;
+  int ret = 0;
+
+  GST_DEBUG_OBJECT (alsamidisrc, "ports: %s", arg);
+
+  /*
+   * Assume that ports are separated by commas.
+   *
+   * Commas are used instead of spaces because those are valid in client
+   * names.
+   */
+  ports_list = g_strsplit (arg, ",", 0);
+
+  alsamidisrc->port_count = g_strv_length (ports_list);
+  alsamidisrc->seq_ports = g_try_new (snd_seq_addr_t, alsamidisrc->port_count);
+  if (!alsamidisrc->seq_ports) {
+    GST_ERROR_OBJECT (alsamidisrc, "Out of memory");
+    ret = -ENOMEM;
+    goto out_free_ports_list;
+  }
+
+  for (i = 0; i < alsamidisrc->port_count; i++) {
+    gchar *port_name = ports_list[i];
+
+    ret = snd_seq_parse_address (alsamidisrc->seq, &alsamidisrc->seq_ports[i],
+        port_name);
+    if (ret < 0) {
+      GST_ERROR_OBJECT (alsamidisrc, "Invalid port %s - %s", port_name,
+          snd_strerror (ret));
+      goto error_free_seq_ports;
+    }
+  }
+
+  goto out_free_ports_list;
+
+error_free_seq_ports:
+  g_free (alsamidisrc->seq_ports);
+out_free_ports_list:
+  g_strfreev (ports_list);
+  return ret;
+}
+
+static int
+create_port (GstAlsaMidiSrc * alsamidisrc)
+{
+  int ret;
+
+  ret = snd_seq_create_simple_port (alsamidisrc->seq, DEFAULT_CLIENT_NAME,
+      SND_SEQ_PORT_CAP_WRITE |
+      SND_SEQ_PORT_CAP_SUBS_WRITE,
+      SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
+  if (ret < 0)
+    GST_ERROR_OBJECT (alsamidisrc, "Cannot create port - %s",
+        snd_strerror (ret));
+
+  return ret;
+}
+
+static void
+connect_ports (GstAlsaMidiSrc * alsamidisrc)
+{
+  int i;
+  int ret;
+
+  for (i = 0; i < alsamidisrc->port_count; ++i) {
+    ret =
+        snd_seq_connect_from (alsamidisrc->seq, 0,
+        alsamidisrc->seq_ports[i].client, alsamidisrc->seq_ports[i].port);
+    if (ret < 0)
+      /* Issue a warning and try the other ports */
+      GST_WARNING_OBJECT (alsamidisrc, "Cannot connect from port %d:%d - %s",
+          alsamidisrc->seq_ports[i].client, alsamidisrc->seq_ports[i].port,
+          snd_strerror (ret));
+  }
+}
+
+/* GStreamer specific functions */
+
+static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/x-midi-event"));
+
+#define DEFAULT_PORTS           NULL
+
+enum
+{
+  PROP_0,
+  PROP_PORTS,
+  PROP_LAST,
+};
+
+#define _do_init \
+    GST_DEBUG_CATEGORY_INIT (gst_alsa_midi_src_debug, "alsamidisrc", 0, "alsamidisrc element");
+#define gst_alsa_midi_src_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstAlsaMidiSrc, gst_alsa_midi_src, GST_TYPE_PUSH_SRC,
+    _do_init);
+
+static void gst_alsa_midi_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_alsa_midi_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static gboolean gst_alsa_midi_src_start (GstBaseSrc * basesrc);
+static gboolean gst_alsa_midi_src_stop (GstBaseSrc * basesrc);
+
+static GstFlowReturn
+gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf);
+
+static void
+gst_alsa_midi_src_class_init (GstAlsaMidiSrcClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+  GstBaseSrcClass *gstbase_src_class;
+  GstPushSrcClass *gstpush_src_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gstelement_class = GST_ELEMENT_CLASS (klass);
+  gstbase_src_class = GST_BASE_SRC_CLASS (klass);
+  gstpush_src_class = GST_PUSH_SRC_CLASS (klass);
+
+  gobject_class->set_property = gst_alsa_midi_src_set_property;
+  gobject_class->get_property = gst_alsa_midi_src_get_property;
+
+  g_object_class_install_property (gobject_class, PROP_PORTS,
+      g_param_spec_string ("ports", "Ports",
+          "Comma separated list of sequencer ports (e.g. client:port,...)",
+          DEFAULT_PORTS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gst_element_class_set_static_metadata (gstelement_class,
+      "AlsaMidi Source",
+      "Source",
+      "Push ALSA MIDI sequencer events around", "Antonio Ospite <ao2@ao2.it>");
+  gst_element_class_add_pad_template (gstelement_class,
+      gst_static_pad_template_get (&srctemplate));
+
+  gstbase_src_class->start = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_start);
+  gstbase_src_class->stop = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_stop);
+  gstpush_src_class->create = GST_DEBUG_FUNCPTR (gst_alsa_midi_src_create);
+}
+
+static void
+gst_alsa_midi_src_init (GstAlsaMidiSrc * alsamidisrc)
+{
+  alsamidisrc->ports = DEFAULT_PORTS;
+
+  gst_base_src_set_format (GST_BASE_SRC (alsamidisrc), GST_FORMAT_TIME);
+  gst_base_src_set_live (GST_BASE_SRC (alsamidisrc), TRUE);
+}
+
+static void
+gst_alsa_midi_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstAlsaMidiSrc *src;
+
+  src = GST_ALSA_MIDI_SRC (object);
+
+  switch (prop_id) {
+    case PROP_PORTS:
+      g_free (src->ports);
+      src->ports = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+gst_alsa_midi_src_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstAlsaMidiSrc *src;
+
+  g_return_if_fail (GST_IS_ALSA_MIDI_SRC (object));
+
+  src = GST_ALSA_MIDI_SRC (object);
+
+  switch (prop_id) {
+    case PROP_PORTS:
+      g_value_set_string (value, src->ports);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static GstBuffer *
+prepare_buffer (GstAlsaMidiSrc * alsamidisrc, gpointer data, guint size)
+{
+  GstClockTime time;
+  gpointer local_data;
+  GstBuffer *buffer;
+
+  buffer = gst_buffer_new ();
+
+  time = alsamidisrc->tick * MIDI_TICK_PERIOD_MS * GST_MSECOND;
+
+  GST_BUFFER_DTS (buffer) = time;
+  GST_BUFFER_PTS (buffer) = time;
+  GST_BUFFER_OFFSET (buffer) = time;
+  GST_BUFFER_DURATION (buffer) = MIDI_TICK_PERIOD_MS * GST_MSECOND;
+
+  local_data = g_memdup (data, size);
+
+  gst_buffer_append_memory (buffer,
+      gst_memory_new_wrapped (0, local_data, size, 0, size, local_data,
+          g_free));
+
+  GST_MEMDUMP_OBJECT (alsamidisrc, "MIDI data:", local_data, size);
+
+  alsamidisrc->tick += 1;
+
+  return buffer;
+}
+
+static void
+push_buffer (GstAlsaMidiSrc * alsamidisrc, gpointer data, guint size,
+    GstBufferList * buffer_list)
+{
+  gst_buffer_list_add (buffer_list, prepare_buffer (alsamidisrc, data, size));
+}
+
+static void
+push_tick_buffer (GstAlsaMidiSrc * alsamidisrc, GstBufferList * buffer_list)
+{
+  alsamidisrc->buffer[0] = MIDI_TICK;
+  push_buffer (alsamidisrc, alsamidisrc->buffer, 1, buffer_list);
+}
+
+static GstFlowReturn
+gst_alsa_midi_src_create (GstPushSrc * src, GstBuffer ** buf)
+{
+  GstAlsaMidiSrc *alsamidisrc;
+  GstBufferList *buffer_list;
+  long size_ev = 0;
+  int err;
+  int ret;
+  guint len;
+
+  alsamidisrc = GST_ALSA_MIDI_SRC (src);
+
+  buffer_list = gst_buffer_list_new ();
+
+  snd_seq_poll_descriptors (alsamidisrc->seq, alsamidisrc->pfds,
+      alsamidisrc->npfds, POLLIN);
+
+  /*
+   * The file descriptors are polled with a timeout _less_ than 10ms (the MIDI
+   * tick period) in order not to loose events because of possible overlaps
+   * with MIDI ticks.
+   *
+   * If the polling times out (no new events) then a MIDI-tick event gets
+   * generated in order to keep the pipeline alive and progressing.
+   *
+   * If new events are present, then they are decoded and queued in
+   * a buffer_list. One buffer per event will be queued, all with different
+   * timestamps (see the prepare_buffer() function); maybe this can be
+   * optimized but a as a proof-of-concept mechanism it works OK.
+   */
+  ret = poll (alsamidisrc->pfds, alsamidisrc->npfds, DEFAULT_POLL_TIMEOUT_MS);
+  if (ret < 0) {
+    GST_ERROR_OBJECT (alsamidisrc, "ERROR in poll: %s", strerror (errno));
+  } else if (ret == 0) {
+    push_tick_buffer (alsamidisrc, buffer_list);
+  } else {
+    /* There are events available */
+    do {
+      snd_seq_event_t *event;
+      err = snd_seq_event_input (alsamidisrc->seq, &event);
+      if (err < 0)
+        break;                  /* Processed all events */
+
+      if (event) {
+        size_ev =
+            snd_midi_event_decode (alsamidisrc->parser, alsamidisrc->buffer,
+            DEFAULT_BUFSIZE, event);
+        if (size_ev < 0) {
+          /* ENOENT indicates an event that is not a MIDI message, silently skip it */
+          if (-ENOENT == size_ev) {
+            GST_WARNING_OBJECT (alsamidisrc,
+                "Warning: Received non-MIDI message");
+            push_tick_buffer (alsamidisrc, buffer_list);
+          } else {
+            GST_ERROR_OBJECT (alsamidisrc,
+                "Error decoding event from ALSA to output: %s",
+                strerror (-size_ev));
+            goto error;
+          }
+        } else {
+          push_buffer (alsamidisrc, alsamidisrc->buffer, size_ev, buffer_list);
+        }
+      }
+    } while (err > 0);
+  }
+
+  len = gst_buffer_list_length (buffer_list);
+  if (len == 0)
+    goto error;
+
+  /* Pop the _last_ buffer in the list */
+  *buf = gst_buffer_copy (gst_buffer_list_get (buffer_list, len - 1));
+  gst_buffer_list_remove (buffer_list, len - 1, 1);
+  --len;
+
+  /* 
+   * If there are no more buffers left, free the list, otherwise push all the
+   * _previous_ buffers left in the list.
+   *
+   * The one popped above will be pushed last when this function returns.
+   */
+  if (len == 0)
+    gst_buffer_list_unref (buffer_list);
+  else
+    gst_pad_push_list (GST_BASE_SRC (src)->srcpad, buffer_list);
+
+  return GST_FLOW_OK;
+
+error:
+  gst_buffer_list_unref (buffer_list);
+  return GST_FLOW_ERROR;
+}
+
+static gboolean
+gst_alsa_midi_src_start (GstBaseSrc * basesrc)
+{
+  GstAlsaMidiSrc *alsamidisrc;
+  int ret;
+
+  alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
+
+  alsamidisrc->tick = 0;
+  alsamidisrc->port_count = 0;
+
+  ret = init_seq (alsamidisrc);
+  if (ret < 0)
+    goto err;
+
+  if (alsamidisrc->ports) {
+    ret = parse_ports (alsamidisrc->ports, alsamidisrc);
+    if (ret < 0)
+      goto error_seq_close;
+  }
+
+  ret = create_port (alsamidisrc);
+  if (ret < 0)
+    goto error_free_seq_ports;
+
+  connect_ports (alsamidisrc);
+
+  ret = snd_seq_nonblock (alsamidisrc->seq, 1);
+  if (ret < 0) {
+    GST_ERROR_OBJECT (alsamidisrc, "Cannot set nonblock mode - %s",
+        snd_strerror (ret));
+    goto error_free_seq_ports;
+  }
+
+  snd_midi_event_new (DEFAULT_BUFSIZE, &alsamidisrc->parser);
+  snd_midi_event_init (alsamidisrc->parser);
+  snd_midi_event_reset_decode (alsamidisrc->parser);
+
+  snd_midi_event_no_status (alsamidisrc->parser, 1);
+
+  alsamidisrc->buffer = g_try_malloc (DEFAULT_BUFSIZE);
+  if (alsamidisrc->buffer == NULL)
+    goto error_free_parser;
+
+  alsamidisrc->npfds =
+      snd_seq_poll_descriptors_count (alsamidisrc->seq, POLLIN);
+  alsamidisrc->pfds =
+      g_try_malloc (sizeof (*alsamidisrc->pfds) * alsamidisrc->npfds);
+  if (alsamidisrc->pfds == NULL)
+    goto error_free_buffer;
+
+  return TRUE;
+
+error_free_buffer:
+  g_free (alsamidisrc->buffer);
+error_free_parser:
+  snd_midi_event_free (alsamidisrc->parser);
+error_free_seq_ports:
+  g_free (alsamidisrc->seq_ports);
+error_seq_close:
+  snd_seq_close (alsamidisrc->seq);
+err:
+  return FALSE;
+}
+
+static gboolean
+gst_alsa_midi_src_stop (GstBaseSrc * basesrc)
+{
+  GstAlsaMidiSrc *alsamidisrc;
+
+  alsamidisrc = GST_ALSA_MIDI_SRC (basesrc);
+
+  g_free (alsamidisrc->pfds);
+  g_free (alsamidisrc->buffer);
+  snd_midi_event_free (alsamidisrc->parser);
+  g_free (alsamidisrc->seq_ports);
+  snd_seq_close (alsamidisrc->seq);
+
+  return TRUE;
+}
diff --git a/gst/midi/alsamidisrc.h b/gst/midi/alsamidisrc.h
new file mode 100644 (file)
index 0000000..8411ad0
--- /dev/null
@@ -0,0 +1,77 @@
+/* GStreamer
+ * Copyright (C) 2014  Antonio Ospite <ao2@ao2.it>
+ *
+ * gstalsamidisrc.h:
+ *
+ * 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_ALSA_MIDI_SRC_H__
+#define __GST_ALSA_MIDI_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstpushsrc.h>
+
+#include <alsa/asoundlib.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_ALSA_MIDI_SRC \
+  (gst_alsa_midi_src_get_type())
+#define GST_ALSA_MIDI_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ALSA_MIDI_SRC,GstAlsaMidiSrc))
+#define GST_ALSA_MIDI_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ALSA_MIDI_SRC,GstAlsaMidiSrcClass))
+#define GST_IS_ALSA_MIDI_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ALSA_MIDI_SRC))
+#define GST_IS_ALSA_MIDI_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ALSA_MIDI_SRC))
+
+typedef struct _GstAlsaMidiSrc GstAlsaMidiSrc;
+typedef struct _GstAlsaMidiSrcClass GstAlsaMidiSrcClass;
+
+/**
+ * GstAlsaMidiSrc:
+ *
+ * Opaque #GstAlsaMidiSrc data structure.
+ */
+struct _GstAlsaMidiSrc
+{
+  GstPushSrc element;
+
+  gchar *ports;
+
+  /*< private > */
+  snd_seq_t *seq;
+  int port_count;
+  snd_seq_addr_t *seq_ports;
+  snd_midi_event_t *parser;
+  unsigned char *buffer;
+
+  struct pollfd *pfds;
+  int npfds;
+
+  guint64 tick;
+};
+
+struct _GstAlsaMidiSrcClass
+{
+  GstPushSrcClass parent_class;
+};
+
+G_GNUC_INTERNAL GType gst_alsa_midi_src_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_ALSA_MIDI_SRC_H__ */
index 6171359..fda2e8f 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <gst/gst-i18n-plugin.h>
 
+#include "alsamidisrc.h"
 #include "midiparse.h"
 
 GST_DEBUG_CATEGORY_STATIC (midi_debug);
@@ -45,14 +46,22 @@ plugin_init (GstPlugin * plugin)
   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
 #endif
 
+#ifdef HAVE_ALSA
+  ret = gst_element_register (plugin, "alsamidisrc", GST_RANK_PRIMARY,
+      GST_TYPE_ALSA_MIDI_SRC);
+  if (!ret)
+    goto out;
+#endif
+
   ret = gst_element_register (plugin, "midiparse", GST_RANK_PRIMARY,
       GST_TYPE_MIDI_PARSE);
 
+out:
   return ret;
 }
 
 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
     GST_VERSION_MINOR,
     midi,
-    "Parse MIDI files",
+    "Generate MIDI events",
     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)