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