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