Merge branch 'master' of ssh://git.stderr.nl/projects/chimara/chimara
[projects/chimara/chimara.git] / libchimara / schannel.c
1 #include <config.h>
2 #include <glib.h>
3 #include <glib/gi18n-lib.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 #define VOLUME_TIMER_RESOLUTION 1.0 /* In milliseconds */
17
18 extern GPrivate *glk_data_key;
19
20 #ifdef GSTREAMER_SOUND
21 /* Stop any currently playing sound on this channel, and remove any
22  format-specific GStreamer elements from the channel. */
23 static void
24 clean_up_after_playing_sound(schanid_t chan)
25 {
26         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
27                 WARNING_S(_("Could not set GstElement state to"), "NULL");
28         if(chan->demux)
29         {
30                 gst_bin_remove(GST_BIN(chan->pipeline), chan->demux);
31                 chan->demux = NULL;
32         }
33         if(chan->decode)
34         {
35                 gst_bin_remove(GST_BIN(chan->pipeline), chan->decode);
36                 chan->decode = NULL;
37         }
38 }
39
40 /* This signal is thrown whenever the GStreamer pipeline generates a message.
41  Most messages are harmless. */
42 static void
43 on_pipeline_message(GstBus *bus, GstMessage *message, schanid_t s)
44 {
45         /* g_printerr("Got %s message\n", GST_MESSAGE_TYPE_NAME(message)); */
46
47         GError *err;
48         gchar *debug_message;
49         
50         switch(GST_MESSAGE_TYPE(message)) {
51         case GST_MESSAGE_ERROR: 
52         {
53                 gst_message_parse_error(message, &err, &debug_message);
54                 IO_WARNING(_("GStreamer error"), err->message, debug_message);
55                 g_error_free(err);
56                 g_free(debug_message);
57                 clean_up_after_playing_sound(s);
58         }
59                 break;
60         case GST_MESSAGE_WARNING:
61         {
62                 gst_message_parse_warning(message, &err, &debug_message);
63                 IO_WARNING(_("GStreamer warning"), err->message, debug_message);
64                 g_error_free(err);
65                 g_free(debug_message);
66         }
67                 break;
68         case GST_MESSAGE_INFO:
69         {
70                 gst_message_parse_info(message, &err, &debug_message);
71                 g_message("GStreamer info \"%s\": %s", err->message, debug_message);
72                 g_error_free(err);
73                 g_free(debug_message);
74         }
75                 break;
76         case GST_MESSAGE_EOS: /* End of stream */
77                 /* Decrease repeats if not set to forever */
78                 if(s->repeats != (glui32)-1)
79                         s->repeats--;
80                 if(s->repeats > 0) {
81                         if(!gst_element_seek_simple(s->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 0)) {
82                                 WARNING(_("Could not execute GStreamer seek"));
83                                 clean_up_after_playing_sound(s);
84                         }
85                 } else {
86                         clean_up_after_playing_sound(s);
87                         /* Sound ended normally, send a notification if requested */
88                         if(s->notify)
89                                 event_throw(s->glk, evtype_SoundNotify, NULL, s->resource, s->notify);
90                 }
91                 break;
92         default:
93                 /* unhandled message */
94                 break;
95         }
96 }
97
98 /* This signal is thrown when the OGG demuxer element has decided what kind of
99  outputs it will output. We connect the decoder element dynamically. */
100 static void
101 on_ogg_demuxer_pad_added(GstElement *demux, GstPad *pad, schanid_t s)
102 {
103         GstPad *sinkpad;
104         
105         /* We can now link this pad with the vorbis-decoder sink pad */
106         sinkpad = gst_element_get_static_pad(s->decode, "sink");
107         if(gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK)
108                 WARNING(_("Could not link OGG demuxer with Vorbis decoder"));
109         gst_object_unref(sinkpad);
110 }
111
112 /* This signal is thrown when the typefinder element has found the type of its
113  input. Now that we know what kind of input stream we have, we can connect the
114  proper demuxer/decoder elements. */
115 static void
116 on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t s)
117 {
118         gchar *type = gst_caps_to_string(caps);
119         if(strcmp(type, "application/ogg") == 0) {
120                 s->demux = gst_element_factory_make("oggdemux", NULL);
121                 s->decode = gst_element_factory_make("vorbisdec", NULL);
122                 if(!s->demux || !s->decode) {
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                         WARNING(_("Could not link GStreamer elements"));
129                         goto finally;
130                 }
131                 /* We link the demuxer and decoder together dynamically, since the
132                  demuxer doesn't know what source pads it will have until it starts
133                  demuxing the stream */
134                 g_signal_connect(s->demux, "pad-added", G_CALLBACK(on_ogg_demuxer_pad_added), s);
135         } else if(strcmp(type, "audio/x-aiff") == 0) {
136                 s->decode = gst_element_factory_make("aiffparse", NULL);
137                 if(!s->decode) {
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                         WARNING(_("Could not link GStreamer elements"));
144                         goto finally;
145                 }
146         } else if(strcmp(type, "audio/x-mod") == 0) {
147                 s->decode = gst_element_factory_make("modplug", NULL);
148                 if(!s->decode) {
149                         WARNING(_("Could not create 'modplug' GStreamer element"));
150                         goto finally;
151                 }
152                 gst_bin_add(GST_BIN(s->pipeline), s->decode);
153                 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
154                         WARNING(_("Could not link GStreamer elements"));
155                         goto finally;
156                 }
157         } else {
158                 WARNING_S(_("Unexpected audio type in blorb"), type);
159         }
160
161 finally:
162         g_free(type);
163 }
164 #endif /* GSTREAMER_SOUND */
165
166 /**
167  * glk_schannel_create:
168  * @rock: The rock value to give the new sound channel.
169  *
170  * This creates a sound channel, about as you'd expect.
171  *
172  * Remember that it is possible that the library will be unable to create a new
173  * channel, in which case glk_schannel_create() will return %NULL.
174  *
175  * When you create a channel using glk_schannel_create(), it has full volume,
176  * represented by the value 0x10000. Half volume would be 0x8000, three-quarters
177  * volume would be 0xC000, and so on. A volume of zero represents silence.
178  *
179  * You can overdrive the volume of a channel by setting a volume greater than 
180  * 0x10000. However, this is not recommended; the library may be unable to 
181  * increase the volume past full, or the sound may become distorted. You should 
182  * always create sound resources with the maximum volume you will need, and then
183  * reduce the volume when appropriate using the channel-volume calls.
184  *
185  * <note><para>
186  *   Mathematically, these volume changes should be taken as linear
187  *   multiplication of a waveform represented as linear samples. As I
188  *   understand it, linear PCM encodes the sound pressure, and therefore a
189  *   volume of 0x8000 should represent a 6 dB drop.
190  * </para></note>
191  *
192  * Returns: A new sound channel, or %NULL.
193  */
194 schanid_t 
195 glk_schannel_create(glui32 rock)
196 {
197         return glk_schannel_create_ext(rock, 0x10000);
198 }
199
200 /**
201  * glk_schannel_create_ext:
202  * @rock: The rock value to give the new sound channel.
203  * @volume: Integer representing the volume; 0x10000 is 100&percnt;.
204  *
205  * The glk_schannel_create_ext() call lets you create a channel with the volume
206  * already set at a given level.
207  *
208  * Not all libraries support glk_schannel_create_ext(). You should test the
209  * %gestalt_Sound2 selector before you rely on it; see <link
210  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
211  * Capabilities</link>.
212  *
213  * Returns: A new sound channel, or %NULL.
214  */
215 schanid_t
216 glk_schannel_create_ext(glui32 rock, glui32 volume)
217 {
218 #ifdef GSTREAMER_SOUND
219         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
220
221         schanid_t s = g_new0(struct glk_schannel_struct, 1);
222         s->magic = MAGIC_SCHANNEL;
223         s->rock = rock;
224         if(glk_data->register_obj)
225                 s->disprock = (*glk_data->register_obj)(s, gidisp_Class_Schannel);
226
227         /* Add it to the global sound channel list */
228         glk_data->schannel_list = g_list_prepend(glk_data->schannel_list, s);
229         s->schannel_list = glk_data->schannel_list;
230
231         /* Add a pointer to the ChimaraGlk widget, for convenience */
232         s->glk = glk_data->self;
233
234         /* Create a GStreamer pipeline for the sound channel */
235         gchar *pipeline_name = g_strdup_printf("pipeline-%p", s);
236         s->pipeline = gst_pipeline_new(pipeline_name);
237         g_free(pipeline_name);
238
239         /* Watch for messages from the pipeline */
240         GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(s->pipeline));
241         gst_bus_add_signal_watch(bus);
242         g_signal_connect(bus, "message", G_CALLBACK(on_pipeline_message), s);
243         gst_object_unref(bus);
244
245         /* Create GStreamer elements to put in the pipeline */
246         s->source = gst_element_factory_make("giostreamsrc", NULL);
247         s->typefind = gst_element_factory_make("typefind", NULL);
248         s->convert = gst_element_factory_make("audioconvert", NULL);
249         s->filter = gst_element_factory_make("volume", NULL);
250         s->sink = gst_element_factory_make("autoaudiosink", NULL);
251         if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) {
252                 WARNING(_("Could not create one or more GStreamer elements"));
253                 goto fail;
254         }
255
256         /* Set the initial volume */
257         glk_schannel_set_volume(s, volume);
258
259         /* Put the elements in the pipeline and link as many together as we can
260          without knowing the type of the audio stream */
261         gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
262         /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */
263         if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
264                 WARNING(_("Could not link GStreamer elements"));
265                 goto fail;
266         }
267         g_signal_connect(s->typefind, "have-type", G_CALLBACK(on_type_found), s);
268         
269         return s;
270
271 fail:
272         glk_schannel_destroy(s);
273         return NULL;
274 #else
275         return NULL;
276 #endif /* GSTREAMER_SOUND */
277 }
278
279 /**
280  * glk_schannel_destroy:
281  * @chan: The sound channel to destroy.
282  *
283  * Destroys the channel. If the channel is playing a sound, the sound stops 
284  * immediately (with no notification event).
285  */
286 void 
287 glk_schannel_destroy(schanid_t chan)
288 {
289         VALID_SCHANNEL(chan, return);
290
291 #ifdef GSTREAMER_SOUND
292         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
293
294         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
295                 WARNING_S(_("Could not set GstElement state to"), "NULL");
296         
297         glk_data->schannel_list = g_list_delete_link(glk_data->schannel_list, chan->schannel_list);
298
299         if(glk_data->unregister_obj)
300         {
301                 (*glk_data->unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
302                 chan->disprock.ptr = NULL;
303         }
304
305         /* This also frees all the objects inside the pipeline */
306         if(chan->pipeline)
307                 gst_object_unref(chan->pipeline);
308         
309         chan->magic = MAGIC_FREE;
310         g_free(chan);
311 #endif
312 }
313
314 /**
315  * glk_schannel_iterate:
316  * @chan: A sound channel, or %NULL.
317  * @rockptr: Return location for the next sound channel's rock, or %NULL.
318  *
319  * This function can be used to iterate through the list of all open channels.
320  * See <link linkend="chimara-Iterating-Through-Opaque-Objects">Iterating 
321  * Through Opaque Objects</link>.
322  *
323  * As that section describes, the order in which channels are returned is 
324  * arbitrary.
325  *
326  * Returns: the next sound channel, or %NULL if there are no more.
327  */
328 schanid_t 
329 glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
330 {
331         VALID_SCHANNEL_OR_NULL(chan, return NULL);
332
333 #ifdef GSTREAMER_SOUND
334         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
335         GList *retnode;
336         
337         if(chan == NULL)
338                 retnode = glk_data->schannel_list;
339         else
340                 retnode = chan->schannel_list->next;
341         schanid_t retval = retnode? (schanid_t)retnode->data : NULL;
342                 
343         /* Store the sound channel's rock in rockptr */
344         if(retval && rockptr)
345                 *rockptr = glk_schannel_get_rock(retval);
346                 
347         return retval;
348 #else
349         return NULL;
350 #endif /* GSTREAMER_SOUND */
351 }
352
353 /**
354  * glk_schannel_get_rock:
355  * @chan: A sound channel.
356  * 
357  * Retrieves the channel's rock value. See <link 
358  * linkend="chimara-Rocks">Rocks</link>.
359  *
360  * Returns: A rock value.
361  */
362 glui32 
363 glk_schannel_get_rock(schanid_t chan)
364 {
365         VALID_SCHANNEL(chan, return 0);
366         return chan->rock;
367 }
368
369 /**
370  * glk_schannel_play:
371  * @chan: Channel to play the sound in.
372  * @snd: Resource number of the sound to play.
373  *
374  * Begins playing the given sound on the channel. If the channel was already
375  * playing a sound (even the same one), the old sound is stopped (with no
376  * notification event.
377  *
378  * This returns 1 if the sound actually started playing, and 0 if there was any
379  * problem.
380  * <note><para>
381  *   The most obvious problem is if there is no sound resource with the given
382  *   identifier. But other problems can occur. For example, the MOD-playing 
383  *   facility in a library might be unable to handle two MODs at the same time,
384  *   in which case playing a MOD resource would fail if one was already playing.
385  * </para></note>
386  *
387  * Returns: 1 on success, 0 on failure.
388  */
389 glui32 
390 glk_schannel_play(schanid_t chan, glui32 snd)
391 {
392         return glk_schannel_play_ext(chan, snd, 1, 0);
393 }
394
395 /**
396  * glk_schannel_play_ext:
397  * @chan: Channel to play the sound in.
398  * @snd: Resource number of the sound to play.
399  * @repeats: Number of times to repeat the sound.
400  * @notify: If nonzero, requests a notification when the sound is finished.
401  *
402  * This works the same as glk_schannel_play(), but lets you specify additional 
403  * options. <code>glk_schannel_play(chan, snd)</code> is exactly equivalent to 
404  * <code>glk_schannel_play_ext(chan, snd, 1, 0)</code>.
405  * 
406  * The @repeats value is the number of times the sound should be repeated. A 
407  * repeat value of -1 (or rather 0xFFFFFFFF) means that the sound should repeat 
408  * forever. A repeat value of 0 means that the sound will not be played at all; 
409  * nothing happens. (Although a previous sound on the channel will be stopped, 
410  * and the function will return 1.)
411  * 
412  * The @notify value should be nonzero in order to request a sound notification
413  * event. If you do this, when the sound is completed, you will get an event 
414  * with type %evtype_SoundNotify. The @window will be %NULL, @val1 will be the 
415  * sound's resource id, and @val2 will be the nonzero value you passed as 
416  * @notify.
417  * 
418  * If you request sound notification, and the repeat value is greater than one, 
419  * you will get the event only after the last repetition. If the repeat value is
420  * 0 or -1, you will never get a notification event at all. Similarly, if the 
421  * sound is stopped or interrupted, or if the channel is destroyed while the 
422  * sound is playing, there will be no notification event.
423  *
424  * Not all libraries support sound notification. You should test the
425  * %gestalt_Sound2 selector before you rely on it; see <link
426  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound 
427  * Capabilities</link>.
428  *
429  * Note that you can play a sound on a channel whose volume is zero. This has
430  * no audible result, unless you later change the volume; but it produces
431  * notifications as usual. You can also play a sound on a paused channel; the
432  * sound is paused immediately, and does not progress.
433  * 
434  * Returns: 1 on success, 0 on failure.
435  */
436 glui32 
437 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
438 {
439         VALID_SCHANNEL(chan, return 0);
440 #ifdef GSTREAMER_SOUND
441         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
442         GInputStream *stream;
443
444         /* Stop the previous sound */
445         clean_up_after_playing_sound(chan);
446
447         /* Don't play if repeats = 0 */
448         if(repeats == 0) {
449                 chan->repeats = 0;
450                 return 1;
451         }
452
453         /* Load the sound into a GInputStream, by whatever method */
454         if(!glk_data->resource_map) {
455                 if(!glk_data->resource_load_callback) {
456                         WARNING(_("No resource map has been loaded yet."));
457                         return 0;
458                 }
459                 gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
460                 if(!filename) {
461                         WARNING(_("Error loading resource from alternative location."));
462                         return 0;
463                 }
464
465                 GError *err = NULL;
466                 GFile *file = g_file_new_for_path(filename);
467                 stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
468                 if(!stream) {
469                         IO_WARNING(_("Error loading resource from file"), filename, err->message);
470                         g_free(filename);
471                         g_object_unref(file);
472                         return 0;
473                 }
474                 g_free(filename);
475                 g_object_unref(file);
476         } else {
477                 giblorb_result_t resource;
478                 giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
479                 if(result != giblorb_err_None) {
480                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
481                         return 0;
482                 }
483                 stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
484         }
485
486         chan->repeats = repeats;
487         chan->resource = snd;
488         chan->notify = notify;
489         g_object_set(chan->source, "stream", stream, NULL);
490         g_object_unref(stream); /* Now owned by GStreamer element */
491         
492         /* Play the sound; unless the channel is paused, then pause it instead */
493         if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
494                 WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING");
495                 return 0;
496         }
497         return 1;
498 #else
499         return 0;
500 #endif
501 }
502
503 /**
504  * glk_schannel_play_multi:
505  * @chanarray: Array of #schanid_t structures.
506  * @chancount: Length of @chanarray.
507  * @sndarray: Array of sound resource numbers.
508  * @soundcount: Length of @sndarray, must be equal to @chanarray.
509  * @notify: If nonzero, request a notification when each sound finishes.
510  *
511  * This works the same as glk_schannel_play_ext(), except that you can specify
512  * more than one sound. The channel references and sound resource numbers are
513  * given as two arrays, which must be the same length. The @notify argument
514  * applies to all the sounds; the repeats value for all the sounds is 1.
515  * 
516  * All the sounds will begin at exactly the same time.
517  * 
518  * This returns the number of sounds that began playing correctly. (This will be
519  * a number from 0 to @soundcount.)
520  *
521  * <note><para>
522  *   If the @notify argument is nonzero, you will get a separate sound
523  *   notification event as each sound finishes. They will all have the same
524  *   @val2 value.
525  * </para></note>
526  * <note><para>
527  *   Note that you have to supply @chancount and @soundcount as separate
528  *   arguments, even though they are required to be the same. This is an awkward
529  *   consequence of the way array arguments are dispatched in Glulx.
530  * </para></note>
531  * 
532  * Returns: The number of sounds that started playing correctly.
533  */
534 glui32
535 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray, glui32 soundcount, glui32 notify)
536 {
537         g_return_val_if_fail(chancount == soundcount, 0);
538         g_return_val_if_fail(chanarray != NULL || chancount == 0, 0);
539         g_return_val_if_fail(sndarray != NULL || soundcount == 0, 0);
540
541         int count;
542         for(count = 0; count < chancount; count++)
543                 VALID_SCHANNEL(chanarray[count], return 0);
544
545 #ifdef GSTREAMER_SOUND
546         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
547         GInputStream *stream;
548
549         if(!glk_data->resource_map && !glk_data->resource_load_callback) {
550                 WARNING(_("No resource map has been loaded yet."));
551                 return 0;
552         }
553
554         /* We keep an array of sounds to skip if any of them have errors */
555         gboolean *skiparray = g_new0(gboolean, chancount);
556
557         /* Set up all the channels one by one */
558         for(count = 0; count < chancount; count++) {
559                 /* Stop the previous sound */
560                 clean_up_after_playing_sound(chanarray[count]);
561
562                 /* Load the sound into a GInputStream, by whatever method */
563                 if(!glk_data->resource_map) {
564                         gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, sndarray[count], glk_data->resource_load_callback_data);
565                         if(!filename) {
566                                 WARNING(_("Error loading resource from alternative location."));
567                                 skiparray[count] = TRUE;
568                                 continue;
569                         }
570
571                         GError *err = NULL;
572                         GFile *file = g_file_new_for_path(filename);
573                         stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
574                         if(!stream) {
575                                 IO_WARNING(_("Error loading resource from file"), filename, err->message);
576                                 g_free(filename);
577                                 g_object_unref(file);
578                                 skiparray[count] = TRUE;
579                                 continue;
580                         }
581                         g_free(filename);
582                         g_object_unref(file);
583                 } else {
584                         giblorb_result_t resource;
585                         giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, sndarray[count]);
586                         if(result != giblorb_err_None) {
587                                 WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
588                                 skiparray[count] = TRUE;
589                                 continue;
590                         }
591                         stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
592                 }
593
594                 chanarray[count]->repeats = 1;
595                 chanarray[count]->resource = sndarray[count];
596                 chanarray[count]->notify = notify;
597                 g_object_set(chanarray[count]->source, "stream", stream, NULL);
598                 g_object_unref(stream); /* Now owned by GStreamer element */
599         }
600
601         /* Start all the sounds as close to each other as possible. */
602         /* FIXME: Is there a way to start them exactly at the same time? */
603         glui32 successes = 0;
604         for(count = 0; count < chancount; count++) {
605                 if(skiparray[count])
606                         continue;
607                 /* Play the sound; unless the channel is paused, then pause it instead */
608                 if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
609                         WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING");
610                         skiparray[count] = TRUE;
611                         continue;
612                 }
613                 successes++;
614         }
615         g_free(skiparray);
616         return successes;
617 #else
618         return 0;
619 #endif
620 }
621
622 /**
623  * glk_schannel_stop:
624  * @chan: Channel to silence.
625  *
626  * Stops any sound playing in the channel. No notification event is generated,
627  * even if you requested one. If no sound is playing, this has no effect.
628  */
629 void 
630 glk_schannel_stop(schanid_t chan)
631 {
632         VALID_SCHANNEL(chan, return);
633 #ifdef GSTREAMER_SOUND
634         clean_up_after_playing_sound(chan);
635 #endif
636 }
637
638 /**
639  * glk_schannel_pause:
640  * @chan: Channel to pause.
641  *
642  * Pause any sound playing in the channel. This does not generate any
643  * notification events. If the channel is already paused, this does nothing.
644  * 
645  * New sounds started in a paused channel are paused immediately.
646  * 
647  * A volume change in progress is <emphasis>not</emphasis> paused, and may
648  * proceed to completion, generating a notification if appropriate.
649  */
650 void
651 glk_schannel_pause(schanid_t chan)
652 {
653         VALID_SCHANNEL(chan, return);
654
655         if(chan->paused)
656                 return; /* Silently do nothing */
657
658         /* Mark the channel as paused even if there is no sound playing yet */
659         chan->paused = TRUE;
660
661         GstState state;
662         if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
663                 WARNING(_("Could not get GstElement state"));
664                 return;
665         }
666         if(state != GST_STATE_PLAYING)
667                 return; /* Silently do nothing if no sound is playing */
668
669         if(!gst_element_set_state(chan->pipeline, GST_STATE_PAUSED)) {
670                 WARNING_S(_("Could not set GstElement state to"), "PAUSED");
671                 return;
672         }
673 }
674
675 /**
676  * glk_schannel_unpause:
677  * @chan: Channel to unpause.
678  *
679  * Unpause the channel. Any paused sounds begin playing where they left off. If
680  * the channel is not already paused, this does nothing.
681  *
682  * <note><para>
683  *   This means, for example, that you can pause a channel that is currently
684  *   not playing any sounds. If you then add a sound to the channel, it will
685  *   not start playing; it will be paused at its beginning. If you later
686  *   unpaise the channel, the sound will commence.
687  * </para></note>
688  */
689 void
690 glk_schannel_unpause(schanid_t chan)
691 {
692         VALID_SCHANNEL(chan, return);
693
694         if(!chan->paused)
695                 return; /* Silently do nothing */
696
697         /* Mark the channel as not paused in any case */
698         chan->paused = FALSE;
699
700         GstState state;
701         if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
702                 WARNING(_("Could not get GstElement state"));
703                 return;
704         }
705         if(state != GST_STATE_PAUSED)
706                 return; /* Silently do nothing */
707
708         if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
709                 WARNING_S(_("Could not set GstElement state to"), "PLAYING");
710                 return;
711         }
712 }
713
714 /**
715  * glk_schannel_set_volume:
716  * @chan: Channel to set the volume of.
717  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
718  *
719  * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
720  * Again, you can overdrive the volume by setting a value greater than 0x10000,
721  * but this is not recommended.
722  *
723  * The glk_schannel_set_volume() function does not include duration and notify
724  * values. Both are assumed to be zero: immediate change, no notification.
725  *
726  * You can call this function between sounds, or while a sound is playing.
727  * However, a zero-duration change while a sound is playing may produce
728  * unpleasant clicks.
729  * 
730  * At most one volume change can be occurring on a sound channel at any time.
731  * If you call this function while a previous volume change is in progress, the
732  * previous change is interrupted.
733  *
734  * Not all libraries support this function. You should test the
735  * %gestalt_SoundVolume selector before you rely on it; see <link
736  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
737  * Capabilities</link>.
738  *
739  * <note><title>Chimara</title>
740  *   <para>Chimara supports volumes from 0 to 1000&percnt;, that is, values of
741  *   @vol up to 0xA0000.</para>
742  * </note>
743  */
744 void 
745 glk_schannel_set_volume(schanid_t chan, glui32 vol)
746 {
747         glk_schannel_set_volume_ext(chan, vol, 0, 0);
748 }
749
750 static double
751 volume_glk_to_gstreamer(glui32 volume_glk)
752 {
753         return CLAMP(((double)volume_glk / 0x10000), 0.0, 10.0);
754 }
755
756 #ifdef GSTREAMER_SOUND
757 static gboolean
758 volume_change_timeout(schanid_t chan)
759 {
760         GTimeVal now;
761         g_get_current_time(&now);
762
763         if(now.tv_sec >= chan->target_time_sec && now.tv_usec >= chan->target_time_usec) {
764                 /* We're done - make sure the volume is at the requested level */
765                 g_object_set(chan->filter, "volume", chan->target_volume, NULL);
766
767                 if(chan->volume_notify)
768                         event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, chan->volume_notify);
769
770                 chan->volume_timer_id = 0;
771                 return FALSE;
772         }
773
774         /* Calculate the appropriate step every time - a busy system may delay or
775          * drop timer ticks */
776         double time_left_msec = (chan->target_time_sec - now.tv_sec) * 1000.0
777                 + (chan->target_time_usec - now.tv_usec) / 1000.0;
778         double steps_left = time_left_msec / VOLUME_TIMER_RESOLUTION;
779         double current_volume;
780         g_object_get(chan->filter, "volume", &current_volume, NULL);
781         double volume_step = (chan->target_volume - current_volume) / steps_left;
782
783         g_object_set(chan->filter, "volume", current_volume + volume_step, NULL);
784
785         return TRUE;
786 }
787 #endif /* GSTREAMER_SOUND */
788
789 /**
790  * glk_schannel_set_volume_ext:
791  * @chan: Channel to set the volume of.
792  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
793  * @duration: Length of volume change in milliseconds, or 0 for immediate.
794  * @notify: If nonzero, requests a notification when the volume change finishes.
795  *
796  * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
797  * Again, you can overdrive the volume by setting a value greater than 0x10000,
798  * but this is not recommended.
799  *
800  * If the @duration is zero, the change is immediate. Otherwise, the change
801  * begins immediately, and occurs smoothly over the next @duration milliseconds.
802  *
803  * The @notify value should be nonzero in order to request a volume notification
804  * event. If you do this, when the volume change is completed, you will get an
805  * event with type #evtype_VolumeNotify. The window will be %NULL, @val1 will be
806  * zero, and @val2 will be the nonzero value you passed as @notify.
807  *
808  * You can call this function between sounds, or while a sound is playing.
809  * However, a zero-duration change while a sound is playing may produce
810  * unpleasant clicks.
811  *
812  * At most one volume change can be occurring on a sound channel at any time. If
813  * you call this function while a previous volume change is in progress, the
814  * previous change is interrupted. The beginning point of the new volume change
815  * should be wherever the previous volume change was interrupted (rather than
816  * the previous change's beginning or ending point).
817  *
818  * Not all libraries support these functions. You should test the appropriate
819  * gestalt selectors before you rely on them; see "Testing for Sound
820  * Capabilities".
821  */
822 void
823 glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32 notify)
824 {
825         VALID_SCHANNEL(chan, return);
826         /* Silently ignore out-of-range volume values */
827
828 #ifdef GSTREAMER_SOUND
829         /* Interrupt a previous volume change */
830         if(chan->volume_timer_id > 0)
831                 g_source_remove(chan->volume_timer_id);
832         
833         double target_volume = volume_glk_to_gstreamer(vol);
834
835         if(duration == 0) {
836                 g_object_set(chan->filter, "volume", target_volume, NULL);
837
838                 if(notify != 0)
839                         event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, notify);
840
841                 return;
842         }
843
844         GTimeVal target_time;
845         g_get_current_time(&target_time);
846         g_time_val_add(&target_time, (long)duration * 1000);
847
848         chan->target_volume = target_volume;
849         chan->target_time_sec = target_time.tv_sec;
850         chan->target_time_usec = target_time.tv_usec;
851         chan->volume_notify = notify;
852
853         /* Set up a timer for the volume */
854         chan->volume_timer_id = g_timeout_add(VOLUME_TIMER_RESOLUTION, (GSourceFunc)volume_change_timeout, chan);
855 #endif
856 }
857
858 /**
859  * glk_sound_load_hint:
860  * @snd: Resource number of a sound.
861  * @flag: Nonzero to tell the library to load the sound, zero to tell the
862  * library to unload it.
863  *
864  * This gives the library a hint about whether the given sound should be loaded
865  * or not. If the @flag is nonzero, the library may preload the sound or do
866  * other initialization, so that glk_schannel_play() will be faster. If the
867  * @flag is zero, the library may release memory or other resources associated
868  * with the sound. Calling this function is always optional, and it has no
869  * effect on what the library actually plays.
870  */
871 void 
872 glk_sound_load_hint(glui32 snd, glui32 flag)
873 {
874 #ifdef GSTREAMER_SOUND
875         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
876         giblorb_result_t resource;
877         giblorb_err_t result;
878
879         /* Sound load hints only work for Blorb resource maps */
880         if(!glk_data->resource_map)
881                 return;
882
883         if(flag) {
884                 /* The sound load hint simply loads the resource from the resource map;
885                  loading a chunk more than once does nothing */
886                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
887                 if(result != giblorb_err_None) {
888                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
889                         return;
890                 }
891         } else {
892                 /* Get the Blorb chunk number by loading the resource with
893                  method_DontLoad, then unload that chunk - has no effect if the chunk
894                  isn't loaded */
895                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_DontLoad, &resource, giblorb_ID_Snd, snd);
896                 if(result != giblorb_err_None) {
897                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
898                         return;
899                 }
900                 result = giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
901                 if(result != giblorb_err_None) {
902                         WARNING_S( _("Error unloading chunk"), giblorb_get_error_message(result) );
903                         return;
904                 }
905         }
906 #endif /* GSTREAMER_SOUND */
907 }