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