Implement alternative resource loading for sound
[projects/chimara/chimara.git] / libchimara / schannel.c
1 #include <config.h>
2 #include <glib.h>
3 #include <glib/gi18n.h>
4 #include <libchimara/glk.h>
5 #ifdef GSTREAMER_SOUND
6 #include <gst/gst.h>
7 #endif
8 #include "magic.h"
9 #include "schannel.h"
10 #include "chimara-glk-private.h"
11 #include "gi_dispa.h"
12 #include "gi_blorb.h"
13 #include "resource.h"
14
15 extern GPrivate *glk_data_key;
16
17 #ifdef GSTREAMER_SOUND
18 /* Stop any currently playing sound on this channel, and remove any
19  format-specific GStreamer elements from the channel. */
20 static void
21 clean_up_after_playing_sound(schanid_t chan)
22 {
23         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
24                 WARNING_S(_("Could not set GstElement state to"), "NULL");
25         if(chan->demux)
26         {
27                 gst_bin_remove(GST_BIN(chan->pipeline), chan->demux);
28                 chan->demux = NULL;
29         }
30         if(chan->decode)
31         {
32                 gst_bin_remove(GST_BIN(chan->pipeline), chan->decode);
33                 chan->decode = NULL;
34         }
35 }
36
37 /* This signal is thrown whenever the GStreamer pipeline generates a message.
38  Most messages are harmless. */
39 static void
40 on_pipeline_message(GstBus *bus, GstMessage *message, schanid_t s)
41 {
42         /* g_printerr("Got %s message\n", GST_MESSAGE_TYPE_NAME(message)); */
43
44         GError *err;
45         gchar *debug_message;
46         
47         switch(GST_MESSAGE_TYPE(message)) {
48         case GST_MESSAGE_ERROR: 
49         {
50                 gst_message_parse_error(message, &err, &debug_message);
51                 IO_WARNING(_("GStreamer error"), err->message, debug_message);
52                 g_error_free(err);
53                 g_free(debug_message);
54                 clean_up_after_playing_sound(s);
55         }
56                 break;
57         case GST_MESSAGE_WARNING:
58         {
59                 gst_message_parse_warning(message, &err, &debug_message);
60                 IO_WARNING(_("GStreamer warning"), err->message, debug_message);
61                 g_error_free(err);
62                 g_free(debug_message);
63         }
64                 break;
65         case GST_MESSAGE_INFO:
66         {
67                 gst_message_parse_info(message, &err, &debug_message);
68                 g_message("GStreamer info \"%s\": %s", err->message, debug_message);
69                 g_error_free(err);
70                 g_free(debug_message);
71         }
72                 break;
73         case GST_MESSAGE_EOS: /* End of stream */
74                 /* Decrease repeats if not set to forever */
75                 if(s->repeats != (glui32)-1)
76                         s->repeats--;
77                 if(s->repeats > 0) {
78                         if(!gst_element_seek_simple(s->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 0)) {
79                                 WARNING(_("Could not execute GStreamer seek"));
80                                 clean_up_after_playing_sound(s);
81                         }
82                 } else {
83                         clean_up_after_playing_sound(s);
84                 }
85                 break;
86         default:
87                 /* unhandled message */
88                 break;
89         }
90 }
91
92 /* This signal is thrown when the OGG demuxer element has decided what kind of
93  outputs it will output. We connect the decoder element dynamically. */
94 static void
95 on_ogg_demuxer_pad_added(GstElement *demux, GstPad *pad, schanid_t s)
96 {
97         GstPad *sinkpad;
98         
99         /* We can now link this pad with the vorbis-decoder sink pad */
100         sinkpad = gst_element_get_static_pad(s->decode, "sink");
101         if(gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK)
102                 WARNING(_("Could not link OGG demuxer with Vorbis decoder"));
103         gst_object_unref(sinkpad);
104 }
105
106 /* This signal is thrown when the typefinder element has found the type of its
107  input. Now that we know what kind of input stream we have, we can connect the
108  proper demuxer/decoder elements. */
109 static void
110 on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t s)
111 {
112         gchar *type = gst_caps_to_string(caps);
113         if(strcmp(type, "application/ogg") == 0) 
114         {
115                 s->demux = gst_element_factory_make("oggdemux", NULL);
116                 s->decode = gst_element_factory_make("vorbisdec", NULL);
117                 if(!s->demux || !s->decode)
118                 {
119                         WARNING(_("Could not create one or more GStreamer elements"));
120                         goto finally;
121                 }
122                 gst_bin_add_many(GST_BIN(s->pipeline), s->demux, s->decode, NULL);
123                 if(!gst_element_link(s->typefind, s->demux) || !gst_element_link(s->decode, s->convert))
124                 {
125                         WARNING(_("Could not link GStreamer elements"));
126                         goto finally;
127                 }
128                 /* We link the demuxer and decoder together dynamically, since the
129                  demuxer doesn't know what source pads it will have until it starts
130                  demuxing the stream */
131                 g_signal_connect(s->demux, "pad-added", G_CALLBACK(on_ogg_demuxer_pad_added), s);
132         }
133         else if(strcmp(type, "audio/x-aiff") == 0)
134         {
135                 s->decode = gst_element_factory_make("aiffparse", NULL);
136                 if(!s->decode)
137                 {
138                         WARNING(_("Could not create 'aiffparse' GStreamer element"));
139                         goto finally;
140                 }
141                 gst_bin_add(GST_BIN(s->pipeline), s->decode);
142                 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL))
143                 {
144                         WARNING(_("Could not link GStreamer elements"));
145                         goto finally;
146                 }
147         }
148         else
149         {
150                 WARNING_S(_("Unexpected audio type in blorb"), type);
151         }
152
153 finally:
154         g_free(type);
155 }
156 #endif /* GSTREAMER_SOUND */
157
158 /**
159  * glk_schannel_create:
160  * @rock: The rock value to give the new sound channel.
161  *
162  * This creates a sound channel, about as you'd expect.
163  *
164  * Remember that it is possible that the library will be unable to create a new
165  * channel, in which case glk_schannel_create() will return %NULL.
166  *
167  * Returns: A new sound channel, or %NULL.
168  */
169 schanid_t 
170 glk_schannel_create(glui32 rock)
171 {
172 #ifdef GSTREAMER_SOUND
173         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
174
175         schanid_t s = g_new0(struct glk_schannel_struct, 1);
176         s->magic = MAGIC_SCHANNEL;
177         s->rock = rock;
178         if(glk_data->register_obj)
179                 s->disprock = (*glk_data->register_obj)(s, gidisp_Class_Schannel);
180
181         /* Add it to the global sound channel list */
182         glk_data->schannel_list = g_list_prepend(glk_data->schannel_list, s);
183         s->schannel_list = glk_data->schannel_list;
184
185         /* Create a GStreamer pipeline for the sound channel */
186         gchar *pipeline_name = g_strdup_printf("pipeline-%p", s);
187         s->pipeline = gst_pipeline_new(pipeline_name);
188         g_free(pipeline_name);
189
190         /* Watch for messages from the pipeline */
191         GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(s->pipeline));
192         gst_bus_add_signal_watch(bus);
193         g_signal_connect(bus, "message", G_CALLBACK(on_pipeline_message), s);
194         gst_object_unref(bus);
195
196         /* Create GStreamer elements to put in the pipeline */
197         s->source = gst_element_factory_make("giostreamsrc", NULL);
198         s->typefind = gst_element_factory_make("typefind", NULL);
199         s->convert = gst_element_factory_make("audioconvert", NULL);
200         s->filter = gst_element_factory_make("volume", NULL);
201         s->sink = gst_element_factory_make("autoaudiosink", NULL);
202         if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) {
203                 WARNING(_("Could not create one or more GStreamer elements"));
204                 goto fail;
205         }
206
207         /* Put the elements in the pipeline and link as many together as we can
208          without knowing the type of the audio stream */
209         gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
210         /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */
211         if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
212                 WARNING(_("Could not link GStreamer elements"));
213                 goto fail;
214         }
215         g_signal_connect(s->typefind, "have-type", G_CALLBACK(on_type_found), s);
216         
217         return s;
218
219 fail:
220         glk_schannel_destroy(s);
221         return NULL;
222 #else
223         return NULL;
224 #endif /* GSTREAMER_SOUND */
225 }
226
227 /**
228  * glk_schannel_destroy:
229  * @chan: The sound channel to destroy.
230  *
231  * Destroys the channel. If the channel is playing a sound, the sound stops 
232  * immediately (with no notification event).
233  */
234 void 
235 glk_schannel_destroy(schanid_t chan)
236 {
237         VALID_SCHANNEL(chan, return);
238
239 #ifdef GSTREAMER_SOUND
240         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
241
242         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
243                 WARNING_S(_("Could not set GstElement state to"), "NULL");
244         
245         glk_data->schannel_list = g_list_delete_link(glk_data->schannel_list, chan->schannel_list);
246
247         if(glk_data->unregister_obj)
248         {
249                 (*glk_data->unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
250                 chan->disprock.ptr = NULL;
251         }
252
253         /* This also frees all the objects inside the pipeline */
254         if(chan->pipeline)
255                 gst_object_unref(chan->pipeline);
256         
257         chan->magic = MAGIC_FREE;
258         g_free(chan);
259 #endif
260 }
261
262 /**
263  * glk_schannel_iterate:
264  * @chan: A sound channel, or %NULL.
265  * @rockptr: Return location for the next sound channel's rock, or %NULL.
266  *
267  * This function can be used to iterate through the list of all open channels.
268  * See <link linkend="chimara-Iterating-Through-Opaque-Objects">Iterating 
269  * Through Opaque Objects</link>.
270  *
271  * As that section describes, the order in which channels are returned is 
272  * arbitrary.
273  *
274  * Returns: the next sound channel, or %NULL if there are no more.
275  */
276 schanid_t 
277 glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
278 {
279         VALID_SCHANNEL_OR_NULL(chan, return NULL);
280
281 #ifdef GSTREAMER_SOUND
282         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
283         GList *retnode;
284         
285         if(chan == NULL)
286                 retnode = glk_data->schannel_list;
287         else
288                 retnode = chan->schannel_list->next;
289         schanid_t retval = retnode? (schanid_t)retnode->data : NULL;
290                 
291         /* Store the sound channel's rock in rockptr */
292         if(retval && rockptr)
293                 *rockptr = glk_schannel_get_rock(retval);
294                 
295         return retval;
296 #else
297         return NULL;
298 #endif /* GSTREAMER_SOUND */
299 }
300
301 /**
302  * glk_schannel_get_rock:
303  * @chan: A sound channel.
304  * 
305  * Retrieves the channel's rock value. See <link 
306  * linkend="chimara-Rocks">Rocks</link>.
307  *
308  * Returns: A rock value.
309  */
310 glui32 
311 glk_schannel_get_rock(schanid_t chan)
312 {
313         VALID_SCHANNEL(chan, return 0);
314         return chan->rock;
315 }
316
317 /**
318  * glk_schannel_play:
319  * @chan: Channel to play the sound in.
320  * @snd: Resource number of the sound to play.
321  *
322  * Begins playing the given sound on the channel. If the channel was already
323  * playing a sound (even the same one), the old sound is stopped (with no
324  * notification event.
325  *
326  * This returns 1 if the sound actually started playing, and 0 if there was any
327  * problem.
328  * <note><para>
329  *   The most obvious problem is if there is no sound resource with the given
330  *   identifier. But other problems can occur. For example, the MOD-playing 
331  *   facility in a library might be unable to handle two MODs at the same time,
332  *   in which case playing a MOD resource would fail if one was already playing.
333  * </para></note>
334  *
335  * Returns: 1 on success, 0 on failure.
336  */
337 glui32 
338 glk_schannel_play(schanid_t chan, glui32 snd)
339 {
340         return glk_schannel_play_ext(chan, snd, 1, 0);
341 }
342
343 /**
344  * glk_schannel_play_ext:
345  * @chan: Channel to play the sound in.
346  * @snd: Resource number of the sound to play.
347  * @repeats: Number of times to repeat the sound.
348  * @notify: If nonzero, requests a notification when the sound is finished.
349  *
350  * This works the same as glk_schannel_play(), but lets you specify additional 
351  * options. <code>glk_schannel_play(chan, snd)</code> is exactly equivalent to 
352  * <code>glk_schannel_play_ext(chan, snd, 1, 0)</code>.
353  * 
354  * The @repeats value is the number of times the sound should be repeated. A 
355  * repeat value of -1 (or rather 0xFFFFFFFF) means that the sound should repeat 
356  * forever. A repeat value of 0 means that the sound will not be played at all; 
357  * nothing happens. (Although a previous sound on the channel will be stopped, 
358  * and the function will return 1.)
359  * 
360  * The @notify value should be nonzero in order to request a sound notification
361  * event. If you do this, when the sound is completed, you will get an event 
362  * with type %evtype_SoundNotify. The @window will be %NULL, @val1 will be the 
363  * sound's resource id, and @val2 will be the nonzero value you passed as 
364  * @notify.
365  * 
366  * If you request sound notification, and the repeat value is greater than one, 
367  * you will get the event only after the last repetition. If the repeat value is
368  * 0 or -1, you will never get a notification event at all. Similarly, if the 
369  * sound is stopped or interrupted, or if the channel is destroyed while the 
370  * sound is playing, there will be no notification event.
371  *
372  * Not all libraries support sound notification. You should test the
373  * %gestalt_SoundNotify selector before you rely on it; see <link
374  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound 
375  * Capabilities</link>.
376  * 
377  * Returns: 1 on success, 0 on failure.
378  */
379 glui32 
380 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
381 {
382         VALID_SCHANNEL(chan, return 0);
383         g_printerr("Play sound %d with repeats %d and notify %d\n", snd, repeats, notify);
384 #ifdef GSTREAMER_SOUND
385         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
386         GInputStream *stream;
387
388         /* Stop the previous sound */
389         clean_up_after_playing_sound(chan);
390
391         /* Don't play if repeats = 0 */
392         if(repeats == 0) {
393                 chan->repeats = 0;
394                 return 1;
395         }
396
397         /* Load the sound into a GInputStream, by whatever method */
398         if(!glk_data->resource_map) {
399                 if(!glk_data->resource_load_callback) {
400                         WARNING(_("No resource map has been loaded yet."));
401                         return 0;
402                 }
403                 gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
404                 if(!filename) {
405                         WARNING(_("Error loading resource from alternative location."));
406                         return 0;
407                 }
408
409                 GError *err = NULL;
410                 GFile *file = g_file_new_for_path(filename);
411                 stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
412                 if(!stream) {
413                         IO_WARNING(_("Error loading resource from file"), filename, err->message);
414                         g_free(filename);
415                         g_object_unref(file);
416                         return 0;
417                 }
418                 g_free(filename);
419                 g_object_unref(file);
420         } else {
421                 giblorb_result_t resource;
422                 giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
423                 if(result != giblorb_err_None) {
424                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
425                         return 0;
426                 }
427                 stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
428         }
429
430         chan->repeats = repeats;
431         g_object_set(chan->source, "stream", stream, NULL);
432         
433         if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
434                 WARNING_S(_("Could not set GstElement state to"), "PLAYING");
435                 return 0;
436         }
437         return 1;
438 #else
439         return 0;
440 #endif
441 }
442
443 /**
444  * glk_schannel_stop:
445  * @chan: Channel to silence.
446  *
447  * Stops any sound playing in the channel. No notification event is generated,
448  * even if you requested one. If no sound is playing, this has no effect.
449  */
450 void 
451 glk_schannel_stop(schanid_t chan)
452 {
453         VALID_SCHANNEL(chan, return);
454 #ifdef GSTREAMER_SOUND
455         clean_up_after_playing_sound(chan);
456 #endif
457 }
458
459 /**
460  * glk_schannel_set_volume:
461  * @chan: Channel to set the volume of.
462  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
463  *
464  * Sets the volume in the channel. When you create a channel, it has full 
465  * volume, represented by the value 0x10000. Half volume would be 0x8000, 
466  * three-quarters volume would be 0xC000, and so on. A volume of zero represents
467  * silence, although the sound is still considered to be playing.
468  *
469  * You can call this function between sounds, or while a sound is playing. The 
470  * effect is immediate.
471  * 
472  * You can overdrive the volume of a channel by setting a volume greater than 
473  * 0x10000. However, this is not recommended; the library may be unable to 
474  * increase the volume past full, or the sound may become distorted. You should 
475  * always create sound resources with the maximum volume you will need, and then
476  * call glk_schannel_set_volume() to reduce the volume when appropriate.
477  *
478  * Not all libraries support this function. You should test the
479  * %gestalt_SoundVolume selector before you rely on it; see <link
480  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
481  * Capabilities</link>.
482  *
483  * <note><title>Chimara</title>
484  *   <para>Chimara supports volumes from 0 to 1000&percnt;, that is, values of
485  *   @vol up to 0xA0000.</para>
486  * </note>
487  */
488 void 
489 glk_schannel_set_volume(schanid_t chan, glui32 vol)
490 {
491         VALID_SCHANNEL(chan, return);
492 #ifdef GSTREAMER_SOUND
493         gdouble volume_gst = (gdouble)vol / 0x10000;
494         g_printerr("Volume set to: %f\n", volume_gst);
495         g_object_set(chan->filter, "volume", CLAMP(volume_gst, 0.0, 10.0), NULL);
496 #endif
497 }
498
499 /**
500  * glk_sound_load_hint:
501  * @snd: Resource number of a sound.
502  * @flag: Nonzero to tell the library to load the sound, zero to tell the
503  * library to unload it.
504  *
505  * This gives the library a hint about whether the given sound should be loaded
506  * or not. If the @flag is nonzero, the library may preload the sound or do
507  * other initialization, so that glk_schannel_play() will be faster. If the
508  * @flag is zero, the library may release memory or other resources associated
509  * with the sound. Calling this function is always optional, and it has no
510  * effect on what the library actually plays.
511  */
512 void 
513 glk_sound_load_hint(glui32 snd, glui32 flag)
514 {
515 #ifdef GSTREAMER_SOUND
516         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
517         giblorb_result_t resource;
518         giblorb_err_t result;
519
520         /* Sound load hints only work for Blorb resource maps */
521         if(!glk_data->resource_map)
522                 return;
523
524         if(flag) {
525                 /* The sound load hint simply loads the resource from the resource map;
526                  loading a chunk more than once does nothing */
527                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
528                 if(result != giblorb_err_None) {
529                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
530                         return;
531                 }
532         } else {
533                 /* Get the Blorb chunk number by loading the resource with
534                  method_DontLoad, then unload that chunk - has no effect if the chunk
535                  isn't loaded */
536                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_DontLoad, &resource, giblorb_ID_Snd, snd);
537                 if(result != giblorb_err_None) {
538                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
539                         return;
540                 }
541                 result = giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
542                 if(result != giblorb_err_None) {
543                         WARNING_S( _("Error unloading chunk"), giblorb_get_error_message(result) );
544                         return;
545                 }
546         }
547 #endif /* GSTREAMER_SOUND */
548 }