Implemented glk_schannel_play_multi()
[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 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                 s->demux = gst_element_factory_make("oggdemux", NULL);
119                 s->decode = gst_element_factory_make("vorbisdec", NULL);
120                 if(!s->demux || !s->decode) {
121                         WARNING(_("Could not create one or more GStreamer elements"));
122                         goto finally;
123                 }
124                 gst_bin_add_many(GST_BIN(s->pipeline), s->demux, s->decode, NULL);
125                 if(!gst_element_link(s->typefind, s->demux) || !gst_element_link(s->decode, s->convert)) {
126                         WARNING(_("Could not link GStreamer elements"));
127                         goto finally;
128                 }
129                 /* We link the demuxer and decoder together dynamically, since the
130                  demuxer doesn't know what source pads it will have until it starts
131                  demuxing the stream */
132                 g_signal_connect(s->demux, "pad-added", G_CALLBACK(on_ogg_demuxer_pad_added), s);
133         } else if(strcmp(type, "audio/x-aiff") == 0) {
134                 s->decode = gst_element_factory_make("aiffparse", NULL);
135                 if(!s->decode) {
136                         WARNING(_("Could not create 'aiffparse' GStreamer element"));
137                         goto finally;
138                 }
139                 gst_bin_add(GST_BIN(s->pipeline), s->decode);
140                 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
141                         WARNING(_("Could not link GStreamer elements"));
142                         goto finally;
143                 }
144         } else if(strcmp(type, "audio/x-mod") == 0) {
145                 s->decode = gst_element_factory_make("modplug", NULL);
146                 if(!s->decode) {
147                         WARNING(_("Could not create 'modplug' GStreamer element"));
148                         goto finally;
149                 }
150                 gst_bin_add(GST_BIN(s->pipeline), s->decode);
151                 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
152                         WARNING(_("Could not link GStreamer elements"));
153                         goto finally;
154                 }
155         } else {
156                 WARNING_S(_("Unexpected audio type in blorb"), type);
157         }
158
159 finally:
160         g_free(type);
161 }
162 #endif /* GSTREAMER_SOUND */
163
164 /**
165  * glk_schannel_create:
166  * @rock: The rock value to give the new sound channel.
167  *
168  * This creates a sound channel, about as you'd expect.
169  *
170  * Remember that it is possible that the library will be unable to create a new
171  * channel, in which case glk_schannel_create() will return %NULL.
172  *
173  * Returns: A new sound channel, or %NULL.
174  */
175 schanid_t 
176 glk_schannel_create(glui32 rock)
177 {
178         return glk_schannel_create_ext(rock, 0x10000);
179 }
180
181 /**
182  * glk_schannel_create_ext:
183  * @rock: The rock value to give the new sound channel.
184  * @volume: Integer representing the volume; 0x10000 is 100&percnt;.
185  *
186  * [DRAFT SPEC]
187  *
188  * The glk_schannel_create_ext() call lets you create a channel with the volume
189  * already set at a given level.
190  *
191  * Returns: A new sound channel, or %NULL.
192  */
193 schanid_t
194 glk_schannel_create_ext(glui32 rock, glui32 volume)
195 {
196 #ifdef GSTREAMER_SOUND
197         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
198
199         schanid_t s = g_new0(struct glk_schannel_struct, 1);
200         s->magic = MAGIC_SCHANNEL;
201         s->rock = rock;
202         if(glk_data->register_obj)
203                 s->disprock = (*glk_data->register_obj)(s, gidisp_Class_Schannel);
204
205         /* Add it to the global sound channel list */
206         glk_data->schannel_list = g_list_prepend(glk_data->schannel_list, s);
207         s->schannel_list = glk_data->schannel_list;
208
209         /* Add a pointer to the ChimaraGlk widget, for convenience */
210         s->glk = glk_data->self;
211
212         /* Create a GStreamer pipeline for the sound channel */
213         gchar *pipeline_name = g_strdup_printf("pipeline-%p", s);
214         s->pipeline = gst_pipeline_new(pipeline_name);
215         g_free(pipeline_name);
216
217         /* Watch for messages from the pipeline */
218         GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(s->pipeline));
219         gst_bus_add_signal_watch(bus);
220         g_signal_connect(bus, "message", G_CALLBACK(on_pipeline_message), s);
221         gst_object_unref(bus);
222
223         /* Create GStreamer elements to put in the pipeline */
224         s->source = gst_element_factory_make("giostreamsrc", NULL);
225         s->typefind = gst_element_factory_make("typefind", NULL);
226         s->convert = gst_element_factory_make("audioconvert", NULL);
227         s->filter = gst_element_factory_make("volume", NULL);
228         s->sink = gst_element_factory_make("autoaudiosink", NULL);
229         if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) {
230                 WARNING(_("Could not create one or more GStreamer elements"));
231                 goto fail;
232         }
233
234         /* Set the initial volume */
235         glk_schannel_set_volume(s, volume);
236
237         /* Put the elements in the pipeline and link as many together as we can
238          without knowing the type of the audio stream */
239         gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
240         /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */
241         if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
242                 WARNING(_("Could not link GStreamer elements"));
243                 goto fail;
244         }
245         g_signal_connect(s->typefind, "have-type", G_CALLBACK(on_type_found), s);
246         
247         return s;
248
249 fail:
250         glk_schannel_destroy(s);
251         return NULL;
252 #else
253         return NULL;
254 #endif /* GSTREAMER_SOUND */
255 }
256
257 /**
258  * glk_schannel_destroy:
259  * @chan: The sound channel to destroy.
260  *
261  * Destroys the channel. If the channel is playing a sound, the sound stops 
262  * immediately (with no notification event).
263  */
264 void 
265 glk_schannel_destroy(schanid_t chan)
266 {
267         VALID_SCHANNEL(chan, return);
268
269 #ifdef GSTREAMER_SOUND
270         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
271
272         if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
273                 WARNING_S(_("Could not set GstElement state to"), "NULL");
274         
275         glk_data->schannel_list = g_list_delete_link(glk_data->schannel_list, chan->schannel_list);
276
277         if(glk_data->unregister_obj)
278         {
279                 (*glk_data->unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
280                 chan->disprock.ptr = NULL;
281         }
282
283         /* This also frees all the objects inside the pipeline */
284         if(chan->pipeline)
285                 gst_object_unref(chan->pipeline);
286         
287         chan->magic = MAGIC_FREE;
288         g_free(chan);
289 #endif
290 }
291
292 /**
293  * glk_schannel_iterate:
294  * @chan: A sound channel, or %NULL.
295  * @rockptr: Return location for the next sound channel's rock, or %NULL.
296  *
297  * This function can be used to iterate through the list of all open channels.
298  * See <link linkend="chimara-Iterating-Through-Opaque-Objects">Iterating 
299  * Through Opaque Objects</link>.
300  *
301  * As that section describes, the order in which channels are returned is 
302  * arbitrary.
303  *
304  * Returns: the next sound channel, or %NULL if there are no more.
305  */
306 schanid_t 
307 glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
308 {
309         VALID_SCHANNEL_OR_NULL(chan, return NULL);
310
311 #ifdef GSTREAMER_SOUND
312         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
313         GList *retnode;
314         
315         if(chan == NULL)
316                 retnode = glk_data->schannel_list;
317         else
318                 retnode = chan->schannel_list->next;
319         schanid_t retval = retnode? (schanid_t)retnode->data : NULL;
320                 
321         /* Store the sound channel's rock in rockptr */
322         if(retval && rockptr)
323                 *rockptr = glk_schannel_get_rock(retval);
324                 
325         return retval;
326 #else
327         return NULL;
328 #endif /* GSTREAMER_SOUND */
329 }
330
331 /**
332  * glk_schannel_get_rock:
333  * @chan: A sound channel.
334  * 
335  * Retrieves the channel's rock value. See <link 
336  * linkend="chimara-Rocks">Rocks</link>.
337  *
338  * Returns: A rock value.
339  */
340 glui32 
341 glk_schannel_get_rock(schanid_t chan)
342 {
343         VALID_SCHANNEL(chan, return 0);
344         return chan->rock;
345 }
346
347 /**
348  * glk_schannel_play:
349  * @chan: Channel to play the sound in.
350  * @snd: Resource number of the sound to play.
351  *
352  * Begins playing the given sound on the channel. If the channel was already
353  * playing a sound (even the same one), the old sound is stopped (with no
354  * notification event.
355  *
356  * This returns 1 if the sound actually started playing, and 0 if there was any
357  * problem.
358  * <note><para>
359  *   The most obvious problem is if there is no sound resource with the given
360  *   identifier. But other problems can occur. For example, the MOD-playing 
361  *   facility in a library might be unable to handle two MODs at the same time,
362  *   in which case playing a MOD resource would fail if one was already playing.
363  * </para></note>
364  *
365  * Returns: 1 on success, 0 on failure.
366  */
367 glui32 
368 glk_schannel_play(schanid_t chan, glui32 snd)
369 {
370         return glk_schannel_play_ext(chan, snd, 1, 0);
371 }
372
373 /**
374  * glk_schannel_play_ext:
375  * @chan: Channel to play the sound in.
376  * @snd: Resource number of the sound to play.
377  * @repeats: Number of times to repeat the sound.
378  * @notify: If nonzero, requests a notification when the sound is finished.
379  *
380  * This works the same as glk_schannel_play(), but lets you specify additional 
381  * options. <code>glk_schannel_play(chan, snd)</code> is exactly equivalent to 
382  * <code>glk_schannel_play_ext(chan, snd, 1, 0)</code>.
383  * 
384  * The @repeats value is the number of times the sound should be repeated. A 
385  * repeat value of -1 (or rather 0xFFFFFFFF) means that the sound should repeat 
386  * forever. A repeat value of 0 means that the sound will not be played at all; 
387  * nothing happens. (Although a previous sound on the channel will be stopped, 
388  * and the function will return 1.)
389  * 
390  * The @notify value should be nonzero in order to request a sound notification
391  * event. If you do this, when the sound is completed, you will get an event 
392  * with type %evtype_SoundNotify. The @window will be %NULL, @val1 will be the 
393  * sound's resource id, and @val2 will be the nonzero value you passed as 
394  * @notify.
395  * 
396  * If you request sound notification, and the repeat value is greater than one, 
397  * you will get the event only after the last repetition. If the repeat value is
398  * 0 or -1, you will never get a notification event at all. Similarly, if the 
399  * sound is stopped or interrupted, or if the channel is destroyed while the 
400  * sound is playing, there will be no notification event.
401  *
402  * Not all libraries support sound notification. You should test the
403  * %gestalt_SoundNotify selector before you rely on it; see <link
404  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound 
405  * Capabilities</link>.
406  * 
407  * Returns: 1 on success, 0 on failure.
408  */
409 glui32 
410 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
411 {
412         VALID_SCHANNEL(chan, return 0);
413 #ifdef GSTREAMER_SOUND
414         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
415         GInputStream *stream;
416
417         /* Stop the previous sound */
418         clean_up_after_playing_sound(chan);
419
420         /* Don't play if repeats = 0 */
421         if(repeats == 0) {
422                 chan->repeats = 0;
423                 return 1;
424         }
425
426         /* Load the sound into a GInputStream, by whatever method */
427         if(!glk_data->resource_map) {
428                 if(!glk_data->resource_load_callback) {
429                         WARNING(_("No resource map has been loaded yet."));
430                         return 0;
431                 }
432                 gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
433                 if(!filename) {
434                         WARNING(_("Error loading resource from alternative location."));
435                         return 0;
436                 }
437
438                 GError *err = NULL;
439                 GFile *file = g_file_new_for_path(filename);
440                 stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
441                 if(!stream) {
442                         IO_WARNING(_("Error loading resource from file"), filename, err->message);
443                         g_free(filename);
444                         g_object_unref(file);
445                         return 0;
446                 }
447                 g_free(filename);
448                 g_object_unref(file);
449         } else {
450                 giblorb_result_t resource;
451                 giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
452                 if(result != giblorb_err_None) {
453                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
454                         return 0;
455                 }
456                 stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
457         }
458
459         chan->repeats = repeats;
460         chan->resource = snd;
461         chan->notify = notify;
462         g_object_set(chan->source, "stream", stream, NULL);
463         g_object_unref(stream); /* Now owned by GStreamer element */
464         
465         /* Play the sound; unless the channel is paused, then pause it instead */
466         if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
467                 WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING");
468                 return 0;
469         }
470         return 1;
471 #else
472         return 0;
473 #endif
474 }
475
476 /**
477  * glk_schannel_play_multi:
478  * @chanarray: Array of #schanid_t structures.
479  * @chancount: Length of @chanarray.
480  * @sndarray: Array of sound resource numbers.
481  * @soundcount: Length of @sndarray, must be equal to @chanarray.
482  * @notify: If nonzero, request a notification when each sound finishes.
483  * 
484  * [DRAFT SPEC]
485  *
486  * This works the same as glk_schannel_play_ext(), except that you can specify
487  * more than one sound. The channel references and sound resource numbers are
488  * given as two arrays, which must be the same length. The @notify argument
489  * applies to all the sounds; the repeats value for all the sounds is 1.
490  * 
491  * All the sounds will begin at exactly the same time.
492  * 
493  * This returns the number of sounds that began playing correctly. (This will be
494  * a number from 0 to @soundcount.)
495  *
496  * <note><para>
497  *   Note that you have to supply @chancount and @soundcount as separate
498  *   arguments, even though they are required to be the same. This is an awkward
499  *   consequence of the way array arguments are dispatched in Glulx.
500  * </para></note>
501  * 
502  * Returns: The number of sounds that started playing correctly.
503  */
504 glui32
505 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray, glui32 soundcount, glui32 notify)
506 {
507         g_return_val_if_fail(chancount == soundcount, 0);
508         g_return_val_if_fail(chanarray != NULL || chancount == 0, 0);
509         g_return_val_if_fail(sndarray != NULL || soundcount == 0, 0);
510
511         int count;
512         for(count = 0; count < chancount; count++)
513                 VALID_SCHANNEL(chanarray[count], return 0);
514
515 #ifdef GSTREAMER_SOUND
516         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
517         GInputStream *stream;
518
519         if(!glk_data->resource_map && !glk_data->resource_load_callback) {
520                 WARNING(_("No resource map has been loaded yet."));
521                 return 0;
522         }
523
524         /* We keep an array of sounds to skip if any of them have errors */
525         gboolean *skiparray = g_new0(gboolean, chancount);
526
527         /* Set up all the channels one by one */
528         for(count = 0; count < chancount; count++) {
529                 /* Stop the previous sound */
530                 clean_up_after_playing_sound(chanarray[count]);
531
532                 /* Load the sound into a GInputStream, by whatever method */
533                 if(!glk_data->resource_map) {
534                         gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, sndarray[count], glk_data->resource_load_callback_data);
535                         if(!filename) {
536                                 WARNING(_("Error loading resource from alternative location."));
537                                 skiparray[count] = TRUE;
538                                 continue;
539                         }
540
541                         GError *err = NULL;
542                         GFile *file = g_file_new_for_path(filename);
543                         stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
544                         if(!stream) {
545                                 IO_WARNING(_("Error loading resource from file"), filename, err->message);
546                                 g_free(filename);
547                                 g_object_unref(file);
548                                 skiparray[count] = TRUE;
549                                 continue;
550                         }
551                         g_free(filename);
552                         g_object_unref(file);
553                 } else {
554                         giblorb_result_t resource;
555                         giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, sndarray[count]);
556                         if(result != giblorb_err_None) {
557                                 WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
558                                 skiparray[count] = TRUE;
559                                 continue;
560                         }
561                         stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
562                 }
563
564                 chanarray[count]->repeats = 1;
565                 chanarray[count]->resource = sndarray[count];
566                 chanarray[count]->notify = notify;
567                 g_object_set(chanarray[count]->source, "stream", stream, NULL);
568                 g_object_unref(stream); /* Now owned by GStreamer element */
569         }
570
571         /* Start all the sounds as close to each other as possible. */
572         /* FIXME: Is there a way to start them exactly at the same time? */
573         glui32 successes = 0;
574         for(count = 0; count < chancount; count++) {
575                 if(skiparray[count])
576                         continue;
577                 /* Play the sound; unless the channel is paused, then pause it instead */
578                 if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
579                         WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING");
580                         skiparray[count] = TRUE;
581                         continue;
582                 }
583                 successes++;
584         }
585         g_free(skiparray);
586         return successes;
587 #else
588         return 0;
589 #endif
590 }
591
592 /**
593  * glk_schannel_stop:
594  * @chan: Channel to silence.
595  *
596  * Stops any sound playing in the channel. No notification event is generated,
597  * even if you requested one. If no sound is playing, this has no effect.
598  */
599 void 
600 glk_schannel_stop(schanid_t chan)
601 {
602         VALID_SCHANNEL(chan, return);
603 #ifdef GSTREAMER_SOUND
604         clean_up_after_playing_sound(chan);
605 #endif
606 }
607
608 /**
609  * glk_schannel_pause:
610  * @chan: Channel to pause.
611  * 
612  * [DRAFT SPEC]
613  * 
614  * Pause any sound playing in the channel. This does not generate any
615  * notification events. If the channel is already paused, this does nothing.
616  * 
617  * New sounds started in a paused channel are paused immediately.
618  * 
619  * A volume change in progress is <emphasis>not</emphasis> paused, and may
620  * proceed to completion, generating a notification if appropriate.
621  */
622 void
623 glk_schannel_pause(schanid_t chan)
624 {
625         VALID_SCHANNEL(chan, return);
626
627         if(chan->paused)
628                 return; /* Silently do nothing */
629
630         /* Mark the channel as paused even if there is no sound playing yet */
631         chan->paused = TRUE;
632
633         GstState state;
634         if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
635                 WARNING(_("Could not get GstElement state"));
636                 return;
637         }
638         if(state != GST_STATE_PLAYING)
639                 return; /* Silently do nothing if no sound is playing */
640
641         if(!gst_element_set_state(chan->pipeline, GST_STATE_PAUSED)) {
642                 WARNING_S(_("Could not set GstElement state to"), "PAUSED");
643                 return;
644         }
645 }
646
647 /**
648  * glk_schannel_unpause:
649  * @chan: Channel to unpause.
650  * 
651  * [DRAFT SPEC]
652  *
653  * Unpause the channel. Any paused sounds begin playing where they left off. If
654  * the channel is not already paused, this does nothing.
655  */
656 void
657 glk_schannel_unpause(schanid_t chan)
658 {
659         VALID_SCHANNEL(chan, return);
660
661         if(!chan->paused)
662                 return; /* Silently do nothing */
663
664         /* Mark the channel as not paused in any case */
665         chan->paused = FALSE;
666
667         GstState state;
668         if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
669                 WARNING(_("Could not get GstElement state"));
670                 return;
671         }
672         if(state != GST_STATE_PAUSED)
673                 return; /* Silently do nothing */
674
675         if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
676                 WARNING_S(_("Could not set GstElement state to"), "PLAYING");
677                 return;
678         }
679 }
680
681 /**
682  * glk_schannel_set_volume:
683  * @chan: Channel to set the volume of.
684  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
685  *
686  * Sets the volume in the channel. When you create a channel, it has full 
687  * volume, represented by the value 0x10000. Half volume would be 0x8000, 
688  * three-quarters volume would be 0xC000, and so on. A volume of zero represents
689  * silence, although the sound is still considered to be playing.
690  *
691  * You can call this function between sounds, or while a sound is playing. The 
692  * effect is immediate.
693  * 
694  * You can overdrive the volume of a channel by setting a volume greater than 
695  * 0x10000. However, this is not recommended; the library may be unable to 
696  * increase the volume past full, or the sound may become distorted. You should 
697  * always create sound resources with the maximum volume you will need, and then
698  * call glk_schannel_set_volume() to reduce the volume when appropriate.
699  *
700  * Not all libraries support this function. You should test the
701  * %gestalt_SoundVolume selector before you rely on it; see <link
702  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
703  * Capabilities</link>.
704  *
705  * <note><title>Chimara</title>
706  *   <para>Chimara supports volumes from 0 to 1000&percnt;, that is, values of
707  *   @vol up to 0xA0000.</para>
708  * </note>
709  */
710 void 
711 glk_schannel_set_volume(schanid_t chan, glui32 vol)
712 {
713         glk_schannel_set_volume_ext(chan, vol, 0, 0);
714 }
715
716 /**
717  * glk_schannel_set_volume_ext:
718  * @chan: Channel to set the volume of.
719  * @vol: Integer representing the volume; 0x10000 is 100&percnt;.
720  * @duration: Length of volume change in milliseconds, or 0 for immediate.
721  * @notify: If nonzero, requests a notification when the volume change finishes.
722  * 
723  * [DRAFT SPEC]
724  * 
725  * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
726  * Again, you can overdrive the volume by setting a value greater than 0x10000,
727  * but this is not recommended.
728  *
729  * If the @duration is zero, the change is immediate. Otherwise, the change
730  * begins immediately, and occurs smoothly over the next @duration milliseconds.
731  *
732  * The @notify value should be nonzero in order to request a volume notification
733  * event. If you do this, when the volume change is completed, you will get an
734  * event with type #evtype_VolumeNotify. The window will be %NULL, @val1 will be
735  * zero, and @val2 will be the nonzero value you passed as @notify.
736  *
737  * The glk_schannel_set_volume() does not include @duration and @notify values.
738  * Both are assumed to be zero: immediate change, no notification.
739  *
740  * You can call these functions between sounds, or while a sound is playing.
741  * However, a zero-duration change while a sound is playing may produce
742  * unpleasant clicks.
743  *
744  * At most one volume change can be occurring on a sound channel at any time. If
745  * you call one of these functions while a previous volume change is in
746  * progress, the previous change is interrupted. The beginning point of the new
747  * volume change should be wherever the previous volume change was interrupted
748  * (rather than the previous change's beginning or ending point).
749  *
750  * Not all libraries support these functions. You should test the appropriate
751  * gestalt selectors before you rely on them; see "Testing for Sound
752  * Capabilities".
753  */
754 void
755 glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32 notify)
756 {
757         VALID_SCHANNEL(chan, return);
758         /* Silently ignore out-of-range volume values */
759         
760 #ifdef GSTREAMER_SOUND
761         gdouble volume_gst = (gdouble)vol / 0x10000;
762         g_object_set(chan->filter, "volume", CLAMP(volume_gst, 0.0, 10.0), NULL);
763 #endif
764
765         /* Not implemented */
766 }
767
768 /**
769  * glk_sound_load_hint:
770  * @snd: Resource number of a sound.
771  * @flag: Nonzero to tell the library to load the sound, zero to tell the
772  * library to unload it.
773  *
774  * This gives the library a hint about whether the given sound should be loaded
775  * or not. If the @flag is nonzero, the library may preload the sound or do
776  * other initialization, so that glk_schannel_play() will be faster. If the
777  * @flag is zero, the library may release memory or other resources associated
778  * with the sound. Calling this function is always optional, and it has no
779  * effect on what the library actually plays.
780  */
781 void 
782 glk_sound_load_hint(glui32 snd, glui32 flag)
783 {
784 #ifdef GSTREAMER_SOUND
785         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
786         giblorb_result_t resource;
787         giblorb_err_t result;
788
789         /* Sound load hints only work for Blorb resource maps */
790         if(!glk_data->resource_map)
791                 return;
792
793         if(flag) {
794                 /* The sound load hint simply loads the resource from the resource map;
795                  loading a chunk more than once does nothing */
796                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
797                 if(result != giblorb_err_None) {
798                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
799                         return;
800                 }
801         } else {
802                 /* Get the Blorb chunk number by loading the resource with
803                  method_DontLoad, then unload that chunk - has no effect if the chunk
804                  isn't loaded */
805                 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_DontLoad, &resource, giblorb_ID_Snd, snd);
806                 if(result != giblorb_err_None) {
807                         WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
808                         return;
809                 }
810                 result = giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
811                 if(result != giblorb_err_None) {
812                         WARNING_S( _("Error unloading chunk"), giblorb_get_error_message(result) );
813                         return;
814                 }
815         }
816 #endif /* GSTREAMER_SOUND */
817 }