From cd7f4e524718a2f75467852235e4e13d64d2ac82 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Fri, 7 Aug 2015 15:28:54 +0200 Subject: [PATCH] midi: add an ALSA MIDI sequencer source 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 | 12 + gst/midi/Makefile.am | 7 + gst/midi/alsamidisrc.c | 525 +++++++++++++++++++++++++++++++++++++++++ gst/midi/alsamidisrc.h | 77 ++++++ gst/midi/midi.c | 11 +- 5 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 gst/midi/alsamidisrc.c create mode 100644 gst/midi/alsamidisrc.h diff --git a/configure.ac b/configure.ac index c8d3f810b0..af3d38dbd1 100644 --- a/configure.ac +++ b/configure.ac @@ -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, [ diff --git a/gst/midi/Makefile.am b/gst/midi/Makefile.am index 55f12df326..44ae2bf83c 100644 --- a/gst/midi/Makefile.am +++ b/gst/midi/Makefile.am @@ -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 index 0000000000..58cd27e415 --- /dev/null +++ b/gst/midi/alsamidisrc.c @@ -0,0 +1,525 @@ +/* GStreamer + * Copyright (C) 2014 Antonio Ospite + * + * 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. + * + * + * Example launch line + * |[ + * 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. + * + */ + +#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 "); + 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 index 0000000000..8411ad037c --- /dev/null +++ b/gst/midi/alsamidisrc.h @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2014 Antonio Ospite + * + * 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 +#include + +#include + +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__ */ diff --git a/gst/midi/midi.c b/gst/midi/midi.c index 6171359f62..fda2e8f535 100644 --- a/gst/midi/midi.c +++ b/gst/midi/midi.c @@ -26,6 +26,7 @@ #include +#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)