1 /*
2 * gstfluiddec - fluiddec 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-fluiddec
24 * @title: fluiddec
25 * @see_also: timidity, wildmidi
26 *
27 * This element renders midi-events as audio streams using
28 * [Fluidsynth](http://fluidsynth.sourceforge.net/).
29 * It offers better sound quality compared to the timidity or wildmidi element.
30 *
31 * ## Example pipeline
32 * |[
33 * gst-launch-1.0 filesrc location=song.mid ! midiparse ! fluiddec ! pulsesink
34 * ]| This example pipeline will parse the midi and render to raw audio which is
35 * played via pulseaudio.
36 *
37 */
38
39 #ifdef HAVE_CONFIG_H
40 # include <config.h>
41 #endif
42
43 #define FLUID_DEC_RATE 44100
44 #define FLUID_DEC_BPS (4 * 2)
45
46 #include <gst/gst.h>
47 #include <string.h>
48 #include <glib.h>
49 #include <glib/gstdio.h>
50 #include <stdlib.h>
51 #include <unistd.h>
52
53 #include <gst/audio/audio.h>
54
55 #include "gstfluiddec.h"
56
57 #define GST_HAVE_FLUIDSYNTH_VERSION(major,minor,micro) \
58 (FLUIDSYNTH_VERSION_MAJOR > (major) || \
59 (FLUIDSYNTH_VERSION_MAJOR == (major) && FLUIDSYNTH_VERSION_MINOR > (minor)) || \
60 (FLUIDSYNTH_VERSION_MAJOR == (major) && FLUIDSYNTH_VERSION_MINOR == (minor) && FLUIDSYNTH_VERSION_MICRO >= (micro)))
61
62 GST_DEBUG_CATEGORY_STATIC (gst_fluid_dec_debug);
63 #define GST_CAT_DEFAULT gst_fluid_dec_debug
64
65 enum
66 {
67 /* FILL ME */
68 LAST_SIGNAL
69 };
70
71 #define DEFAULT_SOUNDFONT NULL
72 #define DEFAULT_SYNTH_CHORUS TRUE
73 #define DEFAULT_SYNTH_REVERB TRUE
74 #define DEFAULT_SYNTH_GAIN 0.2
75 #define DEFAULT_SYNTH_POLYPHONY 256
76
77 enum
78 {
79 PROP_0,
80 PROP_SOUNDFONT,
81 PROP_SYNTH_CHORUS,
82 PROP_SYNTH_REVERB,
83 PROP_SYNTH_GAIN,
84 PROP_SYNTH_POLYPHONY
85 };
86
87 static void gst_fluid_dec_finalize (GObject * object);
88
89 static gboolean gst_fluid_dec_sink_event (GstPad * pad, GstObject * parent,
90 GstEvent * event);
91
92 static GstStateChangeReturn gst_fluid_dec_change_state (GstElement * element,
93 GstStateChange transition);
94
95 static GstFlowReturn gst_fluid_dec_chain (GstPad * sinkpad, GstObject * parent,
96 GstBuffer * buffer);
97
98 static void gst_fluid_dec_set_property (GObject * object, guint prop_id,
99 const GValue * value, GParamSpec * pspec);
100 static void gst_fluid_dec_get_property (GObject * object, guint prop_id,
101 GValue * value, GParamSpec * pspec);
102 static gboolean fluiddec_element_init (GstPlugin * plugin);
103
104 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
105 GST_PAD_SINK,
106 GST_PAD_ALWAYS,
107 GST_STATIC_CAPS ("audio/x-midi-event")
108 );
109
110 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
111 GST_PAD_SRC,
112 GST_PAD_ALWAYS,
113 GST_STATIC_CAPS ("audio/x-raw, "
114 "format = (string) " GST_AUDIO_NE (F32) ", "
115 "rate = (int) " G_STRINGIFY (FLUID_DEC_RATE) ", "
116 "channels = (int) 2, " "layout = (string) interleaved"));
117
118 #define parent_class gst_fluid_dec_parent_class
119 G_DEFINE_TYPE (GstFluidDec, gst_fluid_dec, GST_TYPE_ELEMENT);
120 GST_ELEMENT_REGISTER_DEFINE_CUSTOM (fluiddec, fluiddec_element_init);
121
122 /* initialize the plugin's class */
123 static void
gst_fluid_dec_class_init(GstFluidDecClass * klass)124 gst_fluid_dec_class_init (GstFluidDecClass * 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_fluid_dec_finalize;
133 gobject_class->set_property = gst_fluid_dec_set_property;
134 gobject_class->get_property = gst_fluid_dec_get_property;
135
136 g_object_class_install_property (gobject_class, PROP_SOUNDFONT,
137 g_param_spec_string ("soundfont",
138 "Soundfont", "the filename of a soundfont (NULL for default)",
139 DEFAULT_SOUNDFONT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
140
141 g_object_class_install_property (gobject_class, PROP_SYNTH_CHORUS,
142 g_param_spec_boolean ("synth-chorus",
143 "Synth Chorus", "Turn the chorus on or off",
144 DEFAULT_SYNTH_CHORUS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
145
146 g_object_class_install_property (gobject_class, PROP_SYNTH_REVERB,
147 g_param_spec_boolean ("synth-reverb",
148 "Synth Reverb", "Turn the reverb on or off",
149 DEFAULT_SYNTH_REVERB, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
150
151 g_object_class_install_property (gobject_class, PROP_SYNTH_GAIN,
152 g_param_spec_double ("synth-gain",
153 "Synth Gain", "Set the master gain", 0.0, 10.0,
154 DEFAULT_SYNTH_GAIN, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
155
156 g_object_class_install_property (gobject_class, PROP_SYNTH_POLYPHONY,
157 g_param_spec_int ("synth-polyphony",
158 "Synth Polyphony", "The number of simultaneous voices", 1, 65535,
159 DEFAULT_SYNTH_POLYPHONY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
160
161 gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
162 gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
163
164 gst_element_class_set_static_metadata (gstelement_class, "Fluidsynth",
165 "Codec/Decoder/Audio",
166 "Midi Synthesizer Element", "Wim Taymans <wim.taymans@gmail.com>");
167
168 gstelement_class->change_state = gst_fluid_dec_change_state;
169 }
170
171 /* initialize the new element
172 * instantiate pads and add them to element
173 * set functions
174 * initialize structure
175 */
176 static void
gst_fluid_dec_init(GstFluidDec * filter)177 gst_fluid_dec_init (GstFluidDec * filter)
178 {
179 filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
180 gst_pad_set_event_function (filter->sinkpad, gst_fluid_dec_sink_event);
181 gst_pad_set_chain_function (filter->sinkpad, gst_fluid_dec_chain);
182 gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
183
184 filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
185 gst_pad_use_fixed_caps (filter->srcpad);
186 gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
187
188 filter->soundfont = g_strdup (DEFAULT_SOUNDFONT);
189 filter->synth_chorus = DEFAULT_SYNTH_CHORUS;
190 filter->synth_reverb = DEFAULT_SYNTH_REVERB;
191 filter->synth_gain = DEFAULT_SYNTH_GAIN;
192 filter->synth_polyphony = DEFAULT_SYNTH_POLYPHONY;
193
194 filter->settings = new_fluid_settings ();
195
196 /* http://www.fluidsynth.org/api/fluidsettings.xml */
197 fluid_settings_setnum (filter->settings, "synth.sample-rate", FLUID_DEC_RATE);
198
199 /* FIXME: Initialize after caps negotiation so we can support more rates */
200 filter->synth = new_fluid_synth (filter->settings);
201 filter->sf = -1;
202
203 fluid_synth_set_chorus_on (filter->synth, filter->synth_chorus);
204 fluid_synth_set_reverb_on (filter->synth, filter->synth_reverb);
205 fluid_synth_set_gain (filter->synth, filter->synth_gain);
206 fluid_synth_set_polyphony (filter->synth, filter->synth_polyphony);
207 }
208
209 static void
gst_fluid_dec_finalize(GObject * object)210 gst_fluid_dec_finalize (GObject * object)
211 {
212 GstFluidDec *fluiddec = GST_FLUID_DEC (object);
213
214 delete_fluid_synth (fluiddec->synth);
215 delete_fluid_settings (fluiddec->settings);
216 g_free (fluiddec->soundfont);
217
218 G_OBJECT_CLASS (parent_class)->finalize (object);
219 }
220
221 #if 0
222 static GstBuffer *
223 gst_fluid_dec_clip_buffer (GstFluidDec * fluiddec, GstBuffer * buffer)
224 {
225 guint64 start, stop;
226 guint64 new_start, new_stop;
227 gint64 offset, length;
228
229 /* clipping disabled for now */
230 return buffer;
231
232 start = GST_BUFFER_OFFSET (buffer);
233 stop = GST_BUFFER_OFFSET_END (buffer);
234
235 if (!gst_segment_clip (&fluiddec->segment, GST_FORMAT_DEFAULT,
236 start, stop, &new_start, &new_stop)) {
237 gst_buffer_unref (buffer);
238 return NULL;
239 }
240
241 if (start == new_start && stop == new_stop)
242 return buffer;
243
244 offset = new_start - start;
245 length = new_stop - new_start;
246
247 buffer = gst_buffer_make_writable (buffer);
248 gst_buffer_resize (buffer, offset, length);
249
250 GST_BUFFER_OFFSET (buffer) = new_start;
251 GST_BUFFER_OFFSET_END (buffer) = new_stop;
252 GST_BUFFER_TIMESTAMP (buffer) =
253 gst_util_uint64_scale_int (new_start, GST_SECOND, FLUID_DEC_RATE);
254 GST_BUFFER_DURATION (buffer) =
255 gst_util_uint64_scale_int (new_stop, GST_SECOND, FLUID_DEC_RATE) -
256 GST_BUFFER_TIMESTAMP (buffer);
257
258 return buffer;
259 }
260 #endif
261
262 static void
gst_fluid_dec_reset(GstFluidDec * fluiddec)263 gst_fluid_dec_reset (GstFluidDec * fluiddec)
264 {
265 fluid_synth_system_reset (fluiddec->synth);
266 fluiddec->last_pts = GST_CLOCK_TIME_NONE;
267 }
268
269 static gboolean
gst_fluid_dec_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)270 gst_fluid_dec_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
271 {
272 gboolean res;
273 GstFluidDec *fluiddec = GST_FLUID_DEC (parent);
274
275 GST_DEBUG_OBJECT (pad, "%s event received", GST_EVENT_TYPE_NAME (event));
276
277 switch (GST_EVENT_TYPE (event)) {
278 case GST_EVENT_CAPS:
279 {
280 GstCaps *caps;
281
282 caps = gst_caps_new_simple ("audio/x-raw",
283 "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
284 "rate", G_TYPE_INT, FLUID_DEC_RATE,
285 "channels", G_TYPE_INT, 2,
286 "layout", G_TYPE_STRING, "interleaved", NULL);
287
288 res = gst_pad_push_event (fluiddec->srcpad, gst_event_new_caps (caps));
289 gst_caps_unref (caps);
290 gst_event_unref (event);
291 break;
292 }
293 case GST_EVENT_SEGMENT:
294 gst_event_copy_segment (event, &fluiddec->segment);
295 GST_DEBUG_OBJECT (fluiddec, "configured segment %" GST_SEGMENT_FORMAT,
296 &fluiddec->segment);
297 res = gst_pad_event_default (pad, parent, event);
298 break;
299 case GST_EVENT_FLUSH_STOP:
300 gst_fluid_dec_reset (fluiddec);
301 res = gst_pad_event_default (pad, parent, event);
302 break;
303 case GST_EVENT_EOS:
304 /* FIXME, push last samples */
305 res = gst_pad_event_default (pad, parent, event);
306 break;
307 default:
308 res = gst_pad_event_default (pad, parent, event);
309 break;
310 }
311 return res;
312 }
313
314 static GstFlowReturn
produce_samples(GstFluidDec * fluiddec,GstClockTime pts,guint64 sample)315 produce_samples (GstFluidDec * fluiddec, GstClockTime pts, guint64 sample)
316 {
317 GstClockTime duration, timestamp;
318 guint64 samples, offset;
319 GstMapInfo info;
320 GstBuffer *outbuf;
321
322 samples = sample - fluiddec->last_sample;
323 duration = pts - fluiddec->last_pts;
324 offset = fluiddec->last_sample;
325 timestamp = fluiddec->last_pts;
326
327 fluiddec->last_pts = pts;
328 fluiddec->last_sample = sample;
329
330 if (samples == 0)
331 return GST_FLOW_OK;
332
333 GST_DEBUG_OBJECT (fluiddec, "duration %" GST_TIME_FORMAT
334 ", samples %" G_GUINT64_FORMAT, GST_TIME_ARGS (duration), samples);
335
336 outbuf = gst_buffer_new_allocate (NULL, samples * FLUID_DEC_BPS, NULL);
337
338 gst_buffer_map (outbuf, &info, GST_MAP_WRITE);
339 fluid_synth_write_float (fluiddec->synth, samples, info.data, 0, 2,
340 info.data, 1, 2);
341 gst_buffer_unmap (outbuf, &info);
342
343 GST_BUFFER_DTS (outbuf) = timestamp;
344 GST_BUFFER_PTS (outbuf) = timestamp;
345 GST_BUFFER_DURATION (outbuf) = duration;
346 GST_BUFFER_OFFSET (outbuf) = offset;
347 GST_BUFFER_OFFSET_END (outbuf) = offset + samples;
348
349 if (fluiddec->discont) {
350 GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
351 fluiddec->discont = FALSE;
352 }
353
354 return gst_pad_push (fluiddec->srcpad, outbuf);
355 }
356
357 static void
handle_buffer(GstFluidDec * fluiddec,GstBuffer * buffer)358 handle_buffer (GstFluidDec * fluiddec, GstBuffer * buffer)
359 {
360 GstMapInfo info;
361 guint8 event;
362
363 gst_buffer_map (buffer, &info, GST_MAP_READ);
364
365 if (info.size == 0)
366 goto done;
367
368 event = info.data[0];
369
370 switch (event & 0xf0) {
371 case 0xf0:
372 switch (event) {
373 case 0xff:
374 GST_DEBUG_OBJECT (fluiddec, "system reset");
375 fluid_synth_system_reset (fluiddec->synth);
376 break;
377 case 0xf0:
378 case 0xf7:
379 GST_DEBUG_OBJECT (fluiddec, "sysex 0x%02x", event);
380 GST_MEMDUMP_OBJECT (fluiddec, "bytes ", info.data + 1, info.size - 1);
381 fluid_synth_sysex (fluiddec->synth, (char *) info.data + 1,
382 info.size - 1, NULL, NULL, NULL, 0);
383
384 break;
385 case 0xf9:
386 GST_LOG_OBJECT (fluiddec, "midi tick");
387 break;
388 default:
389 GST_WARNING_OBJECT (fluiddec, "unhandled event 0x%02x", event);
390 break;
391 }
392 break;
393 default:
394 {
395 guint8 channel, p1, p2;
396
397 channel = event & 0x0f;
398
399 p1 = info.size > 1 ? info.data[1] & 0x7f : 0;
400 p2 = info.size > 2 ? info.data[2] & 0x7f : 0;
401
402 GST_DEBUG_OBJECT (fluiddec, "event 0x%02x channel %d, 0x%02x 0x%02x",
403 event, channel, p1, p2);
404
405 switch (event & 0xf0) {
406 case 0x80:
407 fluid_synth_noteoff (fluiddec->synth, channel, p1);
408 break;
409 case 0x90:
410 fluid_synth_noteon (fluiddec->synth, channel, p1, p2);
411 break;
412 case 0xA0:
413 /* aftertouch */
414 break;
415 case 0xB0:
416 fluid_synth_cc (fluiddec->synth, channel, p1, p2);
417 break;
418 case 0xC0:
419 fluid_synth_program_change (fluiddec->synth, channel, p1);
420 break;
421 case 0xD0:
422 fluid_synth_channel_pressure (fluiddec->synth, channel, p1);
423 break;
424 case 0xE0:
425 fluid_synth_pitch_bend (fluiddec->synth, channel, (p2 << 7) | p1);
426 break;
427 default:
428 break;
429 }
430 break;
431 }
432 }
433
434 done:
435
436 gst_buffer_unmap (buffer, &info);
437 }
438
439 static GstFlowReturn
gst_fluid_dec_chain(GstPad * sinkpad,GstObject * parent,GstBuffer * buffer)440 gst_fluid_dec_chain (GstPad * sinkpad, GstObject * parent, GstBuffer * buffer)
441 {
442 GstFlowReturn res = GST_FLOW_OK;
443 GstFluidDec *fluiddec;
444 GstClockTime pts;
445
446 fluiddec = GST_FLUID_DEC (parent);
447
448 if (GST_BUFFER_IS_DISCONT (buffer)) {
449 fluiddec->discont = TRUE;
450 }
451
452 pts = GST_BUFFER_PTS (buffer);
453
454 if (pts != GST_CLOCK_TIME_NONE) {
455 guint64 sample =
456 gst_util_uint64_scale_int (pts, FLUID_DEC_RATE, GST_SECOND);
457
458 if (fluiddec->last_pts == GST_CLOCK_TIME_NONE) {
459 fluiddec->last_pts = pts;
460 fluiddec->last_sample = sample;
461 } else if (fluiddec->last_pts < pts) {
462 /* generate samples for the elapsed time */
463 res = produce_samples (fluiddec, pts, sample);
464 }
465 }
466
467 if (res == GST_FLOW_OK) {
468 handle_buffer (fluiddec, buffer);
469 }
470 gst_buffer_unref (buffer);
471
472 return res;
473 }
474
475 static gboolean
gst_fluid_dec_open(GstFluidDec * fluiddec)476 gst_fluid_dec_open (GstFluidDec * fluiddec)
477 {
478 GDir *dir;
479 GError *error = NULL;
480 const gchar *const *sharedirs;
481
482 if (fluiddec->sf != -1)
483 return TRUE;
484
485 if (fluiddec->soundfont) {
486 GST_DEBUG_OBJECT (fluiddec, "loading soundfont file %s",
487 fluiddec->soundfont);
488
489 fluiddec->sf = fluid_synth_sfload (fluiddec->synth, fluiddec->soundfont, 1);
490 if (fluiddec->sf == -1)
491 goto load_failed;
492
493 GST_DEBUG_OBJECT (fluiddec, "loaded soundfont file %s",
494 fluiddec->soundfont);
495 } else {
496 gint i, j;
497 /* ubuntu/debian in sounds/sf[23], fedora in soundfonts */
498 static const gchar *paths[] =
499 { "sounds/sf3/", "sounds/sf2/", "soundfonts/", NULL };
500
501 sharedirs = g_get_system_data_dirs ();
502
503 for (i = 0; sharedirs[i]; i++) {
504 for (j = 0; paths[j]; j++) {
505 gchar *soundfont_path = g_build_path ("/", sharedirs[i], paths[j],
506 NULL);
507 GST_DEBUG_OBJECT (fluiddec, "Trying to list contents of a %s directory",
508 soundfont_path);
509 error = NULL;
510 dir = g_dir_open (soundfont_path, 0, &error);
511 if (dir == NULL) {
512 GST_DEBUG_OBJECT (fluiddec,
513 "Can't open a potential soundfont directory %s: %s",
514 soundfont_path, error->message);
515 g_free (soundfont_path);
516 g_error_free (error);
517 continue;
518 }
519
520 while (TRUE) {
521 const gchar *name;
522 gchar *filename;
523
524 if ((name = g_dir_read_name (dir)) == NULL)
525 break;
526
527 filename = g_build_filename (soundfont_path, name, NULL);
528
529 GST_DEBUG_OBJECT (fluiddec, "loading soundfont file %s", filename);
530 fluiddec->sf = fluid_synth_sfload (fluiddec->synth, filename, 1);
531 if (fluiddec->sf != -1) {
532 GST_DEBUG_OBJECT (fluiddec, "loaded soundfont file %s", filename);
533 g_free (filename);
534 g_dir_close (dir);
535 g_free (soundfont_path);
536 goto done;
537 }
538 GST_DEBUG_OBJECT (fluiddec, "could not load soundfont file %s",
539 filename);
540 g_free (filename);
541 }
542 g_dir_close (dir);
543 g_free (soundfont_path);
544 }
545 }
546 if (fluiddec->sf == -1) {
547 goto no_soundfont;
548 }
549 }
550 done:
551 return TRUE;
552
553 /* ERRORS */
554 load_failed:
555 {
556 GST_ELEMENT_ERROR (fluiddec, RESOURCE, OPEN_READ,
557 ("Can't open soundfont %s", fluiddec->soundfont),
558 ("failed to open soundfont file %s for reading", fluiddec->soundfont));
559 return FALSE;
560 }
561 no_soundfont:
562 {
563 GST_ELEMENT_ERROR (fluiddec, RESOURCE, OPEN_READ,
564 ("Can't find a soundfont file in subdirectories of XDG_DATA_DIRS paths"),
565 ("no usable soundfont files found in subdirectories of XDG_DATA_DIRS"));
566 return FALSE;
567 }
568 }
569
570 static gboolean
gst_fluid_dec_close(GstFluidDec * fluiddec)571 gst_fluid_dec_close (GstFluidDec * fluiddec)
572 {
573 if (fluiddec->sf) {
574 fluid_synth_sfunload (fluiddec->synth, fluiddec->sf, 1);
575 fluiddec->sf = -1;
576 }
577 return TRUE;
578 }
579
580 static GstStateChangeReturn
gst_fluid_dec_change_state(GstElement * element,GstStateChange transition)581 gst_fluid_dec_change_state (GstElement * element, GstStateChange transition)
582 {
583 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
584 GstFluidDec *fluiddec = GST_FLUID_DEC (element);
585
586 switch (transition) {
587 case GST_STATE_CHANGE_NULL_TO_READY:
588 if (!gst_fluid_dec_open (fluiddec))
589 goto open_failed;
590 break;
591 case GST_STATE_CHANGE_READY_TO_PAUSED:
592 gst_fluid_dec_reset (fluiddec);
593 break;
594 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
595 break;
596 default:
597 break;
598 }
599
600 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
601
602 switch (transition) {
603 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
604 break;
605 case GST_STATE_CHANGE_PAUSED_TO_READY:
606 break;
607 case GST_STATE_CHANGE_READY_TO_NULL:
608 gst_fluid_dec_close (fluiddec);
609 break;
610 default:
611 break;
612 }
613
614 return ret;
615
616 /* ERRORS */
617 open_failed:
618 {
619 GST_ERROR_OBJECT (fluiddec, "could not open");
620 return GST_STATE_CHANGE_FAILURE;
621 }
622 }
623
624 static void
gst_fluid_dec_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)625 gst_fluid_dec_set_property (GObject * object, guint prop_id,
626 const GValue * value, GParamSpec * pspec)
627 {
628 GstFluidDec *fluiddec = GST_FLUID_DEC (object);
629
630 switch (prop_id) {
631 case PROP_SOUNDFONT:
632 g_free (fluiddec->soundfont);
633 fluiddec->soundfont = g_value_dup_string (value);
634 break;
635 case PROP_SYNTH_CHORUS:
636 fluiddec->synth_chorus = g_value_get_boolean (value);
637 fluid_synth_set_chorus_on (fluiddec->synth, fluiddec->synth_chorus);
638 break;
639 case PROP_SYNTH_REVERB:
640 fluiddec->synth_reverb = g_value_get_boolean (value);
641 fluid_synth_set_reverb_on (fluiddec->synth, fluiddec->synth_reverb);
642 break;
643 case PROP_SYNTH_GAIN:
644 fluiddec->synth_gain = g_value_get_double (value);
645 fluid_synth_set_gain (fluiddec->synth, fluiddec->synth_gain);
646 break;
647 case PROP_SYNTH_POLYPHONY:
648 fluiddec->synth_polyphony = g_value_get_int (value);
649 fluid_synth_set_polyphony (fluiddec->synth, fluiddec->synth_polyphony);
650 break;
651 default:
652 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
653 break;
654 }
655 }
656
657 static void
gst_fluid_dec_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)658 gst_fluid_dec_get_property (GObject * object, guint prop_id,
659 GValue * value, GParamSpec * pspec)
660 {
661 GstFluidDec *fluiddec = GST_FLUID_DEC (object);
662
663 switch (prop_id) {
664 case PROP_SOUNDFONT:
665 g_value_set_string (value, fluiddec->soundfont);
666 break;
667 case PROP_SYNTH_CHORUS:
668 g_value_set_boolean (value, fluiddec->synth_chorus);
669 break;
670 case PROP_SYNTH_REVERB:
671 g_value_set_boolean (value, fluiddec->synth_reverb);
672 break;
673 case PROP_SYNTH_GAIN:
674 g_value_set_double (value, fluiddec->synth_gain);
675 break;
676 case PROP_SYNTH_POLYPHONY:
677 g_value_set_int (value, fluiddec->synth_polyphony);
678 break;
679 default:
680 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
681 break;
682 }
683 }
684
685 /* fluid_synth log handler */
686 static void
gst_fluid_synth_error_log_function(int level,const char * message,void * data)687 gst_fluid_synth_error_log_function (int level, const char *message, void *data)
688 {
689 GST_ERROR ("%s", message);
690 }
691
692 static void
gst_fluid_synth_warning_log_function(int level,const char * message,void * data)693 gst_fluid_synth_warning_log_function (int level, const char *message,
694 void *data)
695 {
696 GST_WARNING ("%s", message);
697 }
698
699 static void
gst_fluid_synth_info_log_function(int level,const char * message,void * data)700 gst_fluid_synth_info_log_function (int level, const char *message, void *data)
701 {
702 GST_INFO ("%s", message);
703 }
704
705 static void
gst_fluid_synth_debug_log_function(int level,const char * message,void * data)706 gst_fluid_synth_debug_log_function (int level, const char *message, void *data)
707 {
708 GST_DEBUG ("%s", message);
709 }
710
711 static gboolean
fluiddec_element_init(GstPlugin * plugin)712 fluiddec_element_init (GstPlugin * plugin)
713 {
714 GST_DEBUG_CATEGORY_INIT (gst_fluid_dec_debug, "fluiddec",
715 0, "Fluidsynth MIDI decoder plugin");
716
717 /* We modify FluidSynth's global state here; let's hope nobody tries to use
718 * it natively alongside this plugin. */
719
720 #ifndef GST_DISABLE_GST_DEBUG
721 fluid_set_log_function (FLUID_PANIC,
722 (fluid_log_function_t) gst_fluid_synth_error_log_function, NULL);
723 fluid_set_log_function (FLUID_ERR,
724 (fluid_log_function_t) gst_fluid_synth_warning_log_function, NULL);
725 fluid_set_log_function (FLUID_WARN,
726 (fluid_log_function_t) gst_fluid_synth_warning_log_function, NULL);
727 fluid_set_log_function (FLUID_INFO,
728 (fluid_log_function_t) gst_fluid_synth_info_log_function, NULL);
729 fluid_set_log_function (FLUID_DBG,
730 (fluid_log_function_t) gst_fluid_synth_debug_log_function, NULL);
731 #else
732 fluid_set_log_function (FLUID_PANIC, NULL, NULL);
733 fluid_set_log_function (FLUID_ERR, NULL, NULL);
734 fluid_set_log_function (FLUID_WARN, NULL, NULL);
735 fluid_set_log_function (FLUID_INFO, NULL, NULL);
736 fluid_set_log_function (FLUID_DBG, NULL, NULL);
737 #endif
738
739 #if GST_HAVE_FLUIDSYNTH_VERSION(1, 1, 9)
740 {
741 /* Disable all audio drivers so new_fluid_settings() does not probe them.
742 * This can crash if FluidSynth is already in use. */
743 const char *empty[] = { NULL };
744 if (fluid_audio_driver_register (empty) != FLUID_OK) {
745 GST_WARNING ("Failed to clear audio drivers");
746 }
747 }
748 #endif
749 return gst_element_register (plugin, "fluiddec",
750 GST_RANK_SECONDARY, GST_TYPE_FLUID_DEC);
751 }
752
753 static gboolean
plugin_init(GstPlugin * plugin)754 plugin_init (GstPlugin * plugin)
755 {
756 return GST_ELEMENT_REGISTER (fluiddec, plugin);
757 }
758
759 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
760 GST_VERSION_MINOR,
761 fluidsynthmidi,
762 "Fluidsynth MIDI Plugin",
763 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
764