• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * midiparse - midi parser plugin for gstreamer
3  *
4  * Copyright 2013 Wim Taymans <wim.taymans@gmail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-midiparse
24  * @title: midiparse
25  * @see_also: fluiddec
26  *
27  * This element parses midi-files into midi events. You would need a midi
28  * renderer such as fluidsynth to convert the events into raw samples.
29  *
30  * ## Example pipeline
31  * |[
32  * gst-launch-1.0 filesrc location=song.mid ! midiparse ! fluiddec ! pulsesink
33  * ]| This example pipeline will parse the midi and render to raw audio which is
34  * played via pulseaudio.
35  *
36  */
37 
38 #ifdef HAVE_CONFIG_H
39 #  include <config.h>
40 #endif
41 
42 #include <gst/gst.h>
43 #include <string.h>
44 #include <glib.h>
45 
46 #include "midiparse.h"
47 
48 GST_DEBUG_CATEGORY_STATIC (gst_midi_parse_debug);
49 #define GST_CAT_DEFAULT gst_midi_parse_debug
50 
51 enum
52 {
53   /* FILL ME */
54   LAST_SIGNAL
55 };
56 
57 enum
58 {
59   PROP_0,
60   /* FILL ME */
61 };
62 
63 #define DEFAULT_TEMPO   500000  /* 120 BPM is the default */
64 
65 typedef struct
66 {
67   guint8 *data;
68   guint size;
69   guint offset;
70 
71   guint8 running_status;
72   guint64 pulse;
73   gboolean eot;
74 
75 } GstMidiTrack;
76 
77 typedef GstFlowReturn (*GstMidiPushFunc) (GstMidiParse * parse,
78     GstMidiTrack * track, guint8 event, guint8 * data, guint length,
79     gpointer user_data);
80 
81 static void gst_midi_parse_finalize (GObject * object);
82 
83 static gboolean gst_midi_parse_sink_event (GstPad * pad, GstObject * parent,
84     GstEvent * event);
85 static gboolean gst_midi_parse_src_event (GstPad * pad, GstObject * parent,
86     GstEvent * event);
87 
88 static GstStateChangeReturn gst_midi_parse_change_state (GstElement * element,
89     GstStateChange transition);
90 static gboolean gst_midi_parse_activate (GstPad * pad, GstObject * parent);
91 static gboolean gst_midi_parse_activatemode (GstPad * pad, GstObject * parent,
92     GstPadMode mode, gboolean active);
93 
94 static void gst_midi_parse_loop (GstPad * sinkpad);
95 static GstFlowReturn gst_midi_parse_chain (GstPad * sinkpad, GstObject * parent,
96     GstBuffer * buffer);
97 
98 static gboolean gst_midi_parse_src_query (GstPad * pad, GstObject * parent,
99     GstQuery * query);
100 
101 static void gst_midi_parse_set_property (GObject * object, guint prop_id,
102     const GValue * value, GParamSpec * pspec);
103 static void gst_midi_parse_get_property (GObject * object, guint prop_id,
104     GValue * value, GParamSpec * pspec);
105 
106 static void reset_track (GstMidiTrack * track, GstMidiParse * midiparse);
107 
108 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
109     GST_PAD_SINK,
110     GST_PAD_ALWAYS,
111     GST_STATIC_CAPS ("audio/midi; audio/riff-midi")
112     );
113 
114 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
115     GST_PAD_SRC,
116     GST_PAD_ALWAYS,
117     GST_STATIC_CAPS ("audio/x-midi-event"));
118 
119 #define parent_class gst_midi_parse_parent_class
120 G_DEFINE_TYPE (GstMidiParse, gst_midi_parse, GST_TYPE_ELEMENT);
121 GST_ELEMENT_REGISTER_DEFINE (midiparse, "midiparse", GST_RANK_PRIMARY,
122     GST_TYPE_MIDI_PARSE);
123 
124 /* initialize the plugin's class */
125 static void
gst_midi_parse_class_init(GstMidiParseClass * klass)126 gst_midi_parse_class_init (GstMidiParseClass * klass)
127 {
128   GObjectClass *gobject_class;
129   GstElementClass *gstelement_class;
130 
131   gobject_class = (GObjectClass *) klass;
132   gstelement_class = (GstElementClass *) klass;
133 
134   gobject_class->finalize = gst_midi_parse_finalize;
135   gobject_class->set_property = gst_midi_parse_set_property;
136   gobject_class->get_property = gst_midi_parse_get_property;
137 
138   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
139   gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
140   gst_element_class_set_static_metadata (gstelement_class, "MidiParse",
141       "Codec/Demuxer/Audio",
142       "Midi Parser Element", "Wim Taymans <wim.taymans@gmail.com>");
143 
144   GST_DEBUG_CATEGORY_INIT (gst_midi_parse_debug, "midiparse",
145       0, "MIDI parser plugin");
146 
147   gstelement_class->change_state = gst_midi_parse_change_state;
148 }
149 
150 /* initialize the new element
151  * instantiate pads and add them to element
152  * set functions
153  * initialize structure
154  */
155 static void
gst_midi_parse_init(GstMidiParse * filter)156 gst_midi_parse_init (GstMidiParse * filter)
157 {
158   filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
159 
160   gst_pad_set_activatemode_function (filter->sinkpad,
161       gst_midi_parse_activatemode);
162   gst_pad_set_activate_function (filter->sinkpad, gst_midi_parse_activate);
163   gst_pad_set_event_function (filter->sinkpad, gst_midi_parse_sink_event);
164   gst_pad_set_chain_function (filter->sinkpad, gst_midi_parse_chain);
165   gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
166 
167   filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
168 
169   gst_pad_set_query_function (filter->srcpad, gst_midi_parse_src_query);
170   gst_pad_set_event_function (filter->srcpad, gst_midi_parse_src_event);
171   gst_pad_use_fixed_caps (filter->srcpad);
172 
173   gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
174 
175   gst_segment_init (&filter->segment, GST_FORMAT_TIME);
176 
177   filter->adapter = gst_adapter_new ();
178 
179   filter->have_group_id = FALSE;
180   filter->group_id = G_MAXUINT;
181 }
182 
183 static void
gst_midi_parse_finalize(GObject * object)184 gst_midi_parse_finalize (GObject * object)
185 {
186   GstMidiParse *midiparse;
187 
188   midiparse = GST_MIDI_PARSE (object);
189 
190   g_object_unref (midiparse->adapter);
191   g_free (midiparse->data);
192 
193   G_OBJECT_CLASS (parent_class)->finalize (object);
194 }
195 
196 static gboolean
gst_midi_parse_src_query(GstPad * pad,GstObject * parent,GstQuery * query)197 gst_midi_parse_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
198 {
199   gboolean res = TRUE;
200   GstMidiParse *midiparse = GST_MIDI_PARSE (parent);
201 
202   switch (GST_QUERY_TYPE (query)) {
203     case GST_QUERY_DURATION:
204       gst_query_set_duration (query, GST_FORMAT_TIME,
205           midiparse->segment.duration);
206       break;
207     case GST_QUERY_POSITION:
208       gst_query_set_position (query, GST_FORMAT_TIME,
209           midiparse->segment.position);
210       break;
211     case GST_QUERY_FORMATS:
212       gst_query_set_formats (query, 1, GST_FORMAT_TIME);
213       break;
214     case GST_QUERY_SEGMENT:{
215       GstFormat format;
216       gint64 start, stop;
217 
218       format = midiparse->segment.format;
219 
220       start =
221           gst_segment_to_stream_time (&midiparse->segment, format,
222           midiparse->segment.start);
223       if ((stop = midiparse->segment.stop) == -1)
224         stop = midiparse->segment.duration;
225       else
226         stop = gst_segment_to_stream_time (&midiparse->segment, format, stop);
227 
228       gst_query_set_segment (query, midiparse->segment.rate, format, start,
229           stop);
230       res = TRUE;
231       break;
232     }
233     case GST_QUERY_SEEKING:
234       gst_query_set_seeking (query, midiparse->segment.format,
235           FALSE, 0, midiparse->segment.duration);
236       break;
237     default:
238       res = gst_pad_query_default (pad, parent, query);
239       break;
240   }
241 
242   return res;
243 }
244 
245 static gboolean
gst_midi_parse_do_seek(GstMidiParse * midiparse,GstSegment * segment)246 gst_midi_parse_do_seek (GstMidiParse * midiparse, GstSegment * segment)
247 {
248   /* if seeking backwards, start from 0 else we just let things run and
249    * have it clip downstream */
250   GST_DEBUG_OBJECT (midiparse, "seeking back to 0");
251   segment->position = 0;
252   g_list_foreach (midiparse->tracks, (GFunc) reset_track, midiparse);
253   midiparse->pulse = 0;
254 
255   return TRUE;
256 }
257 
258 static gboolean
gst_midi_parse_perform_seek(GstMidiParse * midiparse,GstEvent * event)259 gst_midi_parse_perform_seek (GstMidiParse * midiparse, GstEvent * event)
260 {
261   gboolean res = TRUE, tres;
262   gdouble rate;
263   GstFormat seek_format;
264   GstSeekFlags flags;
265   GstSeekType start_type, stop_type;
266   gint64 start, stop;
267   gboolean flush;
268   gboolean update;
269   GstSegment seeksegment;
270   guint32 seqnum;
271   GstEvent *tevent;
272 
273   GST_DEBUG_OBJECT (midiparse, "doing seek: %" GST_PTR_FORMAT, event);
274 
275   if (event) {
276     gst_event_parse_seek (event, &rate, &seek_format, &flags,
277         &start_type, &start, &stop_type, &stop);
278 
279     if (seek_format != GST_FORMAT_TIME)
280       goto invalid_format;
281 
282     flush = flags & GST_SEEK_FLAG_FLUSH;
283     seqnum = gst_event_get_seqnum (event);
284   } else {
285     flush = FALSE;
286     /* get next seqnum */
287     seqnum = gst_util_seqnum_next ();
288   }
289 
290   /* send flush start */
291   if (flush) {
292     tevent = gst_event_new_flush_start ();
293     gst_event_set_seqnum (tevent, seqnum);
294     gst_pad_push_event (midiparse->srcpad, tevent);
295   } else
296     gst_pad_pause_task (midiparse->srcpad);
297 
298   /* grab streaming lock, this should eventually be possible, either
299    * because the task is paused, our streaming thread stopped
300    * or because our peer is flushing. */
301   GST_PAD_STREAM_LOCK (midiparse->sinkpad);
302   if (G_UNLIKELY (midiparse->seqnum == seqnum)) {
303     /* we have seen this event before, issue a warning for now */
304     GST_WARNING_OBJECT (midiparse, "duplicate event found %" G_GUINT32_FORMAT,
305         seqnum);
306   } else {
307     midiparse->seqnum = seqnum;
308     GST_DEBUG_OBJECT (midiparse, "seek with seqnum %" G_GUINT32_FORMAT, seqnum);
309   }
310 
311   /* Copy the current segment info into the temp segment that we can actually
312    * attempt the seek with. We only update the real segment if the seek succeeds. */
313   memcpy (&seeksegment, &midiparse->segment, sizeof (GstSegment));
314 
315   /* now configure the final seek segment */
316   if (event) {
317     gst_segment_do_seek (&seeksegment, rate, seek_format, flags,
318         start_type, start, stop_type, stop, &update);
319   }
320 
321   /* Else, no seek event passed, so we're just (re)starting the
322      current segment. */
323   GST_DEBUG_OBJECT (midiparse, "segment configured from %" G_GINT64_FORMAT
324       " to %" G_GINT64_FORMAT ", position %" G_GINT64_FORMAT,
325       seeksegment.start, seeksegment.stop, seeksegment.position);
326 
327   /* do the seek, segment.position contains the new position. */
328   res = gst_midi_parse_do_seek (midiparse, &seeksegment);
329 
330   /* and prepare to continue streaming */
331   if (flush) {
332     tevent = gst_event_new_flush_stop (TRUE);
333     gst_event_set_seqnum (tevent, seqnum);
334     /* send flush stop, peer will accept data and events again. We
335      * are not yet providing data as we still have the STREAM_LOCK. */
336     gst_pad_push_event (midiparse->srcpad, tevent);
337   }
338 
339   /* if the seek was successful, we update our real segment and push
340    * out the new segment. */
341   if (res) {
342     GST_OBJECT_LOCK (midiparse);
343     memcpy (&midiparse->segment, &seeksegment, sizeof (GstSegment));
344     GST_OBJECT_UNLOCK (midiparse);
345 
346     if (seeksegment.flags & GST_SEGMENT_FLAG_SEGMENT) {
347       GstMessage *message;
348 
349       message = gst_message_new_segment_start (GST_OBJECT (midiparse),
350           seeksegment.format, seeksegment.position);
351       gst_message_set_seqnum (message, seqnum);
352 
353       gst_element_post_message (GST_ELEMENT (midiparse), message);
354     }
355     /* for deriving a stop position for the playback segment from the seek
356      * segment, we must take the duration when the stop is not set */
357     if ((stop = seeksegment.stop) == -1)
358       stop = seeksegment.duration;
359 
360     midiparse->segment_pending = TRUE;
361     midiparse->discont = TRUE;
362   }
363 
364   /* and restart the task in case it got paused explicitly or by
365    * the FLUSH_START event we pushed out. */
366   tres =
367       gst_pad_start_task (midiparse->sinkpad,
368       (GstTaskFunction) gst_midi_parse_loop, midiparse->sinkpad, NULL);
369   if (res && !tres)
370     res = FALSE;
371 
372   /* and release the lock again so we can continue streaming */
373   GST_PAD_STREAM_UNLOCK (midiparse->sinkpad);
374 
375   return res;
376 
377   /* ERROR */
378 invalid_format:
379   {
380     GST_DEBUG_OBJECT (midiparse, "Unsupported seek format %s",
381         gst_format_get_name (seek_format));
382     return FALSE;
383   }
384 }
385 
386 static gboolean
gst_midi_parse_src_event(GstPad * pad,GstObject * parent,GstEvent * event)387 gst_midi_parse_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
388 {
389   gboolean res = FALSE;
390   GstMidiParse *midiparse = GST_MIDI_PARSE (parent);
391 
392   GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event));
393 
394   switch (GST_EVENT_TYPE (event)) {
395     case GST_EVENT_SEEK:
396       res = gst_midi_parse_perform_seek (midiparse, event);
397       break;
398     default:
399       break;
400   }
401   gst_event_unref (event);
402 
403   return res;
404 }
405 
406 static gboolean
gst_midi_parse_activate(GstPad * sinkpad,GstObject * parent)407 gst_midi_parse_activate (GstPad * sinkpad, GstObject * parent)
408 {
409   GstQuery *query;
410   gboolean pull_mode;
411 
412   query = gst_query_new_scheduling ();
413 
414   if (!gst_pad_peer_query (sinkpad, query)) {
415     gst_query_unref (query);
416     goto activate_push;
417   }
418 
419   pull_mode = gst_query_has_scheduling_mode_with_flags (query,
420       GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
421   gst_query_unref (query);
422 
423   if (!pull_mode)
424     goto activate_push;
425 
426   GST_DEBUG_OBJECT (sinkpad, "activating pull");
427   return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE);
428 
429 activate_push:
430   {
431     GST_DEBUG_OBJECT (sinkpad, "activating push");
432     return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE);
433   }
434 }
435 
436 static gboolean
gst_midi_parse_activatemode(GstPad * pad,GstObject * parent,GstPadMode mode,gboolean active)437 gst_midi_parse_activatemode (GstPad * pad, GstObject * parent,
438     GstPadMode mode, gboolean active)
439 {
440   gboolean res;
441 
442   switch (mode) {
443     case GST_PAD_MODE_PUSH:
444       res = TRUE;
445       break;
446     case GST_PAD_MODE_PULL:
447       if (active) {
448         res = gst_pad_start_task (pad, (GstTaskFunction) gst_midi_parse_loop,
449             pad, NULL);
450       } else {
451         res = gst_pad_stop_task (pad);
452       }
453       break;
454     default:
455       res = FALSE;
456       break;
457   }
458   return res;
459 }
460 
461 static gboolean
parse_MThd(GstMidiParse * midiparse,guint8 * data,guint size)462 parse_MThd (GstMidiParse * midiparse, guint8 * data, guint size)
463 {
464   guint16 format, ntracks, division;
465   gboolean multitrack;
466 
467   format = GST_READ_UINT16_BE (data);
468   switch (format) {
469     case 0:
470       multitrack = FALSE;
471       break;
472     case 1:
473       multitrack = TRUE;
474       break;
475     default:
476     case 2:
477       goto invalid_format;
478   }
479   ntracks = GST_READ_UINT16_BE (data + 2);
480   if (ntracks > 1 && !multitrack)
481     goto invalid_tracks;
482 
483   division = GST_READ_UINT16_BE (data + 4);
484   if (division & 0x8000)
485     goto invalid_division;
486 
487   GST_DEBUG_OBJECT (midiparse, "format %u, tracks %u, division %u",
488       format, ntracks, division);
489 
490   midiparse->ntracks = ntracks;
491   midiparse->division = division;
492 
493   return TRUE;
494 
495 invalid_format:
496   {
497     GST_ERROR_OBJECT (midiparse, "unsupported midi format %u", format);
498     return FALSE;
499   }
500 invalid_tracks:
501   {
502     GST_ERROR_OBJECT (midiparse, "invalid number of tracks %u for format %u",
503         ntracks, format);
504     return FALSE;
505   }
506 invalid_division:
507   {
508     GST_ERROR_OBJECT (midiparse, "unsupported division");
509     return FALSE;
510   }
511 }
512 
513 static guint
parse_varlen(GstMidiParse * midiparse,guint8 * data,guint size,gint32 * result)514 parse_varlen (GstMidiParse * midiparse, guint8 * data, guint size,
515     gint32 * result)
516 {
517   gint32 res;
518   gint i;
519 
520   res = 0;
521   for (i = 0; i < 4; i++) {
522     if (size == 0)
523       return 0;
524 
525     res = (res << 7) | ((data[i]) & 0x7f);
526     if ((data[i] & 0x80) == 0) {
527       *result = res;
528       return i + 1;
529     }
530   }
531   return 0;
532 }
533 
534 static GstFlowReturn
handle_meta_event(GstMidiParse * midiparse,GstMidiTrack * track,guint8 event)535 handle_meta_event (GstMidiParse * midiparse, GstMidiTrack * track, guint8 event)
536 {
537   guint8 type;
538   guint8 *data;
539   gchar *bytes;
540   guint size, consumed;
541   gint32 length;
542 
543   track->offset += 1;
544 
545   data = track->data + track->offset;
546   size = track->size - track->offset;
547 
548   if (size < 1)
549     goto short_file;
550 
551   type = data[0];
552 
553   consumed = parse_varlen (midiparse, data + 1, size - 1, &length);
554   if (consumed == 0)
555     goto short_file;
556 
557   data += consumed + 1;
558   size -= consumed + 1;
559 
560   if (size < length)
561     goto short_file;
562 
563   GST_DEBUG_OBJECT (midiparse, "handle meta event type 0x%02x, length %u",
564       type, length);
565 
566   bytes = g_strndup ((const gchar *) data, length);
567 
568   switch (type) {
569     case 0x01:
570       GST_DEBUG_OBJECT (midiparse, "Text: %s", bytes);
571       break;
572     case 0x02:
573       GST_DEBUG_OBJECT (midiparse, "Copyright: %s", bytes);
574       break;
575     case 0x03:
576       GST_DEBUG_OBJECT (midiparse, "Track Name: %s", bytes);
577       break;
578     case 0x04:
579       GST_DEBUG_OBJECT (midiparse, "Instrument: %s", bytes);
580       break;
581     case 0x05:
582       GST_DEBUG_OBJECT (midiparse, "Lyric: %s", bytes);
583       break;
584     case 0x06:
585       GST_DEBUG_OBJECT (midiparse, "Marker: %s", bytes);
586       break;
587     case 0x07:
588       GST_DEBUG_OBJECT (midiparse, "Cue point: %s", bytes);
589       break;
590     case 0x08:
591       GST_DEBUG_OBJECT (midiparse, "Patch name: %s", bytes);
592       break;
593     case 0x09:
594       GST_DEBUG_OBJECT (midiparse, "MIDI port: %s", bytes);
595       break;
596     case 0x2f:
597       GST_DEBUG_OBJECT (midiparse, "End of track");
598       break;
599     case 0x51:
600     {
601       guint32 uspqn = (data[0] << 16) | (data[1] << 8) | data[2];
602       midiparse->tempo = (uspqn ? uspqn : DEFAULT_TEMPO);
603       GST_DEBUG_OBJECT (midiparse, "tempo %u", midiparse->tempo);
604       break;
605     }
606     case 0x54:
607       GST_DEBUG_OBJECT (midiparse, "SMPTE offset");
608       break;
609     case 0x58:
610       GST_DEBUG_OBJECT (midiparse, "Time signature");
611       break;
612     case 0x59:
613       GST_DEBUG_OBJECT (midiparse, "Key signature");
614       break;
615     case 0x7f:
616       GST_DEBUG_OBJECT (midiparse, "Proprietary event");
617       break;
618     default:
619       GST_DEBUG_OBJECT (midiparse, "unknown event 0x%02x length %d", type,
620           length);
621       break;
622   }
623   g_free (bytes);
624 
625   track->offset += consumed + length + 1;
626 
627   return GST_FLOW_OK;
628 
629   /* ERRORS */
630 short_file:
631   {
632     GST_DEBUG_OBJECT (midiparse, "not enough data");
633     return GST_FLOW_ERROR;
634   }
635 }
636 
637 static GstFlowReturn
handle_sysex_event(GstMidiParse * midiparse,GstMidiTrack * track,guint8 event,GstMidiPushFunc pushfunc,gpointer user_data)638 handle_sysex_event (GstMidiParse * midiparse, GstMidiTrack * track,
639     guint8 event, GstMidiPushFunc pushfunc, gpointer user_data)
640 {
641   GstFlowReturn ret;
642   guint8 *data;
643   guint size, consumed;
644   gint32 length;
645 
646   track->offset += 1;
647 
648   data = track->data + track->offset;
649   size = track->size - track->offset;
650 
651   consumed = parse_varlen (midiparse, data, size, &length);
652   if (consumed == 0)
653     goto short_file;
654 
655   data += consumed;
656   size -= consumed;
657 
658   if (size < length)
659     goto short_file;
660 
661   GST_DEBUG_OBJECT (midiparse, "handle sysex event 0x%02x, length %u",
662       event, length);
663 
664   if (pushfunc)
665     ret = pushfunc (midiparse, track, event, data, length, user_data);
666   else
667     ret = GST_FLOW_OK;
668 
669   track->offset += consumed + length;
670 
671   return ret;
672 
673   /* ERRORS */
674 short_file:
675   {
676     GST_DEBUG_OBJECT (midiparse, "not enough data");
677     return GST_FLOW_ERROR;
678   }
679 }
680 
681 
682 static guint8
event_from_status(GstMidiParse * midiparse,GstMidiTrack * track,guint8 status)683 event_from_status (GstMidiParse * midiparse, GstMidiTrack * track,
684     guint8 status)
685 {
686   if ((status & 0x80) == 0) {
687     if ((track->running_status & 0x80) == 0)
688       return 0;
689 
690     return track->running_status;
691   } else {
692     return status;
693   }
694 }
695 
696 static gboolean
update_track_position(GstMidiParse * midiparse,GstMidiTrack * track)697 update_track_position (GstMidiParse * midiparse, GstMidiTrack * track)
698 {
699   gint32 delta_time;
700   guint8 *data;
701   guint size, consumed;
702 
703   if (track->offset >= track->size)
704     goto eot;
705 
706   data = track->data + track->offset;
707   size = track->size - track->offset;
708 
709   consumed = parse_varlen (midiparse, data, size, &delta_time);
710   if (consumed == 0)
711     goto eot;
712 
713   track->pulse += delta_time;
714   track->offset += consumed;
715 
716   GST_LOG_OBJECT (midiparse, "updated track to pulse %" G_GUINT64_FORMAT,
717       track->pulse);
718 
719   return TRUE;
720 
721   /* ERRORS */
722 eot:
723   {
724     GST_DEBUG_OBJECT (midiparse, "track ended");
725     track->eot = TRUE;
726     return FALSE;
727   }
728 }
729 
730 static GstFlowReturn
handle_next_event(GstMidiParse * midiparse,GstMidiTrack * track,GstMidiPushFunc pushfunc,gpointer user_data)731 handle_next_event (GstMidiParse * midiparse, GstMidiTrack * track,
732     GstMidiPushFunc pushfunc, gpointer user_data)
733 {
734   GstFlowReturn ret = GST_FLOW_OK;
735   guint8 status, event;
736   guint length;
737   guint8 *data;
738 
739   data = &track->data[track->offset];
740 
741   status = data[0];
742   event = event_from_status (midiparse, track, status);
743 
744   GST_LOG_OBJECT (midiparse, "track %p, status 0x%02x, event 0x%02x", track,
745       status, event);
746 
747   switch (event & 0xf0) {
748     case 0xf0:
749       switch (event) {
750         case 0xff:
751           ret = handle_meta_event (midiparse, track, event);
752           break;
753         case 0xf0:
754         case 0xf7:
755           ret =
756               handle_sysex_event (midiparse, track, event, pushfunc, user_data);
757           break;
758         default:
759           goto unhandled_event;
760       }
761       length = 0;
762       break;
763     case 0xc0:
764     case 0xd0:
765       length = 1;
766       break;
767     case 0x80:
768     case 0x90:
769     case 0xa0:
770     case 0xb0:
771     case 0xe0:
772       length = 2;
773       break;
774     default:
775       goto undefined_status;
776   }
777   if (length > 0) {
778     if (status & 0x80) {
779       if (pushfunc)
780         ret = pushfunc (midiparse, track, event, data + 1, length, user_data);
781       track->offset += length + 1;
782     } else {
783       if (pushfunc)
784         ret = pushfunc (midiparse, track, event, data, length + 1, user_data);
785       track->offset += length;
786     }
787   }
788 
789   if (ret == GST_FLOW_OK) {
790     if (event < 0xF8)
791       track->running_status = event;
792 
793     update_track_position (midiparse, track);
794   }
795   return ret;
796 
797   /* ERRORS */
798 undefined_status:
799   {
800     GST_ERROR_OBJECT (midiparse, "Undefined status and invalid running status");
801     return GST_FLOW_ERROR;
802   }
803 unhandled_event:
804   {
805     /* we don't know the size so we can't continue parsing */
806     GST_ERROR_OBJECT (midiparse, "unhandled event 0x%08x", event);
807     return GST_FLOW_ERROR;
808   }
809 }
810 
811 static void
reset_track(GstMidiTrack * track,GstMidiParse * midiparse)812 reset_track (GstMidiTrack * track, GstMidiParse * midiparse)
813 {
814   GST_DEBUG_OBJECT (midiparse, "reset track");
815   track->offset = 0;
816   track->pulse = 0;
817   track->eot = FALSE;
818   track->running_status = 0xff;
819   update_track_position (midiparse, track);
820 }
821 
822 static gboolean
parse_MTrk(GstMidiParse * midiparse,guint8 * data,guint size)823 parse_MTrk (GstMidiParse * midiparse, guint8 * data, guint size)
824 {
825   GstMidiTrack *track;
826   GstClockTime duration;
827 
828   /* ignore excess tracks */
829   if (midiparse->track_count >= midiparse->ntracks)
830     return TRUE;
831 
832   track = g_slice_new (GstMidiTrack);
833   track->data = data;
834   track->size = size;
835   reset_track (track, midiparse);
836 
837   midiparse->tracks = g_list_append (midiparse->tracks, track);
838   midiparse->track_count++;
839 
840   /* now loop over all events and calculate the duration */
841   while (!track->eot) {
842     handle_next_event (midiparse, track, NULL, NULL);
843   }
844 
845   duration = gst_util_uint64_scale (track->pulse,
846       1000 * midiparse->tempo, midiparse->division);
847 
848   GST_DEBUG_OBJECT (midiparse, "duration %" GST_TIME_FORMAT,
849       GST_TIME_ARGS (duration));
850 
851   if (duration > midiparse->segment.duration)
852     midiparse->segment.duration = duration;
853 
854   reset_track (track, midiparse);
855 
856   return TRUE;
857 }
858 
859 static gboolean
find_midi_chunk(GstMidiParse * midiparse,guint8 * data,guint size,guint * offset,guint * length)860 find_midi_chunk (GstMidiParse * midiparse, guint8 * data, guint size,
861     guint * offset, guint * length)
862 {
863   guint32 type;
864 
865   *length = 0;
866 
867   if (size < 8)
868     goto short_chunk;
869 
870   type = GST_STR_FOURCC (data);
871 
872   if (type == GST_MAKE_FOURCC ('R', 'I', 'F', 'F')) {
873     guint32 riff_len;
874 
875     GST_DEBUG_OBJECT (midiparse, "found RIFF");
876 
877     if (size < 12)
878       goto short_chunk;
879 
880     if (GST_STR_FOURCC (data + 8) != GST_MAKE_FOURCC ('R', 'M', 'I', 'D'))
881       goto invalid_format;
882 
883     riff_len = GST_READ_UINT32_LE (data + 4);
884 
885     if (size < riff_len)
886       goto short_chunk;
887 
888     data += 12;
889     size -= 12;
890     *offset = 12;
891 
892     GST_DEBUG_OBJECT (midiparse, "found RIFF RMID of size %u", riff_len);
893 
894     while (TRUE) {
895       guint32 chunk_type;
896       guint32 chunk_len;
897 
898       if (riff_len < 8)
899         goto short_chunk;
900 
901       chunk_type = GST_STR_FOURCC (data);
902       chunk_len = GST_READ_UINT32_LE (data + 4);
903 
904       riff_len -= 8;
905       if (riff_len < chunk_len)
906         goto short_chunk;
907 
908       data += 8;
909       size -= 8;
910       *offset += 8;
911       riff_len -= chunk_len;
912 
913       if (chunk_type == GST_MAKE_FOURCC ('d', 'a', 't', 'a')) {
914         *length = chunk_len;
915         break;
916       }
917 
918       data += chunk_len;
919       size -= chunk_len;
920     }
921   } else {
922     *offset = 0;
923     *length = size;
924   }
925   return TRUE;
926 
927   /* ERRORS */
928 short_chunk:
929   {
930     GST_LOG_OBJECT (midiparse, "not enough data %u < %u", *length + 8, size);
931     return FALSE;
932   }
933 invalid_format:
934   {
935     GST_ERROR_OBJECT (midiparse, "invalid format");
936     return FALSE;
937   }
938 }
939 
940 static guint
gst_midi_parse_chunk(GstMidiParse * midiparse,guint8 * data,guint size)941 gst_midi_parse_chunk (GstMidiParse * midiparse, guint8 * data, guint size)
942 {
943   guint32 type, length = 0;
944 
945   if (size < 8)
946     goto short_chunk;
947 
948   length = GST_READ_UINT32_BE (data + 4);
949 
950   GST_DEBUG_OBJECT (midiparse, "have type %c%c%c%c, length %u",
951       data[0], data[1], data[2], data[3], length);
952 
953   if (size < length + 8)
954     goto short_chunk;
955 
956   type = GST_STR_FOURCC (data);
957 
958   switch (type) {
959     case GST_MAKE_FOURCC ('M', 'T', 'h', 'd'):
960       if (!parse_MThd (midiparse, data + 8, length))
961         goto invalid_format;
962       break;
963     case GST_MAKE_FOURCC ('M', 'T', 'r', 'k'):
964       if (!parse_MTrk (midiparse, data + 8, length))
965         goto invalid_format;
966       break;
967     default:
968       GST_LOG_OBJECT (midiparse, "ignore chunk");
969       break;
970   }
971 
972   return length + 8;
973 
974   /* ERRORS */
975 short_chunk:
976   {
977     GST_LOG_OBJECT (midiparse, "not enough data %u < %u", size, length + 8);
978     return 0;
979   }
980 invalid_format:
981   {
982     GST_ERROR_OBJECT (midiparse, "invalid format");
983     return 0;
984   }
985 }
986 
987 static GstFlowReturn
gst_midi_parse_parse_song(GstMidiParse * midiparse)988 gst_midi_parse_parse_song (GstMidiParse * midiparse)
989 {
990   GstCaps *outcaps;
991   guint8 *data;
992   guint size, offset, length;
993   GstEvent *event;
994   gchar *stream_id;
995 
996   GST_DEBUG_OBJECT (midiparse, "Parsing song");
997 
998   gst_segment_init (&midiparse->segment, GST_FORMAT_TIME);
999   midiparse->segment.duration = 0;
1000   midiparse->pulse = 0;
1001 
1002   size = gst_adapter_available (midiparse->adapter);
1003   data = gst_adapter_take (midiparse->adapter, size);
1004 
1005   midiparse->data = data;
1006   midiparse->tempo = DEFAULT_TEMPO;
1007 
1008   if (!find_midi_chunk (midiparse, data, size, &offset, &length))
1009     goto invalid_format;
1010 
1011   while (length) {
1012     guint consumed;
1013 
1014     consumed = gst_midi_parse_chunk (midiparse, &data[offset], length);
1015     if (consumed == 0)
1016       goto short_file;
1017 
1018     offset += consumed;
1019     length -= consumed;
1020   }
1021 
1022   GST_DEBUG_OBJECT (midiparse, "song duration %" GST_TIME_FORMAT,
1023       GST_TIME_ARGS (midiparse->segment.duration));
1024 
1025   stream_id =
1026       gst_pad_create_stream_id (midiparse->srcpad, GST_ELEMENT_CAST (midiparse),
1027       NULL);
1028   event =
1029       gst_pad_get_sticky_event (midiparse->sinkpad, GST_EVENT_STREAM_START, 0);
1030   if (event) {
1031     if (gst_event_parse_group_id (event, &midiparse->group_id))
1032       midiparse->have_group_id = TRUE;
1033     else
1034       midiparse->have_group_id = FALSE;
1035     gst_event_unref (event);
1036   } else if (!midiparse->have_group_id) {
1037     midiparse->have_group_id = TRUE;
1038     midiparse->group_id = gst_util_group_id_next ();
1039   }
1040   event = gst_event_new_stream_start (stream_id);
1041   if (midiparse->have_group_id)
1042     gst_event_set_group_id (event, midiparse->group_id);
1043   gst_pad_push_event (midiparse->srcpad, event);
1044   g_free (stream_id);
1045 
1046   outcaps = gst_pad_get_pad_template_caps (midiparse->srcpad);
1047   gst_pad_set_caps (midiparse->srcpad, outcaps);
1048   gst_caps_unref (outcaps);
1049 
1050   midiparse->segment_pending = TRUE;
1051   midiparse->discont = TRUE;
1052 
1053   GST_DEBUG_OBJECT (midiparse, "Parsing song done");
1054 
1055   return GST_FLOW_OK;
1056 
1057   /* ERRORS */
1058 short_file:
1059   {
1060     GST_ERROR_OBJECT (midiparse, "not enough data");
1061     return GST_FLOW_ERROR;
1062   }
1063 invalid_format:
1064   {
1065     GST_ERROR_OBJECT (midiparse, "invalid format");
1066     return GST_FLOW_ERROR;
1067   }
1068 }
1069 
1070 static GstFlowReturn
play_push_func(GstMidiParse * midiparse,GstMidiTrack * track,guint8 event,guint8 * data,guint length,gpointer user_data)1071 play_push_func (GstMidiParse * midiparse, GstMidiTrack * track,
1072     guint8 event, guint8 * data, guint length, gpointer user_data)
1073 {
1074   GstBuffer *outbuf;
1075   GstMapInfo info;
1076   GstClockTime position;
1077 
1078   outbuf = gst_buffer_new_allocate (NULL, length + 1, NULL);
1079 
1080   gst_buffer_map (outbuf, &info, GST_MAP_WRITE);
1081   info.data[0] = event;
1082   if (length)
1083     memcpy (&info.data[1], data, length);
1084   gst_buffer_unmap (outbuf, &info);
1085 
1086   position = midiparse->segment.position;
1087   GST_BUFFER_PTS (outbuf) = position;
1088   GST_BUFFER_DTS (outbuf) = position;
1089 
1090   GST_DEBUG_OBJECT (midiparse, "pushing %" GST_TIME_FORMAT,
1091       GST_TIME_ARGS (position));
1092 
1093   if (midiparse->discont) {
1094     GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
1095     midiparse->discont = FALSE;
1096   }
1097 
1098   return gst_pad_push (midiparse->srcpad, outbuf);
1099 }
1100 
1101 static GstFlowReturn
gst_midi_parse_do_play(GstMidiParse * midiparse)1102 gst_midi_parse_do_play (GstMidiParse * midiparse)
1103 {
1104   GstFlowReturn res;
1105   GList *walk;
1106   guint64 pulse, next_pulse = G_MAXUINT64;
1107   GstClockTime position, next_position;
1108   guint64 tick;
1109 
1110   pulse = midiparse->pulse;
1111   position = midiparse->segment.position;
1112 
1113   if (midiparse->segment_pending) {
1114     gst_pad_push_event (midiparse->srcpad,
1115         gst_event_new_segment (&midiparse->segment));
1116     midiparse->segment_pending = FALSE;
1117   }
1118 
1119   GST_DEBUG_OBJECT (midiparse, "pulse %" G_GUINT64_FORMAT ", position %"
1120       GST_TIME_FORMAT, pulse, GST_TIME_ARGS (position));
1121 
1122   for (walk = midiparse->tracks; walk; walk = g_list_next (walk)) {
1123     GstMidiTrack *track = walk->data;
1124 
1125     while (!track->eot && track->pulse == pulse) {
1126       res = handle_next_event (midiparse, track, play_push_func, NULL);
1127       if (res != GST_FLOW_OK)
1128         goto error;
1129     }
1130 
1131     if (!track->eot && track->pulse < next_pulse)
1132       next_pulse = track->pulse;
1133   }
1134 
1135   if (next_pulse == G_MAXUINT64)
1136     goto eos;
1137 
1138   tick = position / (10 * GST_MSECOND);
1139   GST_DEBUG_OBJECT (midiparse, "current tick %" G_GUINT64_FORMAT, tick);
1140 
1141   next_position = gst_util_uint64_scale (next_pulse,
1142       1000 * midiparse->tempo, midiparse->division);
1143   GST_DEBUG_OBJECT (midiparse, "next position %" GST_TIME_FORMAT,
1144       GST_TIME_ARGS (next_position));
1145 
1146   /* send 10ms ticks to advance the downstream element */
1147   while (TRUE) {
1148     /* get position of next tick */
1149     position = ++tick * (10 * GST_MSECOND);
1150     GST_DEBUG_OBJECT (midiparse, "tick %" G_GUINT64_FORMAT
1151         ", position %" GST_TIME_FORMAT, tick, GST_TIME_ARGS (position));
1152 
1153     if (position >= next_position)
1154       break;
1155 
1156     midiparse->segment.position = position;
1157     res = play_push_func (midiparse, NULL, 0xf9, NULL, 0, NULL);
1158     if (res != GST_FLOW_OK)
1159       goto error;
1160   }
1161 
1162   midiparse->pulse = next_pulse;
1163   midiparse->segment.position = next_position;
1164 
1165   return GST_FLOW_OK;
1166 
1167   /* ERRORS */
1168 eos:
1169   {
1170     GST_DEBUG_OBJECT (midiparse, "we are EOS");
1171     return GST_FLOW_EOS;
1172   }
1173 error:
1174   {
1175     GST_DEBUG_OBJECT (midiparse, "have flow result %s",
1176         gst_flow_get_name (res));
1177     return res;
1178   }
1179 }
1180 
1181 static gboolean
gst_midi_parse_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)1182 gst_midi_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
1183 {
1184   gboolean res;
1185   GstMidiParse *midiparse = GST_MIDI_PARSE (parent);
1186 
1187   GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event));
1188 
1189   switch (GST_EVENT_TYPE (event)) {
1190     case GST_EVENT_EOS:
1191       midiparse->state = GST_MIDI_PARSE_STATE_PARSE;
1192       /* now start the parsing task */
1193       res = gst_pad_start_task (midiparse->sinkpad,
1194           (GstTaskFunction) gst_midi_parse_loop, midiparse->sinkpad, NULL);
1195       /* don't forward the event */
1196       gst_event_unref (event);
1197       break;
1198     case GST_EVENT_CAPS:
1199     case GST_EVENT_STREAM_START:
1200     case GST_EVENT_SEGMENT:
1201       res = TRUE;
1202       gst_event_unref (event);
1203       break;
1204     default:
1205       res = gst_pad_event_default (pad, parent, event);
1206       break;
1207   }
1208   return res;
1209 }
1210 
1211 static GstFlowReturn
gst_midi_parse_chain(GstPad * sinkpad,GstObject * parent,GstBuffer * buffer)1212 gst_midi_parse_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer)
1213 {
1214   GstMidiParse *midiparse;
1215 
1216   midiparse = GST_MIDI_PARSE (parent);
1217 
1218   /* push stuff in the adapter, we will start doing something in the sink event
1219    * handler when we get EOS */
1220   gst_adapter_push (midiparse->adapter, buffer);
1221 
1222   return GST_FLOW_OK;
1223 }
1224 
1225 static void
gst_midi_parse_loop(GstPad * sinkpad)1226 gst_midi_parse_loop (GstPad * sinkpad)
1227 {
1228   GstMidiParse *midiparse = GST_MIDI_PARSE (GST_PAD_PARENT (sinkpad));
1229   GstFlowReturn ret;
1230 
1231   switch (midiparse->state) {
1232     case GST_MIDI_PARSE_STATE_LOAD:
1233     {
1234       GstBuffer *buffer = NULL;
1235 
1236       GST_DEBUG_OBJECT (midiparse, "loading song");
1237 
1238       ret =
1239           gst_pad_pull_range (midiparse->sinkpad, midiparse->offset, -1,
1240           &buffer);
1241 
1242       if (ret == GST_FLOW_EOS) {
1243         GST_DEBUG_OBJECT (midiparse, "Song loaded");
1244         midiparse->state = GST_MIDI_PARSE_STATE_PARSE;
1245       } else if (ret != GST_FLOW_OK) {
1246         GST_ELEMENT_ERROR (midiparse, STREAM, DECODE, (NULL),
1247             ("Unable to read song"));
1248         goto pause;
1249       } else {
1250         GST_DEBUG_OBJECT (midiparse, "pushing buffer");
1251         gst_adapter_push (midiparse->adapter, buffer);
1252         midiparse->offset += gst_buffer_get_size (buffer);
1253       }
1254       break;
1255     }
1256     case GST_MIDI_PARSE_STATE_PARSE:
1257       ret = gst_midi_parse_parse_song (midiparse);
1258       if (ret != GST_FLOW_OK)
1259         goto pause;
1260       midiparse->state = GST_MIDI_PARSE_STATE_PLAY;
1261       break;
1262     case GST_MIDI_PARSE_STATE_PLAY:
1263       ret = gst_midi_parse_do_play (midiparse);
1264       if (ret != GST_FLOW_OK)
1265         goto pause;
1266       break;
1267     default:
1268       break;
1269   }
1270   return;
1271 
1272 pause:
1273   {
1274     const gchar *reason = gst_flow_get_name (ret);
1275     GstEvent *event;
1276 
1277     GST_DEBUG_OBJECT (midiparse, "pausing task, reason %s", reason);
1278     gst_pad_pause_task (sinkpad);
1279     if (ret == GST_FLOW_EOS) {
1280       /* perform EOS logic */
1281       event = gst_event_new_eos ();
1282       gst_pad_push_event (midiparse->srcpad, event);
1283     } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
1284       event = gst_event_new_eos ();
1285       /* for fatal errors we post an error message, post the error
1286        * first so the app knows about the error first. */
1287       GST_ELEMENT_FLOW_ERROR (midiparse, ret);
1288       gst_pad_push_event (midiparse->srcpad, event);
1289     }
1290   }
1291 }
1292 
1293 static void
free_track(GstMidiTrack * track,GstMidiParse * midiparse)1294 free_track (GstMidiTrack * track, GstMidiParse * midiparse)
1295 {
1296   g_slice_free (GstMidiTrack, track);
1297 }
1298 
1299 static void
gst_midi_parse_reset(GstMidiParse * midiparse)1300 gst_midi_parse_reset (GstMidiParse * midiparse)
1301 {
1302   gst_adapter_clear (midiparse->adapter);
1303   g_free (midiparse->data);
1304   midiparse->data = NULL;
1305   g_list_foreach (midiparse->tracks, (GFunc) free_track, midiparse);
1306   g_list_free (midiparse->tracks);
1307   midiparse->tracks = NULL;
1308   midiparse->track_count = 0;
1309   midiparse->have_group_id = FALSE;
1310   midiparse->group_id = G_MAXUINT;
1311 }
1312 
1313 static GstStateChangeReturn
gst_midi_parse_change_state(GstElement * element,GstStateChange transition)1314 gst_midi_parse_change_state (GstElement * element, GstStateChange transition)
1315 {
1316   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
1317   GstMidiParse *midiparse = GST_MIDI_PARSE (element);
1318 
1319   switch (transition) {
1320     case GST_STATE_CHANGE_NULL_TO_READY:
1321       break;
1322     case GST_STATE_CHANGE_READY_TO_PAUSED:
1323       midiparse->offset = 0;
1324       midiparse->state = GST_MIDI_PARSE_STATE_LOAD;
1325       break;
1326     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1327       break;
1328     default:
1329       break;
1330   }
1331 
1332   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1333 
1334   switch (transition) {
1335     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1336       break;
1337     case GST_STATE_CHANGE_PAUSED_TO_READY:
1338       gst_midi_parse_reset (midiparse);
1339       break;
1340     case GST_STATE_CHANGE_READY_TO_NULL:
1341       break;
1342     default:
1343       break;
1344   }
1345 
1346   return ret;
1347 }
1348 
1349 static void
gst_midi_parse_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1350 gst_midi_parse_set_property (GObject * object, guint prop_id,
1351     const GValue * value, GParamSpec * pspec)
1352 {
1353   switch (prop_id) {
1354     default:
1355       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1356       break;
1357   }
1358 }
1359 
1360 static void
gst_midi_parse_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1361 gst_midi_parse_get_property (GObject * object, guint prop_id,
1362     GValue * value, GParamSpec * pspec)
1363 {
1364   switch (prop_id) {
1365     default:
1366       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1367       break;
1368   }
1369 }
1370