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