2 * Copyright (C) <2016> Carlos Rafael Giani <dv at pseudoterminal dot org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
22 * SECTION:element-wildmididec
23 * @see_also: #GstWildmidiDec
25 * wildmididec decodes MIDI files.
27 * It uses [WildMidi](https://www.mindwerks.net/projects/wildmidi/) for this
28 * purpose. It can be autoplugged and therefore works with decodebin.
30 * ## Example launch line
33 * gst-launch-1.0 filesrc location=media/example.mid ! wildmididec ! audioconvert ! audioresample ! autoaudiosink
47 #include <glib/gstdio.h>
52 #define R_OK 4 /* Test for read permission */
59 #include "gstwildmididec.h"
62 GST_DEBUG_CATEGORY_STATIC (wildmididec_debug);
63 #define GST_CAT_DEFAULT wildmididec_debug
66 /* This is hardcoded because the sample rate is set once,
67 * globally, in WildMidi_Init() */
68 #define WILDMIDI_SAMPLE_RATE 44100
69 /* WildMidi always outputs stereo data */
70 #define WILDMIDI_NUM_CHANNELS 2
73 #define WILDMIDI_CFG "/etc/timidity.cfg"
76 #define DEFAULT_LOG_VOLUME_SCALE TRUE
77 #define DEFAULT_ENHANCED_RESAMPLING TRUE
78 #define DEFAULT_REVERB FALSE
79 #define DEFAULT_OUTPUT_BUFFER_SIZE 1024
85 PROP_LOG_VOLUME_SCALE,
86 PROP_ENHANCED_RESAMPLING,
88 PROP_OUTPUT_BUFFER_SIZE
93 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
96 GST_STATIC_CAPS ("audio/midi; audio/riff-midi")
99 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
102 GST_STATIC_CAPS ("audio/x-raw, "
103 "format = (string) " GST_AUDIO_NE (S16) ", "
104 "layout = (string) interleaved, "
105 "rate = (int) " G_STRINGIFY (WILDMIDI_SAMPLE_RATE) ", "
106 "channels = (int) " G_STRINGIFY (WILDMIDI_NUM_CHANNELS)
112 G_DEFINE_TYPE (GstWildmidiDec, gst_wildmidi_dec,
113 GST_TYPE_NONSTREAM_AUDIO_DECODER);
117 static void gst_wildmidi_dec_finalize (GObject * object);
119 static void gst_wildmidi_dec_set_property (GObject * object, guint prop_id,
120 const GValue * value, GParamSpec * pspec);
121 static void gst_wildmidi_dec_get_property (GObject * object, guint prop_id,
122 GValue * value, GParamSpec * pspec);
124 static gboolean gst_wildmidi_dec_seek (GstNonstreamAudioDecoder * dec,
125 GstClockTime * new_position);
126 static GstClockTime gst_wildmidi_dec_tell (GstNonstreamAudioDecoder * dec);
128 static gboolean gst_wildmidi_dec_load_from_buffer (GstNonstreamAudioDecoder *
129 dec, GstBuffer * source_data, guint initial_subsong,
130 GstNonstreamAudioSubsongMode initial_subsong_mode,
131 GstClockTime * initial_position,
132 GstNonstreamAudioOutputMode * initial_output_mode,
133 gint * initial_num_loops);
135 static guint gst_wildmidi_dec_get_current_subsong (GstNonstreamAudioDecoder *
138 static guint gst_wildmidi_dec_get_num_subsongs (GstNonstreamAudioDecoder * dec);
140 gst_wildmidi_dec_get_subsong_duration (GstNonstreamAudioDecoder * dec,
144 gst_wildmidi_dec_get_supported_output_modes (GstNonstreamAudioDecoder * dec);
145 static gboolean gst_wildmidi_dec_decode (GstNonstreamAudioDecoder * dec,
146 GstBuffer ** buffer, guint * num_samples);
148 static void gst_wildmidi_dec_update_options (GstWildmidiDec * wildmidi_dec);
152 static GMutex load_mutex;
153 static unsigned long init_refcount = 0;
154 static volatile gint wildmidi_initialized = 0;
158 gst_wildmidi_get_config_path (void)
160 /* This code is adapted from the original wildmidi
161 * gst-plugins-bad decoder element */
163 gchar *path = g_strdup (g_getenv ("WILDMIDI_CFG"));
166 ("trying configuration path \"%s\" from WILDMIDI_CFG environment variable",
167 GST_STR_NULL (path));
168 if (path && (g_access (path, R_OK) == -1)) {
175 g_build_path (G_DIR_SEPARATOR_S, g_get_home_dir (), ".wildmidirc",
177 GST_DEBUG ("trying configuration path \"%s\"", path);
178 if (path && (g_access (path, R_OK) == -1)) {
186 g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc",
187 "wildmidi.cfg", NULL);
188 GST_DEBUG ("trying configuration path \"%s\"", path);
189 if (path && (g_access (path, R_OK) == -1)) {
197 g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc", "wildmidi",
198 "wildmidi.cfg", NULL);
199 GST_DEBUG ("trying configuration path \"%s\"", path);
200 if (path && (g_access (path, R_OK) == -1)) {
207 path = g_strdup (WILDMIDI_CFG);
208 GST_DEBUG ("trying default configuration path \"%s\"", path);
209 if (path && (g_access (path, R_OK) == -1)) {
217 g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc",
218 "timidity.cfg", NULL);
219 GST_DEBUG ("trying TiMidity configuration path \"%s\"", path);
220 if (path && (g_access (path, R_OK) == -1)) {
228 g_build_path (G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S "etc", "timidity",
229 "timidity.cfg", NULL);
230 GST_DEBUG ("trying TiMidity configuration path \"%s\"", path);
231 if (path && (g_access (path, R_OK) == -1)) {
242 gst_wildmidi_init_library (void)
244 GST_DEBUG ("WildMidi init instance counter: %lu", init_refcount);
246 g_mutex_lock (&load_mutex);
248 if (init_refcount != 0) {
251 gchar *config_path = gst_wildmidi_get_config_path ();
252 if (config_path != NULL) {
253 int ret = WildMidi_Init (config_path, WILDMIDI_SAMPLE_RATE, 0);
254 g_free (config_path);
257 GST_DEBUG ("WildMidi initialized, version string: %s",
258 WildMidi_GetString (WM_GS_VERSION));
260 g_atomic_int_set (&wildmidi_initialized, 1);
262 GST_ERROR ("initializing WildMidi failed");
263 g_atomic_int_set (&wildmidi_initialized, 0);
266 GST_ERROR ("no config file, can't initialise");
267 g_atomic_int_set (&wildmidi_initialized, 0);
271 g_mutex_unlock (&load_mutex);
276 gst_wildmidi_shutdown_library (void)
278 GST_DEBUG ("WildMidi init instance counter: %lu", init_refcount);
280 g_mutex_lock (&load_mutex);
282 if (init_refcount != 0) {
284 if (init_refcount == 0) {
285 WildMidi_Shutdown ();
286 GST_DEBUG ("WildMidi shut down");
287 g_atomic_int_set (&wildmidi_initialized, 0);
291 g_mutex_unlock (&load_mutex);
297 gst_wildmidi_dec_class_init (GstWildmidiDecClass * klass)
299 GObjectClass *object_class;
300 GstElementClass *element_class;
301 GstNonstreamAudioDecoderClass *dec_class;
303 GST_DEBUG_CATEGORY_INIT (wildmididec_debug, "wildmididec", 0,
304 "WildMidi-based MIDI music decoder");
306 object_class = G_OBJECT_CLASS (klass);
307 element_class = GST_ELEMENT_CLASS (klass);
308 dec_class = GST_NONSTREAM_AUDIO_DECODER_CLASS (klass);
310 gst_element_class_add_pad_template (element_class,
311 gst_static_pad_template_get (&sink_template));
312 gst_element_class_add_pad_template (element_class,
313 gst_static_pad_template_get (&src_template));
315 object_class->finalize = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_finalize);
316 object_class->set_property =
317 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_set_property);
318 object_class->get_property =
319 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_property);
321 dec_class->tell = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_tell);
322 dec_class->seek = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_seek);
323 dec_class->load_from_buffer =
324 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_load_from_buffer);
325 dec_class->get_current_subsong =
326 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_current_subsong);
327 dec_class->get_num_subsongs =
328 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_num_subsongs);
329 dec_class->get_subsong_duration =
330 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_subsong_duration);
331 dec_class->get_supported_output_modes =
332 GST_DEBUG_FUNCPTR (gst_wildmidi_dec_get_supported_output_modes);
333 dec_class->decode = GST_DEBUG_FUNCPTR (gst_wildmidi_dec_decode);
335 gst_element_class_set_static_metadata (element_class,
336 "WildMidi-based MIDI music decoder",
337 "Codec/Decoder/Audio",
338 "Decodes MIDI music using WildMidi",
339 "Carlos Rafael Giani <dv@pseudoterminal.org>");
341 g_object_class_install_property (object_class,
342 PROP_LOG_VOLUME_SCALE,
343 g_param_spec_boolean ("log-volume-scale",
344 "Logarithmic volume scale",
345 "Use a logarithmic volume scale if set to TRUE, or a linear scale if set to FALSE",
346 DEFAULT_LOG_VOLUME_SCALE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
348 g_object_class_install_property (object_class,
349 PROP_ENHANCED_RESAMPLING,
350 g_param_spec_boolean ("enhanced-resampling",
351 "Enhanced resampling",
352 "Use enhanced resampling if set to TRUE, or linear interpolation if set to FALSE",
353 DEFAULT_ENHANCED_RESAMPLING,
354 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
356 g_object_class_install_property (object_class,
358 g_param_spec_boolean ("reverb",
360 "Whether or not to enable the WildMidi 8 reflection reverb engine to add more depth to the sound",
361 DEFAULT_REVERB, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
363 /* 2*2 => stereo output with S16 samples; the division ensures that no overflow can happen */
364 g_object_class_install_property (object_class,
365 PROP_OUTPUT_BUFFER_SIZE,
366 g_param_spec_uint ("output-buffer-size",
367 "Output buffer size",
368 "Size of each output buffer, in samples (actual size can be smaller than this during flush or EOS)",
369 1, G_MAXUINT / (2 * 2),
370 DEFAULT_OUTPUT_BUFFER_SIZE,
371 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
377 gst_wildmidi_dec_init (GstWildmidiDec * wildmidi_dec)
379 wildmidi_dec->song = NULL;
381 wildmidi_dec->log_volume_scale = DEFAULT_LOG_VOLUME_SCALE;
382 wildmidi_dec->enhanced_resampling = DEFAULT_ENHANCED_RESAMPLING;
383 wildmidi_dec->reverb = DEFAULT_REVERB;
384 wildmidi_dec->output_buffer_size = DEFAULT_OUTPUT_BUFFER_SIZE;
386 gst_wildmidi_init_library ();
391 gst_wildmidi_dec_finalize (GObject * object)
393 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (object);
395 if (wildmidi_dec->song != NULL)
396 WildMidi_Close (wildmidi_dec->song);
398 gst_wildmidi_shutdown_library ();
400 G_OBJECT_CLASS (gst_wildmidi_dec_parent_class)->finalize (object);
405 gst_wildmidi_dec_set_property (GObject * object, guint prop_id,
406 const GValue * value, GParamSpec * pspec)
408 GstWildmidiDec *wildmidi_dec;
410 wildmidi_dec = GST_WILDMIDI_DEC (object);
413 case PROP_LOG_VOLUME_SCALE:
414 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
415 wildmidi_dec->log_volume_scale = g_value_get_boolean (value);
416 gst_wildmidi_dec_update_options (wildmidi_dec);
417 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
420 case PROP_ENHANCED_RESAMPLING:
421 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
422 wildmidi_dec->enhanced_resampling = g_value_get_boolean (value);
423 gst_wildmidi_dec_update_options (wildmidi_dec);
424 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
428 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
429 wildmidi_dec->reverb = g_value_get_boolean (value);
430 gst_wildmidi_dec_update_options (wildmidi_dec);
431 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
434 case PROP_OUTPUT_BUFFER_SIZE:
435 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
436 wildmidi_dec->output_buffer_size = g_value_get_uint (value);
437 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
441 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
448 gst_wildmidi_dec_get_property (GObject * object, guint prop_id, GValue * value,
451 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (object);
454 case PROP_LOG_VOLUME_SCALE:
455 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
456 g_value_set_boolean (value, wildmidi_dec->log_volume_scale);
457 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
460 case PROP_ENHANCED_RESAMPLING:
461 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
462 g_value_set_boolean (value, wildmidi_dec->enhanced_resampling);
463 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
467 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
468 g_value_set_boolean (value, wildmidi_dec->reverb);
469 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
472 case PROP_OUTPUT_BUFFER_SIZE:
473 GST_NONSTREAM_AUDIO_DECODER_LOCK_MUTEX (object);
474 g_value_set_uint (value, wildmidi_dec->output_buffer_size);
475 GST_NONSTREAM_AUDIO_DECODER_UNLOCK_MUTEX (object);
479 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
486 gst_wildmidi_dec_seek (GstNonstreamAudioDecoder * dec,
487 GstClockTime * new_position)
489 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
490 unsigned long int sample_pos =
491 gst_util_uint64_scale_int (*new_position, WILDMIDI_SAMPLE_RATE,
494 if (G_UNLIKELY (wildmidi_dec->song == NULL))
497 WildMidi_FastSeek (wildmidi_dec->song, &sample_pos);
500 gst_util_uint64_scale_int (sample_pos, GST_SECOND, WILDMIDI_SAMPLE_RATE);
506 gst_wildmidi_dec_tell (GstNonstreamAudioDecoder * dec)
508 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
509 struct _WM_Info *info;
511 if (G_UNLIKELY (wildmidi_dec->song == NULL))
512 return GST_CLOCK_TIME_NONE;
514 info = WildMidi_GetInfo (wildmidi_dec->song);
515 return gst_util_uint64_scale_int (info->current_sample, GST_SECOND,
516 WILDMIDI_SAMPLE_RATE);
521 gst_wildmidi_dec_load_from_buffer (GstNonstreamAudioDecoder * dec,
522 GstBuffer * source_data, G_GNUC_UNUSED guint initial_subsong,
523 G_GNUC_UNUSED GstNonstreamAudioSubsongMode initial_subsong_mode,
524 GstClockTime * initial_position,
525 GstNonstreamAudioOutputMode * initial_output_mode,
526 G_GNUC_UNUSED gint * initial_num_loops)
528 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
529 GstMapInfo buffer_map;
532 if (g_atomic_int_get (&wildmidi_initialized) == 0) {
533 GST_ERROR_OBJECT (wildmidi_dec,
534 "Could not start loading: WildMidi is not initialized");
539 /* Set output format */
540 if (!gst_nonstream_audio_decoder_set_output_format_simple (dec,
541 WILDMIDI_SAMPLE_RATE, GST_AUDIO_FORMAT_S16, WILDMIDI_NUM_CHANNELS))
546 gst_buffer_map (source_data, &buffer_map, GST_MAP_READ);
547 wildmidi_dec->song = WildMidi_OpenBuffer (buffer_map.data, buffer_map.size);
548 gst_buffer_unmap (source_data, &buffer_map);
550 if (wildmidi_dec->song == NULL) {
551 GST_ERROR_OBJECT (wildmidi_dec, "Could not load MIDI tune");
555 gst_wildmidi_dec_update_options (wildmidi_dec);
558 /* Seek to initial position */
559 if (*initial_position != 0) {
560 unsigned long int sample_pos =
561 gst_util_uint64_scale_int (*initial_position, WILDMIDI_SAMPLE_RATE,
563 WildMidi_FastSeek (wildmidi_dec->song, &sample_pos);
565 gst_util_uint64_scale_int (sample_pos, GST_SECOND,
566 WILDMIDI_SAMPLE_RATE);
570 /* LOOPING output mode is not supported */
571 *initial_output_mode = GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY;
579 gst_wildmidi_dec_get_current_subsong (G_GNUC_UNUSED GstNonstreamAudioDecoder *
587 gst_wildmidi_dec_get_num_subsongs (G_GNUC_UNUSED GstNonstreamAudioDecoder * dec)
594 gst_wildmidi_dec_get_subsong_duration (GstNonstreamAudioDecoder * dec,
595 G_GNUC_UNUSED guint subsong)
597 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
598 struct _WM_Info *info;
600 if (G_UNLIKELY (wildmidi_dec->song == NULL))
601 return GST_CLOCK_TIME_NONE;
603 info = WildMidi_GetInfo (wildmidi_dec->song);
604 return gst_util_uint64_scale_int (info->approx_total_samples, GST_SECOND,
605 WILDMIDI_SAMPLE_RATE);
610 gst_wildmidi_dec_get_supported_output_modes (G_GNUC_UNUSED
611 GstNonstreamAudioDecoder * dec)
613 return 1u << GST_NONSTREAM_AUDIO_OUTPUT_MODE_STEADY;
618 gst_wildmidi_dec_decode (GstNonstreamAudioDecoder * dec, GstBuffer ** buffer,
621 GstWildmidiDec *wildmidi_dec = GST_WILDMIDI_DEC (dec);
625 int decoded_size_in_bytes;
627 if (G_UNLIKELY (wildmidi_dec->song == NULL))
630 /* Allocate output buffer
631 * Multiply by 2 to accommodate for the sample size (16 bit = 2 byte) */
632 outbuf_size = wildmidi_dec->output_buffer_size * 2 * WILDMIDI_NUM_CHANNELS;
634 gst_nonstream_audio_decoder_allocate_output_buffer (dec, outbuf_size);
635 if (G_UNLIKELY (outbuf == NULL))
638 /* The actual decoding */
639 gst_buffer_map (outbuf, &info, GST_MAP_WRITE);
640 decoded_size_in_bytes =
641 WildMidi_GetOutput (wildmidi_dec->song, (int8_t *) (info.data),
643 gst_buffer_unmap (outbuf, &info);
645 if (decoded_size_in_bytes == 0) {
646 gst_buffer_unref (outbuf);
651 *num_samples = decoded_size_in_bytes / 2 / WILDMIDI_NUM_CHANNELS;
658 gst_wildmidi_dec_update_options (GstWildmidiDec * wildmidi_dec)
660 unsigned short int options = 0;
662 if (wildmidi_dec->song == NULL)
665 if (wildmidi_dec->log_volume_scale)
666 options |= WM_MO_LOG_VOLUME;
667 if (wildmidi_dec->enhanced_resampling)
668 options |= WM_MO_ENHANCED_RESAMPLING;
669 if (wildmidi_dec->reverb)
670 options |= WM_MO_REVERB;
672 WildMidi_SetOption (wildmidi_dec->song,
673 WM_MO_LOG_VOLUME | WM_MO_ENHANCED_RESAMPLING | WM_MO_REVERB, options);
678 plugin_init (GstPlugin * plugin)
680 return gst_element_register (plugin, "wildmididec", GST_RANK_MARGINAL,
681 gst_wildmidi_dec_get_type ());
684 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
687 "WildMidi-based MIDI playback plugin",
688 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)