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