Bug Summary

File:build-analysis/../src/plugins/fluidsynth/fluidsynth.c
Warning:line 153, column 2
Value stored to 'cfgv' is never read

Annotated Source Code

1/* XMMS2 - X Music Multiplexer System
2 * Copyright (C) 2003-2017 XMMS2 Team
3 *
4 * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 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 * Lesser General Public License for more details.
15 *
16 * ---------------------------------
17 *
18 * FluidSynth plugin for XMMS2
19 *
20 * audio/rawmidi format (this plugin's input data)
21 *
22 * This format is standard MIDI track data (as you would find in an MTrk chunk
23 * in a .mid file) but with no headers. The initial tempo is set in the
24 * track metadata.
25 *
26 * This allows support for streaming MIDI data, at the cost of only supporting
27 * a single track. If you want to play multitrack MIDI data, convert it
28 * to audio/miditracks instead, and the midsquash plugin will take care of
29 * converting that to a single track.
30 *
31 * Note that meta-event 0x2F is normally used to end a track. This event will
32 * cause the song to finish, so you probably don't want to send it in the
33 * middle of a song. (The midsquash plugin removes these events when
34 * merging multiple tracks into a single track.)
35 */
36
37#include <xmms/xmms_xformplugin.h>
38#include <xmms/xmms_sample.h>
39#include <xmms/xmms_medialib.h>
40#include <xmms/xmms_log.h>
41#include <fluidsynth.h>
42
43#include <glib.h>
44#include <string.h>
45#include <unistd.h>
46#include <stdlib.h>
47#include <ctype.h>
48
49/* Fill up the buffer with MIDI events every 500 milliseconds */
50#define CALLBACK_FREQ500 500
51
52/* FluidSynth uses floating point samples internally. Uncomment this to pass
53 * those samples to XMMS2 unchanged. Comment it out to have FluidSynth convert
54 * the samples to signed 16-bit before passing them to XMMS2.
55 *
56 * Floating point will become the default if/when all xform plugins support it.
57 */
58/*#define FLUIDSYNTH_USE_FLOAT*/
59
60/*
61 * Type definitions
62 */
63typedef struct xmms_fluidsynth_data_St {
64 fluid_synth_t *synth;
65 fluid_settings_t *settings;
66 fluid_sequencer_t *sequencer;
67 short synthseq_id, callback_id;
68 GArray *soundfont_id; /* int */
69 guint num_soundfonts;
70 gboolean end_of_song;
71 guint trailing_samples;
72 GString *comment;
73 gboolean has_title;
74
75 gint32 ticks_per_quarter_note;
76 gulong us_per_quarter_note;
77 gulong now; /* Time of last event in microseconds since synth start */
78
79 gulong delay; /* Current time (in MIDI delta ticks) until next event */
80
81 guchar prev_event; /* Last command (for MIDI "running status") */
82} xmms_fluidsynth_data_t;
83
84/*
85 * Function prototypes
86 */
87static gboolean xmms_fluidsynth_plugin_setup (xmms_xform_plugin_t *xform_plugin);
88static gboolean xmms_fluidsynth_init (xmms_xform_t *xform);
89static void xmms_fluidsynth_destroy (xmms_xform_t *xform);
90static gint xmms_fluidsynth_read (xmms_xform_t *xform, xmms_sample_t *buf, gint len, xmms_error_t *err);
91static void xmms_fluidsynth_sf_config_changed (xmms_object_t *obj, xmmsv_t *_value, gpointer udata);
92static void xmms_fluidsynth_config_changed (xmms_object_t *obj, xmmsv_t *_value, gpointer udata);
93static void xmms_fluidsynth_skip_bytes (xmms_xform_t *xform, guint count);
94static gboolean xmms_fluidsynth_get_byte (xmms_xform_t *xform, guchar *val);
95static gboolean xmms_fluidsynth_read_midi_num (xmms_xform_t *xform, gulong *val, guchar *firstbyte);
96static void xmms_fluidsynth_callback (unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *vdata);
97static void xmms_fluidsynth_sched_callback (xmms_fluidsynth_data_t *data, unsigned int callback_date);
98static void xmms_fluidsynth_set_metadata (xmms_xform_t *xform, const gchar *metakey, const gchar *text, guint len);
99
100static const struct {
101 const gchar *key;
102 const gchar *value;
103} config_params[] = {
104 {"sample-rate", "48000"},
105 {"encoding", "ISO8859-1"}
106};
107
108/*
109 * Plugin header
110 */
111XMMS_XFORM_PLUGIN_DEFINE ("fluidsynth",xmms_plugin_desc_t __attribute__((visibility ("default"))) XMMS_PLUGIN_DESC
= { XMMS_PLUGIN_TYPE_XFORM, 7, "fluidsynth", "FluidSynth synthesiser "
, "clang-analysis", "MIDI synthesiser", (gboolean (*)(gpointer
))xmms_fluidsynth_plugin_setup };
112 "FluidSynth synthesiser ",xmms_plugin_desc_t __attribute__((visibility ("default"))) XMMS_PLUGIN_DESC
= { XMMS_PLUGIN_TYPE_XFORM, 7, "fluidsynth", "FluidSynth synthesiser "
, "clang-analysis", "MIDI synthesiser", (gboolean (*)(gpointer
))xmms_fluidsynth_plugin_setup };
113 XMMS_VERSION,xmms_plugin_desc_t __attribute__((visibility ("default"))) XMMS_PLUGIN_DESC
= { XMMS_PLUGIN_TYPE_XFORM, 7, "fluidsynth", "FluidSynth synthesiser "
, "clang-analysis", "MIDI synthesiser", (gboolean (*)(gpointer
))xmms_fluidsynth_plugin_setup };
114 "MIDI synthesiser",xmms_plugin_desc_t __attribute__((visibility ("default"))) XMMS_PLUGIN_DESC
= { XMMS_PLUGIN_TYPE_XFORM, 7, "fluidsynth", "FluidSynth synthesiser "
, "clang-analysis", "MIDI synthesiser", (gboolean (*)(gpointer
))xmms_fluidsynth_plugin_setup };
115 xmms_fluidsynth_plugin_setup)xmms_plugin_desc_t __attribute__((visibility ("default"))) XMMS_PLUGIN_DESC
= { XMMS_PLUGIN_TYPE_XFORM, 7, "fluidsynth", "FluidSynth synthesiser "
, "clang-analysis", "MIDI synthesiser", (gboolean (*)(gpointer
))xmms_fluidsynth_plugin_setup };
;
116
117static gboolean
118xmms_fluidsynth_plugin_setup (xmms_xform_plugin_t *xform_plugin)
119{
120 xmms_xform_methods_t methods;
121 xmms_config_property_t *cfgv;
122 gchar key[64];
123 int i;
124
125 XMMS_XFORM_METHODS_INIT (methods)memset (&methods, 0, sizeof (xmms_xform_methods_t));
126 methods.init = xmms_fluidsynth_init;
127 methods.destroy = xmms_fluidsynth_destroy;
128 methods.read = xmms_fluidsynth_read;
129
130 xmms_xform_plugin_methods_set (xform_plugin, &methods);
131
132 /*
133 xmms_plugin_info_add (plugin, "URL", "http://www.xmms.org/");
134 xmms_plugin_info_add (plugin, "Author", "Adam Nielsen <malvineous@shikadi.net>");
135 xmms_plugin_info_add (plugin, "License", "LGPL");
136 */
137
138 xmms_xform_plugin_indata_add (xform_plugin,
139 XMMS_STREAM_TYPE_MIMETYPE,
140 "audio/rawmidi",
141 XMMS_STREAM_TYPE_END);
142
143 for (i = 0; i < G_N_ELEMENTS (config_params)(sizeof (config_params) / sizeof ((config_params)[0])); i++) {
144 xmms_xform_plugin_config_property_register (xform_plugin,
145 config_params[i].key,
146 config_params[i].value,
147 NULL((void*)0), NULL((void*)0));
148 }
149
150 /* Set up the soundfont.0 config option and the function to call when its
151 * value is changed.
152 */
153 cfgv = xmms_xform_plugin_config_property_register (xform_plugin,
Value stored to 'cfgv' is never read
154 "soundfont.0",
155 "/path/to/your-soundfont-goes-here.sf2",
156 xmms_fluidsynth_sf_config_changed,
157 xform_plugin);
158
159 /* Look for >= soundfont.1 (which have been loaded from the config file) and
160 * set up the callback function on those.
161 */
162 for (i = 1; ; i++) {
163 g_snprintf (key, sizeof (key), "soundfont.%i", i);
164
165 cfgv = xmms_xform_plugin_config_lookup (xform_plugin, key);
166 if (!cfgv) {
167 break;
168 }
169
170 xmms_config_property_callback_set (cfgv,
171 xmms_fluidsynth_sf_config_changed,
172 xform_plugin);
173 }
174
175 /* Make sure the last SoundFont entry is empty so there's always room to add
176 * another SoundFont.
177 */
178 xmms_fluidsynth_sf_config_changed (NULL((void*)0), NULL((void*)0), xform_plugin);
179
180 return TRUE(!(0));
181}
182
183static gboolean
184xmms_fluidsynth_init (xmms_xform_t *xform)
185{
186 xmms_fluidsynth_data_t *data;
187 xmms_config_property_t *cfgv;
188 double samplerate;
189 gint i, id;
190 gchar key[64];
191 const gchar *soundfont_path;
192
193 g_return_val_if_fail (xform, FALSE)do{ if (xform) { } else { g_return_if_fail_warning (((gchar*)
0), ((const char*) (__func__)), "xform"); return ((0)); }; }
while (0)
;
194
195 data = g_new0 (xmms_fluidsynth_data_t, 1)((xmms_fluidsynth_data_t *) g_malloc0_n ((1), sizeof (xmms_fluidsynth_data_t
)))
;
196 g_return_val_if_fail (data, FALSE)do{ if (data) { } else { g_return_if_fail_warning (((gchar*) 0
), ((const char*) (__func__)), "data"); return ((0)); }; }while
(0)
;
197 xmms_xform_private_data_set (xform, data);
198
199 if (!xmms_xform_auxdata_get_intxmms_xform_auxdata_get_int32 (xform, "tempo", &data->ticks_per_quarter_note)) {
200 XMMS_DBG ("xform auxdata value 'tempo' not set (bug in previous xform plugin)")g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "200" ": "
"xform auxdata value 'tempo' not set (bug in previous xform plugin)"
)
;
201 g_free (data);
202 return FALSE(0);
203 }
204
205 data->settings = new_fluid_settings ();
206
207 /* Set up the current configuration. This needs to happen *after* the
208 * FluidSynth settings instance has been created because the callback
209 * function uses it directly. This also has nothing to do with the
210 * dynamic SoundFont options (e.g. soundfont.1)
211 */
212 for (i = 0; i < G_N_ELEMENTS (config_params)(sizeof (config_params) / sizeof ((config_params)[0])); i++) {
213 cfgv = xmms_xform_config_lookup (xform, config_params[i].key);
214 xmms_config_property_callback_set (cfgv,
215 xmms_fluidsynth_config_changed,
216 data);
217
218 xmms_fluidsynth_config_changed (XMMS_OBJECT (cfgv)((xmms_object_t *)cfgv), NULL((void*)0), data);
219 }
220
221 fluid_settings_getnum (data->settings, "synth.sample-rate", &samplerate);
222
223 xmms_xform_outdata_type_add (xform,
224 XMMS_STREAM_TYPE_MIMETYPE,
225 "audio/pcm",
226 XMMS_STREAM_TYPE_FMT_FORMAT,
227#ifdef FLUIDSYNTH_USE_FLOAT
228 XMMS_SAMPLE_FORMAT_FLOAT,
229#else
230 XMMS_SAMPLE_FORMAT_S16,
231#endif
232 XMMS_STREAM_TYPE_FMT_CHANNELS,
233 2,
234 XMMS_STREAM_TYPE_FMT_SAMPLERATE,
235 (gint)samplerate,
236 XMMS_STREAM_TYPE_END);
237
238 data->synth = new_fluid_synth (data->settings);
239 data->sequencer = new_fluid_sequencer2 (0);
240 data->synthseq_id = fluid_sequencer_register_fluidsynth (data->sequencer,
241 data->synth);
242 data->callback_id = fluid_sequencer_register_client (data->sequencer, "xmms2",
243 xmms_fluidsynth_callback,
244 xform);
245
246 /* Scan through all the SoundFonts that have been set and load them all */
247 data->soundfont_id = g_array_new (FALSE(0), FALSE(0), sizeof (int));
248 for (i = 0; ; i++) {
249 g_snprintf (key, sizeof (key), "soundfont.%i", i);
250
251 cfgv = xmms_xform_config_lookup (xform, key);
252 if (!cfgv) {
253 /* No more SoundFonts to load */
254 break;
255 }
256
257 soundfont_path = xmms_config_property_get_string (cfgv);
258 if (!soundfont_path[0]) {
259 /* Ignore blank entries (i.e. the last one) */
260 continue;
261 }
262
263 id = fluid_synth_sfload (data->synth, soundfont_path, 1);
264 if (id == FLUID_FAILED(-1)) {
265 /* Since this is the last thing to allocate, just call the destroy
266 * function instead of duplicating all that code as cleanup here.
267 */
268 xmms_fluidsynth_destroy (xform);
269
270 xmms_log_error ("Unable to open SoundFont: %s", soundfont_path)g_warning ("../src/plugins/fluidsynth/fluidsynth.c" ":" "270"
": " "Unable to open SoundFont: %s", soundfont_path)
;
271 return FALSE(0);
272 } else {
273 g_array_append_val (data->soundfont_id, id)g_array_append_vals (data->soundfont_id, &(id), 1);
274 }
275 }
276
277 /* So that the song doesn't get cut off early, after the MIDI data runs out
278 * wait for four times the callback interval for the notes to finish playing.
279 * 2x the interval is enough for the last buffered event to be processed,
280 * then another 2x will give about one second on top of that.
281 */
282 data->trailing_samples = samplerate * (CALLBACK_FREQ500 / 250);
283
284 /* Default tempo is 500,000us (0.5 sec) per quarter-note */
285 data->us_per_quarter_note = 500000;
286
287 data->comment = NULL((void*)0);
288 data->has_title = FALSE(0);
289 data->end_of_song = FALSE(0);
290
291 data->now = fluid_sequencer_get_tick (data->sequencer);
292 xmms_fluidsynth_sched_callback (data, data->now);
293
294 return TRUE(!(0));
295}
296
297static void
298xmms_fluidsynth_destroy (xmms_xform_t *xform)
299{
300 xmms_config_property_t *cfgv;
301 xmms_fluidsynth_data_t *data;
302 gint i;
303
304 g_return_if_fail (xform)do{ if (xform) { } else { g_return_if_fail_warning (((gchar*)
0), ((const char*) (__func__)), "xform"); return; }; }while (
0)
;
305
306 data = xmms_xform_private_data_get (xform);
307 g_return_if_fail (data)do{ if (data) { } else { g_return_if_fail_warning (((gchar*) 0
), ((const char*) (__func__)), "data"); return; }; }while (0)
;
308
309 for (i = data->soundfont_id->len; i > 0; i--) {
310 fluid_synth_sfunload (data->synth,
311 g_array_index (data->soundfont_id, int, i - 1)(((int*) (void *) (data->soundfont_id)->data) [(i - 1)]
)
,
312 FALSE(0));
313 }
314
315 g_array_free (data->soundfont_id, TRUE(!(0)));
316
317 if (data->sequencer)
318 delete_fluid_sequencer (data->sequencer);
319
320 if (data->synth)
321 delete_fluid_synth (data->synth);
322
323 if (data->comment)
324 g_string_free (data->comment, TRUE(!(0)));
325
326 /* Remove callbacks from the FluidSynth options (sample rate etc.) */
327 for (i = 0; i < G_N_ELEMENTS (config_params)(sizeof (config_params) / sizeof ((config_params)[0])); i++) {
328 cfgv = xmms_xform_config_lookup (xform, config_params[i].key);
329
330 xmms_config_property_callback_remove (cfgv,
331 xmms_fluidsynth_config_changed,
332 data);
333 }
334
335 g_free (data);
336}
337
338static gint
339xmms_fluidsynth_read (xmms_xform_t *xform, xmms_sample_t *buf, gint len, xmms_error_t *err)
340{
341 xmms_fluidsynth_data_t *data;
342 gint status;
343 gint sample_size;
344
345#ifdef FLUIDSYNTH_USE_FLOAT
346 sample_size = xmms_sample_size_get (XMMS_SAMPLE_FORMAT_FLOAT) * 2;
347#else
348 sample_size = xmms_sample_size_get (XMMS_SAMPLE_FORMAT_U16) * 2;
349#endif
350
351 data = xmms_xform_private_data_get (xform);
352 if (data->end_of_song) {
353 /* The song is ended, but don't stop too abruptly! */
354 if (data->trailing_samples == 0) return 0;
355 data->trailing_samples -= MIN (data->trailing_samples, len / sample_size)(((data->trailing_samples) < (len / sample_size)) ? (data
->trailing_samples) : (len / sample_size))
;
356 }
357
358#ifdef FLUIDSYNTH_USE_FLOAT
359 status = fluid_synth_write_float (
360#else
361 status = fluid_synth_write_s16 (
362#endif
363 data->synth, len / sample_size,
364 buf, 0, 2, /* Left channel (off 0 incr 2) */
365 buf, 1, 2 /* Right channel (off 1 incr 2) */
366 );
367
368 return (status == FLUID_OK(0)) ? len : 0;
369}
370
371static void
372xmms_fluidsynth_sf_config_changed (xmms_object_t *obj, xmmsv_t *_value, gpointer udata)
373{
374 xmms_config_property_t *cfgv;
375 xmms_xform_plugin_t *xform_plugin = (xmms_xform_plugin_t *) udata;
376 const gchar *soundfont_path = "";
377 gchar key[64];
378 gint i;
379
380 g_return_if_fail (xform_plugin)do{ if (xform_plugin) { } else { g_return_if_fail_warning (((
gchar*) 0), ((const char*) (__func__)), "xform_plugin"); return
; }; }while (0)
;
381
382 /* Figure out how many entries exist, and if the last one is not blank, add
383 * a blank one to allow another SoundFont to be added.
384 */
385 for (i = 0; ; i++) {
386 g_snprintf (key, sizeof (key), "soundfont.%i", i);
387
388 cfgv = xmms_xform_plugin_config_lookup (xform_plugin, key);
389 if (!cfgv) {
390 /* No more entries, soundfont_path is the content of the previous entry
391 * (the last one in the list.)
392 */
393 if (soundfont_path[0] != 0) {
394 /* It had a SoundFont set, so create a new empty one */
395 xmms_xform_plugin_config_property_register (xform_plugin,
396 key, "",
397 xmms_fluidsynth_sf_config_changed,
398 xform_plugin);
399 }
400 break;
401 }
402
403 soundfont_path = xmms_config_property_get_string (cfgv);
404 }
405}
406
407static void
408xmms_fluidsynth_config_changed (xmms_object_t *obj, xmmsv_t *_value, gpointer udata)
409{
410 xmms_fluidsynth_data_t *data;
411 const gchar *name;
412 const gchar *str_value;
413 gint int_value;
414 gdouble dbl_value;
415 int type;
416
417 data = (xmms_fluidsynth_data_t *) udata;
418 g_return_if_fail (data)do{ if (data) { } else { g_return_if_fail_warning (((gchar*) 0
), ((const char*) (__func__)), "data"); return; }; }while (0)
;
419
420 name = xmms_config_property_get_name ((xmms_config_property_t *) obj);
421
422 /* Ignore these non-FluidSynth options, they are looked up as needed */
423 if (strcmp (name, "fluidsynth.encoding") == 0) {
424 return;
425 }
426
427 /* XMMS settings are "fluidsynth.blah" so chop off the first five chars and
428 * they become FluidSynth options like "synth.blah"
429 */
430 name = &name[5];
431
432 type = fluid_settings_get_type (data->settings, name);
433 switch (type) {
434 case FLUID_INT_TYPE:
435 int_value = xmms_config_property_get_int ((xmms_config_property_t *) obj);
436 fluid_settings_setint (data->settings, name, int_value);
437 break;
438 case FLUID_STR_TYPE:
439 str_value = xmms_config_property_get_string ((xmms_config_property_t *) obj);
440 fluid_settings_setstr (data->settings, name, str_value);
441 break;
442 case FLUID_NUM_TYPE: /* double */
443 dbl_value = xmms_config_property_get_float ((xmms_config_property_t *) obj);
444 fluid_settings_setnum (data->settings, name, dbl_value);
445 break;
446 case FLUID_SET_TYPE: /* set of values */
447 XMMS_DBG ("Unsupported data type for FluidSynth config value %s", name)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "447" ": "
"Unsupported data type for FluidSynth config value %s", name
)
;
448 break;
449 case FLUID_NO_TYPE: /* undefined */
450 XMMS_DBG ("Invalid FluidSynth config option %s", name)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "450" ": "
"Invalid FluidSynth config option %s", name)
;
451 break;
452 }
453
454}
455
456static void
457xmms_fluidsynth_skip_bytes (xmms_xform_t *xform, guint count)
458{
459 xmms_error_t error;
460 gint ret;
461 guchar dummy[16];
462 while (count > 0) {
463 ret = xmms_xform_read (xform, dummy, MIN (count, sizeof (dummy))(((count) < (sizeof (dummy))) ? (count) : (sizeof (dummy))
)
, &error);
464 if (ret < 1) break;
465 count -= ret;
466 }
467 return;
468}
469
470static gboolean
471xmms_fluidsynth_get_byte (xmms_xform_t *xform, guchar *val)
472{
473 xmms_error_t error;
474 gint ret;
475 ret = xmms_xform_read (xform, val, 1, &error);
476 if (ret <= 0) return FALSE(0);
477 return TRUE(!(0));
478}
479
480/* Read a variable-length MIDI number. If firstbyte is non-NULL it is used
481 * as if it was the next byte read out of the file.
482 */
483static gboolean
484xmms_fluidsynth_read_midi_num (xmms_xform_t *xform, gulong *val,
485 guchar *firstbyte)
486{
487 gint i;
488 guchar buf;
489
490 *val = 0;
491 for (i = 0; i < 4; i++) {
492 if (firstbyte) {
493 buf = *firstbyte;
494 firstbyte = NULL((void*)0);
495 } else {
496 if (!xmms_fluidsynth_get_byte (xform, &buf))
497 return FALSE(0);
498 }
499 *val |= buf & 0x7F;
500 if (buf & 0x80) *val <<= 7;
501 else break;
502 }
503
504 return TRUE(!(0));
505}
506
507/* Callback when the MIDI buffer gets low and needs more notes */
508static void
509xmms_fluidsynth_callback (unsigned int time, fluid_event_t *event,
510 fluid_sequencer_t *seq, void *vdata)
511{
512 const gchar *metakey;
513 xmms_error_t error;
514 gint i;
515 guchar midi_event;
516 xmms_xform_t *xform;
517 xmms_fluidsynth_data_t *data;
518 fluid_event_t *evt;
519 guchar event_data[2];
520 gulong delay;
521
522 xform = (xmms_xform_t *) vdata;
523 g_return_if_fail (xform)do{ if (xform) { } else { g_return_if_fail_warning (((gchar*)
0), ((const char*) (__func__)), "xform"); return; }; }while (
0)
;
524
525 data = xmms_xform_private_data_get (xform);
526 g_return_if_fail (data)do{ if (data) { } else { g_return_if_fail_warning (((gchar*) 0
), ((const char*) (__func__)), "data"); return; }; }while (0)
;
527
528 evt = new_fluid_event ();
529 fluid_event_set_source (evt, -1);
530 fluid_event_set_dest (evt, data->synthseq_id);
531
532 while (!(data->end_of_song = !xmms_fluidsynth_read_midi_num (xform, &delay, NULL((void*)0)))) {
533 gint chan;
534
535 /* Read MIDI event */
536 data->end_of_song = !xmms_fluidsynth_get_byte (xform, &midi_event);
537 if (data->end_of_song) break;
538
539 if (!(midi_event & 0x80)) {
540 /* This is a running-status byte */
541 event_data[0] = midi_event;
542 midi_event = data->prev_event;
543 } else {
544 if ((midi_event & 0xF0) != 0xF0) {
545 /* Meta events (0xF0 through 0xFF) can appear in the middle of
546 * running-status data without affecting the running status, so we
547 * only update the 'last event' if this isn't a meta-event.
548 */
549 data->prev_event = midi_event;
550 }
551 if (!xmms_fluidsynth_get_byte (xform, &event_data[0]))
552 break;
553 }
554
555 chan = midi_event & 0x0F;
556 gboolean send_event = FALSE(0);
557 switch (midi_event & 0xF0) {
558 case 0x80: /* Note off (two bytes) */
559 /* FIXME: Release velocity unused */
560 fluid_event_noteoff (evt, chan, event_data[0]);
561 xmms_fluidsynth_skip_bytes (xform, 1);
562 send_event = TRUE(!(0));
563 break;
564 case 0x90: /* Note on (two bytes) */
565 if (!xmms_fluidsynth_get_byte (xform, &event_data[1]))
566 break;
567 fluid_event_noteon (evt, chan, event_data[0], event_data[1]);
568 send_event = TRUE(!(0));
569 break;
570 case 0xA0: /* Key pressure (two bytes) */
571 /* TODO: Does FluidSynth support this? */
572 xmms_fluidsynth_skip_bytes (xform, 1);
573 break;
574 case 0xB0: /* Controller change (two bytes) */
575 if (!xmms_fluidsynth_get_byte (xform, &event_data[1]))
576 break;
577 fluid_event_control_change (evt, chan, event_data[0], event_data[1]);
578 send_event = TRUE(!(0));
579 break;
580 case 0xC0: /* Instrument change (one byte) */
581 fluid_event_program_change (evt, chan, event_data[0]);
582 send_event = TRUE(!(0));
583 break;
584 case 0xD0: /* Channel pressure (one byte) */
585 fluid_event_channel_pressure (evt, chan, event_data[0]);
586 send_event = TRUE(!(0));
587 break;
588 case 0xE0: /* Pitch bend (two bytes) */
589 if (!xmms_fluidsynth_get_byte (xform, &event_data[1]))
590 break;
591 fluid_event_pitch_bend (evt, chan, (event_data[1] << 7) | event_data[0]);
592 send_event = TRUE(!(0));
593 break;
594 case 0xF0: {
595 if (midi_event == 0xFF) { /* Meta-event */
596 gulong len;
597 guchar meta_event = event_data[0];
598 if (!xmms_fluidsynth_read_midi_num (xform, &len, NULL((void*)0)))
599 break;
600 switch (meta_event) {
601 case 0x01: { /* Text event */
602 gint orig_size;
603 gboolean first_comment = FALSE(0);
604
605 if (!data->comment) {
606 data->comment = g_string_sized_new (len);
607 first_comment = TRUE(!(0));
608 } else {
609 g_string_append (data->comment, ", ");
610 }
611
612 orig_size = data->comment->len;
613 g_string_set_size (data->comment, orig_size + len);
614 xmms_xform_read (xform, &data->comment->str[orig_size], len, &error);
615
616 /* Ensure trailing NULL */
617 g_string_set_size (data->comment, orig_size + len);
618 XMMS_DBG ("Found text event: %s", &data->comment->str[orig_size])g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "618" ": "
"Found text event: %s", &data->comment->str[orig_size
])
;
619
620 if (first_comment) {
621 if (data->comment->len > 0) {
622 xmms_fluidsynth_set_metadata (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_TITLE"title", data->comment->str, data->comment->len);
623 } /* else ignore blank fields, but treat them as used */
624 data->has_title = TRUE(!(0));
625 } else {
626 xmms_fluidsynth_set_metadata (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_COMMENT"comment", data->comment->str, data->comment->len);
627 }
628 break;
629 }
630 case 0x02: { /* Copyright notice */
631 GString *cp = g_string_sized_new (len);
632 xmms_xform_read (xform, cp->str, len, &error);
633 g_string_set_size (cp, len);
634 XMMS_DBG ("Found copyright notice: %s", cp->str)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "634" ": "
"Found copyright notice: %s", cp->str)
;
635
636 xmms_fluidsynth_set_metadata (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_COPYRIGHT"copyright", cp->str, cp->len);
637 break;
638 }
639 case 0x03: { /* Song title */
640 GString *title = g_string_sized_new (len);
641 xmms_xform_read (xform, title->str, len, &error);
642 /* Ensure trailing NULL */
643 g_string_set_size (title, len);
644 XMMS_DBG ("Found title event: %s", title->str)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "644" ": "
"Found title event: %s", title->str)
;
645
646 /* Only set the first event as the title, but because these
647 * events are used for track names it can be overridden by
648 * a text event (meta event 0x01) if there is one.
649 */
650 if (!data->has_title) {
651 if (title->len > 0) {
652 xmms_fluidsynth_set_metadata (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_TITLE"title", title->str, title->len);
653 } /* else ignore blank fields, but treat them as used */
654 data->has_title = TRUE(!(0));
655 }
656 break;
657 }
658 case 0x51: { /* Set tempo */
659 gulong bpm;
660
661 data->us_per_quarter_note = 0;
662 for (i = 0; i < len; i++) {
663 if (!xmms_fluidsynth_get_byte (xform, &event_data[1]))
664 break;
665 data->us_per_quarter_note <<= 8;
666 data->us_per_quarter_note |= event_data[1];
667 }
668 if (data->us_per_quarter_note == 0) {
669 /* Invalid time, will cause divide by zero */
670 data->us_per_quarter_note = 500000;
671 XMMS_DBG ("Invalid tempo change to 0 BPM, using 120 BPM")g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "671" ": "
"Invalid tempo change to 0 BPM, using 120 BPM")
;
672 }
673 bpm = 60000000 / data->us_per_quarter_note;
674 XMMS_DBG ("Tempo change to %lu BPM", bpm)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "674" ": "
"Tempo change to %lu BPM", bpm)
;
675 metakey = XMMS_MEDIALIB_ENTRY_PROPERTY_BPM"bpm";
676 xmms_xform_metadata_set_int (xform, metakey, bpm);
677 break;
678 }
679 case 0x2F: /* End of track */
680 data->end_of_song = TRUE(!(0));
681 break;
682 default: /* Unknown, skip over the data */
683 XMMS_DBG ("Ignoring unknown meta event %02X", meta_event)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "683" ": "
"Ignoring unknown meta event %02X", meta_event)
;
684 xmms_fluidsynth_skip_bytes (xform, len);
685 break;
686 }
687 } else { /* Sysex */
688 gulong len;
689 /* We have to pass event_data[0] here as it's the first byte of
690 * the length field.
691 */
692 if (!xmms_fluidsynth_read_midi_num (xform, &len, &event_data[0]))
693 break;
694 XMMS_DBG ("MIDI: Ignoring sysex (%lu bytes)", len)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "694" ": "
"MIDI: Ignoring sysex (%lu bytes)", len)
;
695 xmms_fluidsynth_skip_bytes (xform, len);
696 }
697 break;
698 }
699 default:
700 XMMS_DBG ("Corrupted MIDI file (invalid event 0x%02X)", midi_event)g_debug ("../src/plugins/fluidsynth/fluidsynth.c" ":" "700" ": "
"Corrupted MIDI file (invalid event 0x%02X)", midi_event)
;
701 data->end_of_song = TRUE(!(0));
702 break;
703 }
704
705 data->now += delay * (data->us_per_quarter_note / data->ticks_per_quarter_note);
706 gulong event_time = data->now / 1000;
707 if (send_event) {
708 fluid_sequencer_send_at (data->sequencer, evt, event_time, 1);
709 } /* else no event was generated from the MIDI data */
710
711 /* Break out if we've got enough notes to play until the next callback */
712 if (time + CALLBACK_FREQ500 * 2 < event_time) break;
713 }
714
715 if (!data->end_of_song) {
716 /* Once we get here we've got enough notes in the buffer waiting to be
717 * played until the next callback, so schedule it now.
718 */
719 xmms_fluidsynth_sched_callback (data, time + CALLBACK_FREQ500);
720 } else {
721 metakey = XMMS_MEDIALIB_ENTRY_PROPERTY_DURATION"duration";
722 xmms_xform_metadata_set_int (xform, metakey, data->now / 1000);
723 }
724 delete_fluid_event (evt);
725}
726
727static void
728xmms_fluidsynth_sched_callback (xmms_fluidsynth_data_t *data,
729 unsigned int callback_date)
730{
731 fluid_event_t *evt;
732 int fluid_res;
733
734 evt = new_fluid_event ();
735 fluid_event_set_source (evt, -1);
736 fluid_event_set_dest (evt, data->callback_id);
737 fluid_event_timer (evt, NULL((void*)0));
738
739 fluid_res = fluid_sequencer_send_at (data->sequencer, evt, callback_date, 1);
740 if (fluid_res != FLUID_OK(0)) data->end_of_song = TRUE(!(0));
741
742 delete_fluid_event (evt);
743}
744
745
746static void
747xmms_fluidsynth_set_metadata (xmms_xform_t *xform, const gchar *metakey,
748 const gchar *text, guint len)
749{
750 gsize readsize,writsize;
751 GError *err = NULL((void*)0);
752 gchar *tmp;
753 const gchar *encoding;
754 xmms_config_property_t *config;
755
756 /* Find out what encoding we're currently using */
757 config = xmms_xform_config_lookup (xform, "encoding");
758 if (config) {
759 encoding = xmms_config_property_get_string (config);
760 } else {
761 encoding = (const gchar *)"ISO8859-1";
762 }
763
764 tmp = g_convert ((const char *)text, len, "UTF-8", encoding, &readsize,
765 &writsize, &err);
766
767 if (!tmp) {
768 xmms_log_info ("Converting text '%s' failed (check fluidsynth.encoding property): %s",g_message ("../src/plugins/fluidsynth/fluidsynth.c" ":" "769"
": " "Converting text '%s' failed (check fluidsynth.encoding property): %s"
, text, err ? err->message : "Error not set")
769 text, err ? err->message : "Error not set")g_message ("../src/plugins/fluidsynth/fluidsynth.c" ":" "769"
": " "Converting text '%s' failed (check fluidsynth.encoding property): %s"
, text, err ? err->message : "Error not set")
;
770 err = NULL((void*)0);
771 tmp = g_convert ((const char *)text, len, "UTF-8", "ISO8859-1", &readsize, &writsize, &err);
772 }
773
774 if (tmp) {
775 g_strstrip (tmp)g_strchomp (g_strchug (tmp));
776 if (tmp[0] != '\0') {
777 xmms_xform_metadata_set_str (xform, metakey, tmp);
778 }
779 g_free (tmp);
780 }
781
782 return;
783}