3 #include <glib/gi18n-lib.h>
4 #include <libchimara/glk.h>
10 #include "chimara-glk-private.h"
16 #define VOLUME_TIMER_RESOLUTION 1.0 /* In milliseconds */
18 extern GPrivate glk_data_key;
20 #ifdef GSTREAMER_SOUND
21 /* Stop any currently playing sound on this channel, and remove any
22 format-specific GStreamer elements from the channel. */
24 clean_up_after_playing_sound(schanid_t chan)
26 if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
27 WARNING_S(_("Could not set GstElement state to"), "NULL");
30 gst_bin_remove(GST_BIN(chan->pipeline), chan->source);
35 gst_bin_remove(GST_BIN(chan->pipeline), chan->demux);
40 gst_bin_remove(GST_BIN(chan->pipeline), chan->decode);
45 /* This signal is thrown whenever the GStreamer pipeline generates a message.
46 Most messages are harmless. */
48 on_pipeline_message(GstBus *bus, GstMessage *message, schanid_t s)
50 /* g_printerr("Got %s message\n", GST_MESSAGE_TYPE_NAME(message)); */
55 switch(GST_MESSAGE_TYPE(message)) {
56 case GST_MESSAGE_ERROR:
58 gst_message_parse_error(message, &err, &debug_message);
59 IO_WARNING(_("GStreamer error"), err->message, debug_message);
61 g_free(debug_message);
62 clean_up_after_playing_sound(s);
65 case GST_MESSAGE_WARNING:
67 gst_message_parse_warning(message, &err, &debug_message);
68 IO_WARNING(_("GStreamer warning"), err->message, debug_message);
70 g_free(debug_message);
73 case GST_MESSAGE_INFO:
75 gst_message_parse_info(message, &err, &debug_message);
76 g_message("GStreamer info \"%s\": %s", err->message, debug_message);
78 g_free(debug_message);
81 case GST_MESSAGE_EOS: /* End of stream */
82 /* Decrease repeats if not set to forever */
83 if(s->repeats != (glui32)-1)
86 if(!gst_element_seek_simple(s->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 0)) {
87 WARNING(_("Could not execute GStreamer seek"));
88 clean_up_after_playing_sound(s);
91 clean_up_after_playing_sound(s);
92 /* Sound ended normally, send a notification if requested */
94 event_throw(s->glk, evtype_SoundNotify, NULL, s->resource, s->notify);
98 /* unhandled message */
103 /* This signal is thrown when the OGG demuxer element has decided what kind of
104 outputs it will output. We connect the decoder element dynamically. */
106 on_ogg_demuxer_pad_added(GstElement *demux, GstPad *pad, schanid_t s)
110 /* We can now link this pad with the vorbis-decoder sink pad */
111 sinkpad = gst_element_get_static_pad(s->decode, "sink");
112 if(gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK)
113 WARNING(_("Could not link OGG demuxer with Vorbis decoder"));
114 gst_object_unref(sinkpad);
117 /* This signal is thrown when the typefinder element has found the type of its
118 input. Now that we know what kind of input stream we have, we can connect the
119 proper demuxer/decoder elements. */
121 on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t s)
123 gchar *type = gst_caps_to_string(caps);
124 if(strcmp(type, "application/ogg") == 0) {
125 s->demux = gst_element_factory_make("oggdemux", NULL);
126 s->decode = gst_element_factory_make("vorbisdec", NULL);
127 if(!s->demux || !s->decode) {
128 WARNING(_("Could not create one or more GStreamer elements"));
131 gst_bin_add_many(GST_BIN(s->pipeline), s->demux, s->decode, NULL);
132 if(!gst_element_link(s->typefind, s->demux) || !gst_element_link(s->decode, s->convert)) {
133 WARNING(_("Could not link GStreamer elements"));
136 /* We link the demuxer and decoder together dynamically, since the
137 demuxer doesn't know what source pads it will have until it starts
138 demuxing the stream */
139 g_signal_connect(s->demux, "pad-added", G_CALLBACK(on_ogg_demuxer_pad_added), s);
140 } else if(strcmp(type, "audio/x-aiff") == 0) {
141 s->decode = gst_element_factory_make("aiffparse", NULL);
143 WARNING(_("Could not create 'aiffparse' GStreamer element"));
146 gst_bin_add(GST_BIN(s->pipeline), s->decode);
147 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
148 WARNING(_("Could not link GStreamer elements"));
151 } else if(strcmp(type, "audio/x-mod") == 0) {
152 s->decode = gst_element_factory_make("modplug", NULL);
154 WARNING(_("Could not create 'modplug' GStreamer element"));
157 gst_bin_add(GST_BIN(s->pipeline), s->decode);
158 if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
159 WARNING(_("Could not link GStreamer elements"));
163 WARNING_S(_("Unexpected audio type in blorb"), type);
169 #endif /* GSTREAMER_SOUND */
172 * glk_schannel_create:
173 * @rock: The rock value to give the new sound channel.
175 * This creates a sound channel, about as you'd expect.
177 * Remember that it is possible that the library will be unable to create a new
178 * channel, in which case glk_schannel_create() will return %NULL.
180 * When you create a channel using glk_schannel_create(), it has full volume,
181 * represented by the value 0x10000. Half volume would be 0x8000, three-quarters
182 * volume would be 0xC000, and so on. A volume of zero represents silence.
184 * You can overdrive the volume of a channel by setting a volume greater than
185 * 0x10000. However, this is not recommended; the library may be unable to
186 * increase the volume past full, or the sound may become distorted. You should
187 * always create sound resources with the maximum volume you will need, and then
188 * reduce the volume when appropriate using the channel-volume calls.
191 * Mathematically, these volume changes should be taken as linear
192 * multiplication of a waveform represented as linear samples. As I
193 * understand it, linear PCM encodes the sound pressure, and therefore a
194 * volume of 0x8000 should represent a 6 dB drop.
197 * Returns: A new sound channel, or %NULL.
200 glk_schannel_create(glui32 rock)
202 return glk_schannel_create_ext(rock, 0x10000);
206 * glk_schannel_create_ext:
207 * @rock: The rock value to give the new sound channel.
208 * @volume: Integer representing the volume; 0x10000 is 100%.
210 * The glk_schannel_create_ext() call lets you create a channel with the volume
211 * already set at a given level.
213 * Not all libraries support glk_schannel_create_ext(). You should test the
214 * %gestalt_Sound2 selector before you rely on it; see <link
215 * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
216 * Capabilities</link>.
218 * Returns: A new sound channel, or %NULL.
221 glk_schannel_create_ext(glui32 rock, glui32 volume)
223 #ifdef GSTREAMER_SOUND
224 ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
226 schanid_t s = g_new0(struct glk_schannel_struct, 1);
227 s->magic = MAGIC_SCHANNEL;
229 if(glk_data->register_obj)
230 s->disprock = (*glk_data->register_obj)(s, gidisp_Class_Schannel);
232 /* Add it to the global sound channel list */
233 glk_data->schannel_list = g_list_prepend(glk_data->schannel_list, s);
234 s->schannel_list = glk_data->schannel_list;
236 /* Add a pointer to the ChimaraGlk widget, for convenience */
237 s->glk = glk_data->self;
239 /* Create a GStreamer pipeline for the sound channel */
240 gchar *pipeline_name = g_strdup_printf("pipeline-%p", s);
241 s->pipeline = gst_pipeline_new(pipeline_name);
242 g_free(pipeline_name);
244 /* Watch for messages from the pipeline */
245 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(s->pipeline));
246 gst_bus_add_signal_watch(bus);
247 g_signal_connect(bus, "message", G_CALLBACK(on_pipeline_message), s);
248 gst_object_unref(bus);
250 /* Create GStreamer elements to put in the pipeline */
251 s->typefind = gst_element_factory_make("typefind", NULL);
252 s->convert = gst_element_factory_make("audioconvert", NULL);
253 s->filter = gst_element_factory_make("volume", NULL);
254 s->sink = gst_element_factory_make("autoaudiosink", NULL);
255 if(!s->typefind || !s->convert || !s->filter || !s->sink) {
256 WARNING(_("Could not create one or more GStreamer elements"));
260 /* Set the initial volume */
261 glk_schannel_set_volume(s, volume);
263 /* Put the elements in the pipeline and link as many together as we can
264 without knowing the type of the audio stream */
265 gst_bin_add_many(GST_BIN(s->pipeline), s->typefind, s->convert, s->filter, s->sink, NULL);
267 /* Link elements: ??? -> Converter -> Volume filter -> Sink */
268 if(!gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
269 WARNING(_("Could not link GStreamer elements"));
272 g_signal_connect(s->typefind, "have-type", G_CALLBACK(on_type_found), s);
277 glk_schannel_destroy(s);
281 #endif /* GSTREAMER_SOUND */
285 * glk_schannel_destroy:
286 * @chan: The sound channel to destroy.
288 * Destroys the channel. If the channel is playing a sound, the sound stops
289 * immediately (with no notification event).
292 glk_schannel_destroy(schanid_t chan)
294 VALID_SCHANNEL(chan, return);
296 #ifdef GSTREAMER_SOUND
297 ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
299 if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
300 WARNING_S(_("Could not set GstElement state to"), "NULL");
302 glk_data->schannel_list = g_list_delete_link(glk_data->schannel_list, chan->schannel_list);
304 if(glk_data->unregister_obj)
306 (*glk_data->unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
307 chan->disprock.ptr = NULL;
310 /* This also frees all the objects inside the pipeline */
312 gst_object_unref(chan->pipeline);
314 chan->magic = MAGIC_FREE;
320 * glk_schannel_iterate:
321 * @chan: A sound channel, or %NULL.
322 * @rockptr: Return location for the next sound channel's rock, or %NULL.
324 * This function can be used to iterate through the list of all open channels.
325 * See <link linkend="chimara-Iterating-Through-Opaque-Objects">Iterating
326 * Through Opaque Objects</link>.
328 * As that section describes, the order in which channels are returned is
331 * Returns: the next sound channel, or %NULL if there are no more.
334 glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
336 VALID_SCHANNEL_OR_NULL(chan, return NULL);
338 #ifdef GSTREAMER_SOUND
339 ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
343 retnode = glk_data->schannel_list;
345 retnode = chan->schannel_list->next;
346 schanid_t retval = retnode? (schanid_t)retnode->data : NULL;
348 /* Store the sound channel's rock in rockptr */
349 if(retval && rockptr)
350 *rockptr = glk_schannel_get_rock(retval);
355 #endif /* GSTREAMER_SOUND */
359 * glk_schannel_get_rock:
360 * @chan: A sound channel.
362 * Retrieves the channel's rock value. See <link
363 * linkend="chimara-Rocks">Rocks</link>.
365 * Returns: A rock value.
368 glk_schannel_get_rock(schanid_t chan)
370 VALID_SCHANNEL(chan, return 0);
376 * @chan: Channel to play the sound in.
377 * @snd: Resource number of the sound to play.
379 * Begins playing the given sound on the channel. If the channel was already
380 * playing a sound (even the same one), the old sound is stopped (with no
381 * notification event.
383 * This returns 1 if the sound actually started playing, and 0 if there was any
386 * The most obvious problem is if there is no sound resource with the given
387 * identifier. But other problems can occur. For example, the MOD-playing
388 * facility in a library might be unable to handle two MODs at the same time,
389 * in which case playing a MOD resource would fail if one was already playing.
392 * Returns: 1 on success, 0 on failure.
395 glk_schannel_play(schanid_t chan, glui32 snd)
397 return glk_schannel_play_ext(chan, snd, 1, 0);
401 * glk_schannel_play_ext:
402 * @chan: Channel to play the sound in.
403 * @snd: Resource number of the sound to play.
404 * @repeats: Number of times to repeat the sound.
405 * @notify: If nonzero, requests a notification when the sound is finished.
407 * This works the same as glk_schannel_play(), but lets you specify additional
408 * options. <code>glk_schannel_play(chan, snd)</code> is exactly equivalent to
409 * <code>glk_schannel_play_ext(chan, snd, 1, 0)</code>.
411 * The @repeats value is the number of times the sound should be repeated. A
412 * repeat value of -1 (or rather 0xFFFFFFFF) means that the sound should repeat
413 * forever. A repeat value of 0 means that the sound will not be played at all;
414 * nothing happens. (Although a previous sound on the channel will be stopped,
415 * and the function will return 1.)
417 * The @notify value should be nonzero in order to request a sound notification
418 * event. If you do this, when the sound is completed, you will get an event
419 * with type %evtype_SoundNotify. The @window will be %NULL, @val1 will be the
420 * sound's resource id, and @val2 will be the nonzero value you passed as
423 * If you request sound notification, and the repeat value is greater than one,
424 * you will get the event only after the last repetition. If the repeat value is
425 * 0 or -1, you will never get a notification event at all. Similarly, if the
426 * sound is stopped or interrupted, or if the channel is destroyed while the
427 * sound is playing, there will be no notification event.
429 * Not all libraries support sound notification. You should test the
430 * %gestalt_Sound2 selector before you rely on it; see <link
431 * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
432 * Capabilities</link>.
434 * Note that you can play a sound on a channel whose volume is zero. This has
435 * no audible result, unless you later change the volume; but it produces
436 * notifications as usual. You can also play a sound on a paused channel; the
437 * sound is paused immediately, and does not progress.
439 * Returns: 1 on success, 0 on failure.
442 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
444 VALID_SCHANNEL(chan, return 0);
445 #ifdef GSTREAMER_SOUND
446 ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
447 GInputStream *stream;
449 /* Stop the previous sound */
450 clean_up_after_playing_sound(chan);
452 /* Don't play if repeats = 0 */
458 /* Load the sound into a GInputStream, by whatever method */
459 if(!glk_data->resource_map) {
460 if(!glk_data->resource_load_callback) {
461 WARNING(_("No resource map has been loaded yet."));
464 gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
466 WARNING(_("Error loading resource from alternative location."));
471 GFile *file = g_file_new_for_path(filename);
472 stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
474 IO_WARNING(_("Error loading resource from file"), filename, err->message);
476 g_object_unref(file);
480 g_object_unref(file);
482 giblorb_result_t resource;
483 giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
484 if(result != giblorb_err_None) {
485 WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
488 stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
491 chan->source = gst_element_factory_make("giostreamsrc", NULL);
492 g_object_set(chan->source, "stream", stream, NULL);
493 g_object_unref(stream); /* Now owned by GStreamer element */
494 gst_bin_add(GST_BIN(chan->pipeline), chan->source);
495 if(!gst_element_link(chan->source, chan->typefind)) {
496 WARNING(_("Could not link GStreamer elements"));
497 clean_up_after_playing_sound(chan);
501 chan->repeats = repeats;
502 chan->resource = snd;
503 chan->notify = notify;
505 /* Play the sound; unless the channel is paused, then pause it instead */
506 if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
507 WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING");
508 clean_up_after_playing_sound(chan);
518 * glk_schannel_play_multi:
519 * @chanarray: Array of #schanid_t structures.
520 * @chancount: Length of @chanarray.
521 * @sndarray: Array of sound resource numbers.
522 * @soundcount: Length of @sndarray, must be equal to @chanarray.
523 * @notify: If nonzero, request a notification when each sound finishes.
525 * This works the same as glk_schannel_play_ext(), except that you can specify
526 * more than one sound. The channel references and sound resource numbers are
527 * given as two arrays, which must be the same length. The @notify argument
528 * applies to all the sounds; the repeats value for all the sounds is 1.
530 * All the sounds will begin at exactly the same time.
532 * This returns the number of sounds that began playing correctly. (This will be
533 * a number from 0 to @soundcount.)
536 * If the @notify argument is nonzero, you will get a separate sound
537 * notification event as each sound finishes. They will all have the same
541 * Note that you have to supply @chancount and @soundcount as separate
542 * arguments, even though they are required to be the same. This is an awkward
543 * consequence of the way array arguments are dispatched in Glulx.
546 * Returns: The number of sounds that started playing correctly.
549 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray, glui32 soundcount, glui32 notify)
551 g_return_val_if_fail(chancount == soundcount, 0);
552 g_return_val_if_fail(chanarray != NULL || chancount == 0, 0);
553 g_return_val_if_fail(sndarray != NULL || soundcount == 0, 0);
556 for(count = 0; count < chancount; count++)
557 VALID_SCHANNEL(chanarray[count], return 0);
559 #ifdef GSTREAMER_SOUND
560 ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
561 GInputStream *stream;
563 if(!glk_data->resource_map && !glk_data->resource_load_callback) {
564 WARNING(_("No resource map has been loaded yet."));
568 /* We keep an array of sounds to skip if any of them have errors */
569 gboolean *skiparray = g_new0(gboolean, chancount);
571 /* Set up all the channels one by one */
572 for(count = 0; count < chancount; count++) {
573 /* Stop the previous sound */
574 clean_up_after_playing_sound(chanarray[count]);
576 /* Load the sound into a GInputStream, by whatever method */
577 if(!glk_data->resource_map) {
578 gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, sndarray[count], glk_data->resource_load_callback_data);
580 WARNING(_("Error loading resource from alternative location."));
581 skiparray[count] = TRUE;
586 GFile *file = g_file_new_for_path(filename);
587 stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
589 IO_WARNING(_("Error loading resource from file"), filename, err->message);
591 g_object_unref(file);
592 skiparray[count] = TRUE;
596 g_object_unref(file);
598 giblorb_result_t resource;
599 giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, sndarray[count]);
600 if(result != giblorb_err_None) {
601 WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
602 skiparray[count] = TRUE;
605 stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
608 chanarray[count]->source = gst_element_factory_make("giostreamsrc", NULL);
609 g_object_set(chanarray[count]->source, "stream", stream, NULL);
610 g_object_unref(stream); /* Now owned by GStreamer element */
611 gst_bin_add(GST_BIN(chanarray[count]->pipeline), chanarray[count]->source);
612 if(!gst_element_link(chanarray[count]->source, chanarray[count]->typefind)) {
613 WARNING(_("Could not link GStreamer elements"));
614 clean_up_after_playing_sound(chanarray[count]);
617 chanarray[count]->repeats = 1;
618 chanarray[count]->resource = sndarray[count];
619 chanarray[count]->notify = notify;
622 /* Start all the sounds as close to each other as possible. */
623 /* FIXME: Is there a way to start them exactly at the same time? */
624 glui32 successes = 0;
625 for(count = 0; count < chancount; count++) {
628 /* Play the sound; unless the channel is paused, then pause it instead */
629 if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
630 WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING");
631 skiparray[count] = TRUE;
632 clean_up_after_playing_sound(chanarray[count]);
646 * @chan: Channel to silence.
648 * Stops any sound playing in the channel. No notification event is generated,
649 * even if you requested one. If no sound is playing, this has no effect.
652 glk_schannel_stop(schanid_t chan)
654 VALID_SCHANNEL(chan, return);
655 #ifdef GSTREAMER_SOUND
656 clean_up_after_playing_sound(chan);
661 * glk_schannel_pause:
662 * @chan: Channel to pause.
664 * Pause any sound playing in the channel. This does not generate any
665 * notification events. If the channel is already paused, this does nothing.
667 * New sounds started in a paused channel are paused immediately.
669 * A volume change in progress is <emphasis>not</emphasis> paused, and may
670 * proceed to completion, generating a notification if appropriate.
673 glk_schannel_pause(schanid_t chan)
675 VALID_SCHANNEL(chan, return);
678 return; /* Silently do nothing */
680 /* Mark the channel as paused even if there is no sound playing yet */
684 if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
685 WARNING(_("Could not get GstElement state"));
688 if(state != GST_STATE_PLAYING)
689 return; /* Silently do nothing if no sound is playing */
691 if(!gst_element_set_state(chan->pipeline, GST_STATE_PAUSED)) {
692 WARNING_S(_("Could not set GstElement state to"), "PAUSED");
698 * glk_schannel_unpause:
699 * @chan: Channel to unpause.
701 * Unpause the channel. Any paused sounds begin playing where they left off. If
702 * the channel is not already paused, this does nothing.
705 * This means, for example, that you can pause a channel that is currently
706 * not playing any sounds. If you then add a sound to the channel, it will
707 * not start playing; it will be paused at its beginning. If you later
708 * unpaise the channel, the sound will commence.
712 glk_schannel_unpause(schanid_t chan)
714 VALID_SCHANNEL(chan, return);
717 return; /* Silently do nothing */
719 /* Mark the channel as not paused in any case */
720 chan->paused = FALSE;
723 if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
724 WARNING(_("Could not get GstElement state"));
727 if(state != GST_STATE_PAUSED)
728 return; /* Silently do nothing */
730 if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
731 WARNING_S(_("Could not set GstElement state to"), "PLAYING");
737 * glk_schannel_set_volume:
738 * @chan: Channel to set the volume of.
739 * @vol: Integer representing the volume; 0x10000 is 100%.
741 * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
742 * Again, you can overdrive the volume by setting a value greater than 0x10000,
743 * but this is not recommended.
745 * The glk_schannel_set_volume() function does not include duration and notify
746 * values. Both are assumed to be zero: immediate change, no notification.
748 * You can call this function between sounds, or while a sound is playing.
749 * However, a zero-duration change while a sound is playing may produce
752 * At most one volume change can be occurring on a sound channel at any time.
753 * If you call this function while a previous volume change is in progress, the
754 * previous change is interrupted.
756 * Not all libraries support this function. You should test the
757 * %gestalt_SoundVolume selector before you rely on it; see <link
758 * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
759 * Capabilities</link>.
761 * <note><title>Chimara</title>
762 * <para>Chimara supports volumes from 0 to 1000%, that is, values of
763 * @vol up to 0xA0000.</para>
767 glk_schannel_set_volume(schanid_t chan, glui32 vol)
769 glk_schannel_set_volume_ext(chan, vol, 0, 0);
773 volume_glk_to_gstreamer(glui32 volume_glk)
775 return CLAMP(((double)volume_glk / 0x10000), 0.0, 10.0);
778 #ifdef GSTREAMER_SOUND
780 volume_change_timeout(schanid_t chan)
783 g_get_current_time(&now);
785 if(now.tv_sec >= chan->target_time_sec && now.tv_usec >= chan->target_time_usec) {
786 /* We're done - make sure the volume is at the requested level */
787 g_object_set(chan->filter, "volume", chan->target_volume, NULL);
789 if(chan->volume_notify)
790 event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, chan->volume_notify);
792 chan->volume_timer_id = 0;
796 /* Calculate the appropriate step every time - a busy system may delay or
797 * drop timer ticks */
798 double time_left_msec = (chan->target_time_sec - now.tv_sec) * 1000.0
799 + (chan->target_time_usec - now.tv_usec) / 1000.0;
800 double steps_left = time_left_msec / VOLUME_TIMER_RESOLUTION;
801 double current_volume;
802 g_object_get(chan->filter, "volume", ¤t_volume, NULL);
803 double volume_step = (chan->target_volume - current_volume) / steps_left;
805 g_object_set(chan->filter, "volume", current_volume + volume_step, NULL);
809 #endif /* GSTREAMER_SOUND */
812 * glk_schannel_set_volume_ext:
813 * @chan: Channel to set the volume of.
814 * @vol: Integer representing the volume; 0x10000 is 100%.
815 * @duration: Length of volume change in milliseconds, or 0 for immediate.
816 * @notify: If nonzero, requests a notification when the volume change finishes.
818 * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
819 * Again, you can overdrive the volume by setting a value greater than 0x10000,
820 * but this is not recommended.
822 * If the @duration is zero, the change is immediate. Otherwise, the change
823 * begins immediately, and occurs smoothly over the next @duration milliseconds.
825 * The @notify value should be nonzero in order to request a volume notification
826 * event. If you do this, when the volume change is completed, you will get an
827 * event with type #evtype_VolumeNotify. The window will be %NULL, @val1 will be
828 * zero, and @val2 will be the nonzero value you passed as @notify.
830 * You can call this function between sounds, or while a sound is playing.
831 * However, a zero-duration change while a sound is playing may produce
834 * At most one volume change can be occurring on a sound channel at any time. If
835 * you call this function while a previous volume change is in progress, the
836 * previous change is interrupted. The beginning point of the new volume change
837 * should be wherever the previous volume change was interrupted (rather than
838 * the previous change's beginning or ending point).
840 * Not all libraries support these functions. You should test the appropriate
841 * gestalt selectors before you rely on them; see "Testing for Sound
845 glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32 notify)
847 VALID_SCHANNEL(chan, return);
848 /* Silently ignore out-of-range volume values */
850 #ifdef GSTREAMER_SOUND
851 /* Interrupt a previous volume change */
852 if(chan->volume_timer_id > 0)
853 g_source_remove(chan->volume_timer_id);
855 double target_volume = volume_glk_to_gstreamer(vol);
858 g_object_set(chan->filter, "volume", target_volume, NULL);
861 event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, notify);
866 GTimeVal target_time;
867 g_get_current_time(&target_time);
868 g_time_val_add(&target_time, (long)duration * 1000);
870 chan->target_volume = target_volume;
871 chan->target_time_sec = target_time.tv_sec;
872 chan->target_time_usec = target_time.tv_usec;
873 chan->volume_notify = notify;
875 /* Set up a timer for the volume */
876 chan->volume_timer_id = g_timeout_add(VOLUME_TIMER_RESOLUTION, (GSourceFunc)volume_change_timeout, chan);
881 * glk_sound_load_hint:
882 * @snd: Resource number of a sound.
883 * @flag: Nonzero to tell the library to load the sound, zero to tell the
884 * library to unload it.
886 * This gives the library a hint about whether the given sound should be loaded
887 * or not. If the @flag is nonzero, the library may preload the sound or do
888 * other initialization, so that glk_schannel_play() will be faster. If the
889 * @flag is zero, the library may release memory or other resources associated
890 * with the sound. Calling this function is always optional, and it has no
891 * effect on what the library actually plays.
894 glk_sound_load_hint(glui32 snd, glui32 flag)
896 #ifdef GSTREAMER_SOUND
897 ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
898 giblorb_result_t resource;
899 giblorb_err_t result;
901 /* Sound load hints only work for Blorb resource maps */
902 if(!glk_data->resource_map)
906 /* The sound load hint simply loads the resource from the resource map;
907 loading a chunk more than once does nothing */
908 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
909 if(result != giblorb_err_None) {
910 WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
914 /* Get the Blorb chunk number by loading the resource with
915 method_DontLoad, then unload that chunk - has no effect if the chunk
917 result = giblorb_load_resource(glk_data->resource_map, giblorb_method_DontLoad, &resource, giblorb_ID_Snd, snd);
918 if(result != giblorb_err_None) {
919 WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
922 result = giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
923 if(result != giblorb_err_None) {
924 WARNING_S( _("Error unloading chunk"), giblorb_get_error_message(result) );
928 #endif /* GSTREAMER_SOUND */