gestalt_Timer
gestalt_Graphics
gestalt_DrawImage
+gestalt_Sound2
gestalt_Sound
gestalt_SoundVolume
gestalt_SoundNotify
<FILE>glk-sound-channels</FILE>
<TITLE>Creating and Destroying Sound Channels</TITLE>
glk_schannel_create
+glk_schannel_create_ext
glk_schannel_destroy
</SECTION>
<TITLE>Playing Sounds</TITLE>
glk_schannel_play
glk_schannel_play_ext
+glk_schannel_play_multi
glk_schannel_stop
+glk_schannel_pause
+glk_schannel_unpause
glk_schannel_set_volume
+glk_schannel_set_volume_ext
glk_sound_load_hint
</SECTION>
<FILE>glk-sound-testing</FILE>
<TITLE>Testing for Sound Capabilities</TITLE>
GLK_MODULE_SOUND
+GLK_MODULE_SOUND2
</SECTION>
<SECTION>
* SECTION:glk-sound-testing
* @short_description: Checking whether the library supports sound
*
- * Before calling Glk sound functions, you should use the %gestalt_Sound
- * selector. To test for additional capabilities, you can use the
- * %gestalt_SoundMusic, %gestalt_SoundVolume, and %gestalt_SoundNotify
- * selectors.
+ * Before calling Glk sound functions, you should use the %gestalt_Sound2
+ * selector.
+ *
+ * Earlier versions of the Glk spec defined separate selectors for various
+ * optional capabilities. This has proven to be an unnecessarily confusing
+ * strategy, and is no longer used. The %gestalt_Sound, %gestalt_SoundMusic,
+ * %gestalt_SoundVolume, and %gestalt_SoundNotify selectors still exist, but you
+ * should not need to test them; the %gestalt_Sound2 selector covers all of
+ * them.
*/
/**
* may implement both, neither, or only one.
*/
+/**
+ * gestalt_Sound2:
+ *
+ * You can test whether the library supports sound:
+ * |[
+ * glui32 res;
+ * res = glk_gestalt(gestalt_Sound2, 0);
+ * ]|
+ * This returns 1 if the overall suite of sound functions is available. This
+ * includes all the functions defined in <link
+ * linkend="chimara-chapter-Sound">this chapter</link>. It also includes the
+ * capabilities described below under %gestalt_SoundMusic, %gestalt_SoundVolume,
+ * and %gestalt_SoundNotify.
+ */
+
/**
* gestalt_Sound:
*
- * You can test whether the library supports sound:
* |[
* glui32 res;
* res = glk_gestalt(gestalt_Sound, 0);
* glk_sound_load_hint().
*
* If this selector returns 0, you should not try to call these functions. They
- * may have no effect, or they may cause a run-time error.
+ * may have no effect, or they may cause a run-time error.
*/
/**
* res = glk_gestalt(gestalt_SoundVolume, 0);
* ]|
* This selector returns 1 if the glk_schannel_set_volume() function works. If
- * it returns zero, glk_schannel_set_volume() has no effect.
+ * it returns zero, glk_schannel_set_volume() has no effect.
*/
/**
* res = glk_gestalt(gestalt_SoundNotify, 0);
* ]|
* This selector returns 1 if the library supports sound notification events. If
- * it returns zero, you will never get such events.
+ * it returns zero, you will never get such events.
*/
/**
case gestalt_SoundVolume:
case gestalt_SoundNotify:
case gestalt_SoundMusic:
+ case gestalt_Sound2:
#ifdef GSTREAMER_SOUND
return 1;
#else
-/* gi_dispa.c: Dispatch layer for Glk API, version 0.7.2.
+/* gi_dispa.c: Dispatch layer for Glk API, version 0.7.3.
Designed by Andrew Plotkin <erkyrath@eblong.com>
http://eblong.com/zarf/glk/
{ "evtype_Redraw", (6) },
{ "evtype_SoundNotify", (7) },
{ "evtype_Timer", (1) },
+ { "evtype_VolumeNotify", (9) },
{ "filemode_Read", (0x02) },
{ "filemode_ReadWrite", (0x03) },
{ "gestalt_LineTerminators", (18) },
{ "gestalt_MouseInput", (4) },
{ "gestalt_Sound", (8) },
+ { "gestalt_Sound2", (21) },
{ "gestalt_SoundMusic", (13) },
{ "gestalt_SoundNotify", (10) },
{ "gestalt_SoundVolume", (9) },
{ 0x00FA, glk_schannel_stop, "schannel_stop" },
{ 0x00FB, glk_schannel_set_volume, "schannel_set_volume" },
{ 0x00FC, glk_sound_load_hint, "sound_load_hint" },
+#ifdef GLK_MODULE_SOUND2
+ { 0x00F4, glk_schannel_create_ext, "schannel_create_ext" },
+ { 0x00F7, glk_schannel_play_multi, "schannel_play_multi" },
+ { 0x00FD, glk_schannel_set_volume_ext, "schannel_set_volume_ext" },
+ { 0x00FE, glk_schannel_pause, "schannel_pause" },
+ { 0x00FF, glk_schannel_unpause, "schannel_unpause" },
+#endif /* GLK_MODULE_SOUND2 */
#endif /* GLK_MODULE_SOUND */
#ifdef GLK_MODULE_HYPERLINKS
{ 0x0100, glk_set_hyperlink, "set_hyperlink" },
return "2QdIu:";
case 0x00FC: /* sound_load_hint */
return "2IuIu:";
+
+#ifdef GLK_MODULE_SOUND2
+ case 0x00F4: /* schannel_create_ext */
+ return "3IuIu:Qd";
+ case 0x00F7: /* schannel_play_multi */
+ return "4>+#Qd>+#IuIu:Iu";
+ case 0x00FD: /* schannel_set_volume_ext */
+ return "4QdIuIuIu:";
+ case 0x00FE: /* schannel_pause */
+ return "1Qd:";
+ case 0x00FF: /* schannel_unpause */
+ return "1Qd:";
+#endif /* GLK_MODULE_SOUND2 */
#endif /* GLK_MODULE_SOUND */
#ifdef GLK_MODULE_HYPERLINKS
case 0x00FC: /* sound_load_hint */
glk_sound_load_hint(arglist[0].uint, arglist[1].uint);
break;
+
+#ifdef GLK_MODULE_SOUND2
+ case 0x00F4: /* schannel_create_ext */
+ arglist[3].opaqueref = glk_schannel_create_ext(arglist[0].uint, arglist[1].uint);
+ break;
+ case 0x00F7: /* schannel_play_multi */
+ if (arglist[0].ptrflag && arglist[3].ptrflag)
+ arglist[8].uint = glk_schannel_play_multi(arglist[1].array, arglist[2].uint, arglist[4].array, arglist[5].uint, arglist[6].uint);
+ else if (arglist[0].ptrflag)
+ arglist[6].uint = glk_schannel_play_multi(arglist[1].array, arglist[2].uint, NULL, 0, arglist[4].uint);
+ else if (arglist[1].ptrflag)
+ arglist[6].uint = glk_schannel_play_multi(NULL, 0, arglist[2].array, arglist[3].uint, arglist[4].uint);
+ else
+ arglist[4].uint = glk_schannel_play_multi(NULL, 0, NULL, 0, arglist[2].uint);
+ break;
+ case 0x00FD: /* schannel_set_volume_ext */
+ glk_schannel_set_volume_ext(arglist[0].opaqueref, arglist[1].uint, arglist[2].uint, arglist[3].uint);
+ break;
+ case 0x00FE: /* schannel_pause */
+ glk_schannel_pause(arglist[0].opaqueref);
+ break;
+ case 0x00FF: /* schannel_unpause */
+ glk_schannel_unpause(arglist[0].opaqueref);
+ break;
+#endif /* GLK_MODULE_SOUND2 */
#endif /* GLK_MODULE_SOUND */
#ifdef GLK_MODULE_HYPERLINKS
#ifndef _GI_DISPA_H
#define _GI_DISPA_H
-/* gi_dispa.h: Header file for dispatch layer of Glk API, version 0.7.2.
+/* gi_dispa.h: Header file for dispatch layer of Glk API, version 0.7.3.
Designed by Andrew Plotkin <erkyrath@eblong.com>
http://www.eblong.com/zarf/glk/index.html
#ifndef GLK_H
#define GLK_H
-/* glk.h: Header file for Glk API, version 0.7.2.
- Designed by Andrew Plotkin <erkyrath@eblong.com>
- http://eblong.com/zarf/glk/
-
- This file is copyright 1998-2011 by Andrew Plotkin. You may copy,
- distribute, and incorporate it into your own programs, by any means
- and under any conditions, as long as you do not modify it. You may
- also modify this file, incorporate it into your own programs,
- and distribute the modified version, as long as you retain a notice
- in your program or documentation which mentions my name and the URL
- shown above.
- */
+/* glk.h: Header file for Glk API, version 0.7.3.
+ Designed by Andrew Plotkin <erkyrath@eblong.com>
+ http://eblong.com/zarf/glk/
+
+ This file is copyright 1998-2011 by Andrew Plotkin. You may copy,
+ distribute, and incorporate it into your own programs, by any means
+ and under any conditions, as long as you do not modify it. You may
+ also modify this file, incorporate it into your own programs,
+ and distribute the modified version, as long as you retain a notice
+ in your program or documentation which mentions my name and the URL
+ shown above.
+*/
/* If your system does not have <stdint.h>, you'll have to remove this
- include line. Then edit the definition of glui32 to make sure it's
- really a 32-bit unsigned integer type, and glsi32 to make sure
- it's really a 32-bit signed integer type. If they're not, horrible
- things will happen. */
+ include line. Then edit the definition of glui32 to make sure it's
+ really a 32-bit unsigned integer type, and glsi32 to make sure
+ it's really a 32-bit signed integer type. If they're not, horrible
+ things will happen. */
#include <stdint.h>
typedef uint32_t glui32;
typedef int32_t glsi32;
/* These are the compile-time conditionals that reveal various Glk optional
- modules. */
+ modules. Note that if GLK_MODULE_SOUND2 is defined, GLK_MODULE_SOUND
+ must be also. */
#define GLK_MODULE_LINE_ECHO
#define GLK_MODULE_LINE_TERMINATORS
#define GLK_MODULE_UNICODE
#define GLK_MODULE_UNICODE_NORM
#define GLK_MODULE_IMAGE
#define GLK_MODULE_SOUND
+#define GLK_MODULE_SOUND2
#define GLK_MODULE_HYPERLINKS
#define GLK_MODULE_DATETIME
/* These types are opaque object identifiers. They're pointers to opaque
- C structures, which are defined differently by each library. */
+ C structures, which are defined differently by each library. */
typedef struct glk_window_struct *winid_t;
typedef struct glk_stream_struct *strid_t;
typedef struct glk_fileref_struct *frefid_t;
#define gestalt_LineTerminators (18)
#define gestalt_LineTerminatorKey (19)
#define gestalt_DateTime (20)
+#define gestalt_Sound2 (21)
#define evtype_None (0)
#define evtype_Timer (1)
#define evtype_Redraw (6)
#define evtype_SoundNotify (7)
#define evtype_Hyperlink (8)
+#define evtype_VolumeNotify (9)
typedef struct event_struct {
glui32 type;
#define stylehint_just_RightFlush (3)
/* glk_main() is the top-level function which you define. The Glk library
- calls it. */
+ calls it. */
extern void glk_main(void);
extern void glk_exit(void);
extern glui32 glk_gestalt(glui32 sel, glui32 val);
extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr,
- glui32 arrlen);
+ glui32 arrlen);
extern unsigned char glk_char_to_lower(unsigned char ch);
extern unsigned char glk_char_to_upper(unsigned char ch);
extern winid_t glk_window_get_root(void);
extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size,
- glui32 wintype, glui32 rock);
+ glui32 wintype, glui32 rock);
extern void glk_window_close(winid_t win, stream_result_t *result);
extern void glk_window_get_size(winid_t win, glui32 *widthptr,
- glui32 *heightptr);
+ glui32 *heightptr);
extern void glk_window_set_arrangement(winid_t win, glui32 method,
- glui32 size, winid_t keywin);
+ glui32 size, winid_t keywin);
extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr,
- glui32 *sizeptr, winid_t *keywinptr);
+ glui32 *sizeptr, winid_t *keywinptr);
extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr);
extern glui32 glk_window_get_rock(winid_t win);
extern glui32 glk_window_get_type(winid_t win);
extern void glk_set_window(winid_t win);
extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode,
- glui32 rock);
+ glui32 rock);
extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode,
- glui32 rock);
+ glui32 rock);
extern void glk_stream_close(strid_t str, stream_result_t *result);
extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr);
extern glui32 glk_stream_get_rock(strid_t str);
extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len);
extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint,
- glsi32 val);
+ glsi32 val);
extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint);
extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2);
extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint,
- glui32 *result);
+ glui32 *result);
extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock);
extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name,
- glui32 rock);
+ glui32 rock);
extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode,
- glui32 rock);
+ glui32 rock);
extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref,
- glui32 rock);
+ glui32 rock);
extern void glk_fileref_destroy(frefid_t fref);
extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr);
extern glui32 glk_fileref_get_rock(frefid_t fref);
extern void glk_request_timer_events(glui32 millisecs);
extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen,
- glui32 initlen);
+ glui32 initlen);
extern void glk_request_char_event(winid_t win);
extern void glk_request_mouse_event(winid_t win);
#ifdef GLK_MODULE_LINE_TERMINATORS
extern void glk_set_terminators_line_event(winid_t win, glui32 *keycodes,
- glui32 count);
+ glui32 count);
#endif /* GLK_MODULE_LINE_TERMINATORS */
#ifdef GLK_MODULE_UNICODE
extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len,
- glui32 numchars);
+ glui32 numchars);
extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len,
- glui32 numchars);
+ glui32 numchars);
extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len,
- glui32 numchars, glui32 lowerrest);
+ glui32 numchars, glui32 lowerrest);
extern void glk_put_char_uni(glui32 ch);
extern void glk_put_string_uni(glui32 *s);
extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len);
extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode,
- glui32 rock);
+ glui32 rock);
extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen,
- glui32 fmode, glui32 rock);
+ glui32 fmode, glui32 rock);
extern void glk_request_char_event_uni(winid_t win);
extern void glk_request_line_event_uni(winid_t win, glui32 *buf,
- glui32 maxlen, glui32 initlen);
+ glui32 maxlen, glui32 initlen);
#endif /* GLK_MODULE_UNICODE */
#ifdef GLK_MODULE_UNICODE_NORM
extern glui32 glk_buffer_canon_decompose_uni(glui32 *buf, glui32 len,
- glui32 numchars);
+ glui32 numchars);
extern glui32 glk_buffer_canon_normalize_uni(glui32 *buf, glui32 len,
- glui32 numchars);
+ glui32 numchars);
#endif /* GLK_MODULE_UNICODE_NORM */
extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2);
extern glui32 glk_image_draw_scaled(winid_t win, glui32 image,
- glsi32 val1, glsi32 val2, glui32 width, glui32 height);
+ glsi32 val1, glsi32 val2, glui32 width, glui32 height);
extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height);
extern void glk_window_flow_break(winid_t win);
extern void glk_window_erase_rect(winid_t win,
- glsi32 left, glsi32 top, glui32 width, glui32 height);
+ glsi32 left, glsi32 top, glui32 width, glui32 height);
extern void glk_window_fill_rect(winid_t win, glui32 color,
- glsi32 left, glsi32 top, glui32 width, glui32 height);
+ glsi32 left, glsi32 top, glui32 width, glui32 height);
extern void glk_window_set_background_color(winid_t win, glui32 color);
#endif /* GLK_MODULE_IMAGE */
extern glui32 glk_schannel_play(schanid_t chan, glui32 snd);
extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats,
- glui32 notify);
+ glui32 notify);
extern void glk_schannel_stop(schanid_t chan);
extern void glk_schannel_set_volume(schanid_t chan, glui32 vol);
extern void glk_sound_load_hint(glui32 snd, glui32 flag);
+#ifdef GLK_MODULE_SOUND2
+/* Note that this section is nested inside the #ifdef GLK_MODULE_SOUND.
+ GLK_MODULE_SOUND must be defined if GLK_MODULE_SOUND2 is. */
+
+extern schanid_t glk_schannel_create_ext(glui32 rock, glui32 volume);
+extern glui32 glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount,
+ glui32 *sndarray, glui32 soundcount, glui32 notify);
+extern void glk_schannel_pause(schanid_t chan);
+extern void glk_schannel_unpause(schanid_t chan);
+extern void glk_schannel_set_volume_ext(schanid_t chan, glui32 vol,
+ glui32 duration, glui32 notify);
+
+#endif /* GLK_MODULE_SOUND2 */
#endif /* GLK_MODULE_SOUND */
#ifdef GLK_MODULE_HYPERLINKS
extern void glk_time_to_date_utc(glktimeval_t *time, glkdate_t *date);
extern void glk_time_to_date_local(glktimeval_t *time, glkdate_t *date);
extern void glk_simple_time_to_date_utc(glsi32 time, glui32 factor,
- glkdate_t *date);
+ glkdate_t *date);
extern void glk_simple_time_to_date_local(glsi32 time, glui32 factor,
- glkdate_t *date);
+ glkdate_t *date);
extern void glk_date_to_time_utc(glkdate_t *date, glktimeval_t *time);
extern void glk_date_to_time_local(glkdate_t *date, glktimeval_t *time);
extern glsi32 glk_date_to_simple_time_utc(glkdate_t *date, glui32 factor);
#include "resource.h"
#include "event.h"
+#define VOLUME_TIMER_RESOLUTION 1.0 /* In milliseconds */
+
extern GPrivate *glk_data_key;
#ifdef GSTREAMER_SOUND
*/
schanid_t
glk_schannel_create(glui32 rock)
+{
+ return glk_schannel_create_ext(rock, 0x10000);
+}
+
+/**
+ * glk_schannel_create_ext:
+ * @rock: The rock value to give the new sound channel.
+ * @volume: Integer representing the volume; 0x10000 is 100%.
+ *
+ * [DRAFT SPEC]
+ *
+ * The glk_schannel_create_ext() call lets you create a channel with the volume
+ * already set at a given level.
+ *
+ * Returns: A new sound channel, or %NULL.
+ */
+schanid_t
+glk_schannel_create_ext(glui32 rock, glui32 volume)
{
#ifdef GSTREAMER_SOUND
ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
goto fail;
}
+ /* Set the initial volume */
+ glk_schannel_set_volume(s, volume);
+
/* Put the elements in the pipeline and link as many together as we can
without knowing the type of the audio stream */
gst_bin_add_many(GST_BIN(s->pipeline), s->source, s->typefind, s->convert, s->filter, s->sink, NULL);
glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
{
VALID_SCHANNEL(chan, return 0);
- g_printerr("Play sound %d with repeats %d and notify %d\n", snd, repeats, notify);
#ifdef GSTREAMER_SOUND
ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
GInputStream *stream;
g_object_set(chan->source, "stream", stream, NULL);
g_object_unref(stream); /* Now owned by GStreamer element */
- if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
- WARNING_S(_("Could not set GstElement state to"), "PLAYING");
+ /* Play the sound; unless the channel is paused, then pause it instead */
+ if(!gst_element_set_state(chan->pipeline, chan->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
+ WARNING_S(_("Could not set GstElement state to"), chan->paused? "PAUSED" : "PLAYING");
return 0;
}
return 1;
#endif
}
+/**
+ * glk_schannel_play_multi:
+ * @chanarray: Array of #schanid_t structures.
+ * @chancount: Length of @chanarray.
+ * @sndarray: Array of sound resource numbers.
+ * @soundcount: Length of @sndarray, must be equal to @chanarray.
+ * @notify: If nonzero, request a notification when each sound finishes.
+ *
+ * [DRAFT SPEC]
+ *
+ * This works the same as glk_schannel_play_ext(), except that you can specify
+ * more than one sound. The channel references and sound resource numbers are
+ * given as two arrays, which must be the same length. The @notify argument
+ * applies to all the sounds; the repeats value for all the sounds is 1.
+ *
+ * All the sounds will begin at exactly the same time.
+ *
+ * This returns the number of sounds that began playing correctly. (This will be
+ * a number from 0 to @soundcount.)
+ *
+ * <note><para>
+ * Note that you have to supply @chancount and @soundcount as separate
+ * arguments, even though they are required to be the same. This is an awkward
+ * consequence of the way array arguments are dispatched in Glulx.
+ * </para></note>
+ *
+ * Returns: The number of sounds that started playing correctly.
+ */
+glui32
+glk_schannel_play_multi(schanid_t *chanarray, glui32 chancount, glui32 *sndarray, glui32 soundcount, glui32 notify)
+{
+ g_return_val_if_fail(chancount == soundcount, 0);
+ g_return_val_if_fail(chanarray != NULL || chancount == 0, 0);
+ g_return_val_if_fail(sndarray != NULL || soundcount == 0, 0);
+
+ int count;
+ for(count = 0; count < chancount; count++)
+ VALID_SCHANNEL(chanarray[count], return 0);
+
+#ifdef GSTREAMER_SOUND
+ ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+ GInputStream *stream;
+
+ if(!glk_data->resource_map && !glk_data->resource_load_callback) {
+ WARNING(_("No resource map has been loaded yet."));
+ return 0;
+ }
+
+ /* We keep an array of sounds to skip if any of them have errors */
+ gboolean *skiparray = g_new0(gboolean, chancount);
+
+ /* Set up all the channels one by one */
+ for(count = 0; count < chancount; count++) {
+ /* Stop the previous sound */
+ clean_up_after_playing_sound(chanarray[count]);
+
+ /* Load the sound into a GInputStream, by whatever method */
+ if(!glk_data->resource_map) {
+ gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, sndarray[count], glk_data->resource_load_callback_data);
+ if(!filename) {
+ WARNING(_("Error loading resource from alternative location."));
+ skiparray[count] = TRUE;
+ continue;
+ }
+
+ GError *err = NULL;
+ GFile *file = g_file_new_for_path(filename);
+ stream = G_INPUT_STREAM(g_file_read(file, NULL, &err));
+ if(!stream) {
+ IO_WARNING(_("Error loading resource from file"), filename, err->message);
+ g_free(filename);
+ g_object_unref(file);
+ skiparray[count] = TRUE;
+ continue;
+ }
+ g_free(filename);
+ g_object_unref(file);
+ } else {
+ giblorb_result_t resource;
+ giblorb_err_t result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, sndarray[count]);
+ if(result != giblorb_err_None) {
+ WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
+ skiparray[count] = TRUE;
+ continue;
+ }
+ stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
+ }
+
+ chanarray[count]->repeats = 1;
+ chanarray[count]->resource = sndarray[count];
+ chanarray[count]->notify = notify;
+ g_object_set(chanarray[count]->source, "stream", stream, NULL);
+ g_object_unref(stream); /* Now owned by GStreamer element */
+ }
+
+ /* Start all the sounds as close to each other as possible. */
+ /* FIXME: Is there a way to start them exactly at the same time? */
+ glui32 successes = 0;
+ for(count = 0; count < chancount; count++) {
+ if(skiparray[count])
+ continue;
+ /* Play the sound; unless the channel is paused, then pause it instead */
+ if(!gst_element_set_state(chanarray[count]->pipeline, chanarray[count]->paused? GST_STATE_PAUSED : GST_STATE_PLAYING)) {
+ WARNING_S(_("Could not set GstElement state to"), chanarray[count]->paused? "PAUSED" : "PLAYING");
+ skiparray[count] = TRUE;
+ continue;
+ }
+ successes++;
+ }
+ g_free(skiparray);
+ return successes;
+#else
+ return 0;
+#endif
+}
+
/**
* glk_schannel_stop:
* @chan: Channel to silence.
#endif
}
+/**
+ * glk_schannel_pause:
+ * @chan: Channel to pause.
+ *
+ * [DRAFT SPEC]
+ *
+ * Pause any sound playing in the channel. This does not generate any
+ * notification events. If the channel is already paused, this does nothing.
+ *
+ * New sounds started in a paused channel are paused immediately.
+ *
+ * A volume change in progress is <emphasis>not</emphasis> paused, and may
+ * proceed to completion, generating a notification if appropriate.
+ */
+void
+glk_schannel_pause(schanid_t chan)
+{
+ VALID_SCHANNEL(chan, return);
+
+ if(chan->paused)
+ return; /* Silently do nothing */
+
+ /* Mark the channel as paused even if there is no sound playing yet */
+ chan->paused = TRUE;
+
+ GstState state;
+ if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
+ WARNING(_("Could not get GstElement state"));
+ return;
+ }
+ if(state != GST_STATE_PLAYING)
+ return; /* Silently do nothing if no sound is playing */
+
+ if(!gst_element_set_state(chan->pipeline, GST_STATE_PAUSED)) {
+ WARNING_S(_("Could not set GstElement state to"), "PAUSED");
+ return;
+ }
+}
+
+/**
+ * glk_schannel_unpause:
+ * @chan: Channel to unpause.
+ *
+ * [DRAFT SPEC]
+ *
+ * Unpause the channel. Any paused sounds begin playing where they left off. If
+ * the channel is not already paused, this does nothing.
+ */
+void
+glk_schannel_unpause(schanid_t chan)
+{
+ VALID_SCHANNEL(chan, return);
+
+ if(!chan->paused)
+ return; /* Silently do nothing */
+
+ /* Mark the channel as not paused in any case */
+ chan->paused = FALSE;
+
+ GstState state;
+ if(gst_element_get_state(chan->pipeline, &state, NULL, GST_CLOCK_TIME_NONE) != GST_STATE_CHANGE_SUCCESS) {
+ WARNING(_("Could not get GstElement state"));
+ return;
+ }
+ if(state != GST_STATE_PAUSED)
+ return; /* Silently do nothing */
+
+ if(!gst_element_set_state(chan->pipeline, GST_STATE_PLAYING)) {
+ WARNING_S(_("Could not set GstElement state to"), "PLAYING");
+ return;
+ }
+}
+
/**
* glk_schannel_set_volume:
* @chan: Channel to set the volume of.
*/
void
glk_schannel_set_volume(schanid_t chan, glui32 vol)
+{
+ glk_schannel_set_volume_ext(chan, vol, 0, 0);
+}
+
+static double
+volume_glk_to_gstreamer(glui32 volume_glk)
+{
+ return CLAMP(((double)volume_glk / 0x10000), 0.0, 10.0);
+}
+
+#ifdef GSTREAMER_SOUND
+static gboolean
+volume_change_timeout(schanid_t chan)
+{
+ GTimeVal now;
+ g_get_current_time(&now);
+
+ if(now.tv_sec >= chan->target_time_sec && now.tv_usec >= chan->target_time_usec) {
+ /* We're done - make sure the volume is at the requested level */
+ g_object_set(chan->filter, "volume", chan->target_volume, NULL);
+
+ if(chan->volume_notify)
+ event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, chan->volume_notify);
+
+ chan->volume_timer_id = 0;
+ return FALSE;
+ }
+
+ /* Calculate the appropriate step every time - a busy system may delay or
+ * drop timer ticks */
+ double time_left_msec = (chan->target_time_sec - now.tv_sec) * 1000.0
+ + (chan->target_time_usec - now.tv_usec) / 1000.0;
+ double steps_left = time_left_msec / VOLUME_TIMER_RESOLUTION;
+ double current_volume;
+ g_object_get(chan->filter, "volume", ¤t_volume, NULL);
+ double volume_step = (chan->target_volume - current_volume) / steps_left;
+
+ g_object_set(chan->filter, "volume", current_volume + volume_step, NULL);
+
+ return TRUE;
+}
+#endif /* GSTREAMER_SOUND */
+
+/**
+ * glk_schannel_set_volume_ext:
+ * @chan: Channel to set the volume of.
+ * @vol: Integer representing the volume; 0x10000 is 100%.
+ * @duration: Length of volume change in milliseconds, or 0 for immediate.
+ * @notify: If nonzero, requests a notification when the volume change finishes.
+ *
+ * [DRAFT SPEC]
+ *
+ * Sets the volume in the channel, from 0 (silence) to 0x10000 (full volume).
+ * Again, you can overdrive the volume by setting a value greater than 0x10000,
+ * but this is not recommended.
+ *
+ * If the @duration is zero, the change is immediate. Otherwise, the change
+ * begins immediately, and occurs smoothly over the next @duration milliseconds.
+ *
+ * The @notify value should be nonzero in order to request a volume notification
+ * event. If you do this, when the volume change is completed, you will get an
+ * event with type #evtype_VolumeNotify. The window will be %NULL, @val1 will be
+ * zero, and @val2 will be the nonzero value you passed as @notify.
+ *
+ * The glk_schannel_set_volume() does not include @duration and @notify values.
+ * Both are assumed to be zero: immediate change, no notification.
+ *
+ * You can call these functions between sounds, or while a sound is playing.
+ * However, a zero-duration change while a sound is playing may produce
+ * unpleasant clicks.
+ *
+ * At most one volume change can be occurring on a sound channel at any time. If
+ * you call one of these functions while a previous volume change is in
+ * progress, the previous change is interrupted. The beginning point of the new
+ * volume change should be wherever the previous volume change was interrupted
+ * (rather than the previous change's beginning or ending point).
+ *
+ * Not all libraries support these functions. You should test the appropriate
+ * gestalt selectors before you rely on them; see "Testing for Sound
+ * Capabilities".
+ */
+void
+glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32 notify)
{
VALID_SCHANNEL(chan, return);
+ /* Silently ignore out-of-range volume values */
+
#ifdef GSTREAMER_SOUND
- gdouble volume_gst = (gdouble)vol / 0x10000;
- g_printerr("Volume set to: %f\n", volume_gst);
- g_object_set(chan->filter, "volume", CLAMP(volume_gst, 0.0, 10.0), NULL);
+ /* Interrupt a previous volume change */
+ if(chan->volume_timer_id > 0)
+ g_source_remove(chan->volume_timer_id);
+
+ double target_volume = volume_glk_to_gstreamer(vol);
+
+ if(duration == 0) {
+ g_object_set(chan->filter, "volume", target_volume, NULL);
+
+ if(notify != 0)
+ event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, notify);
+
+ return;
+ }
+
+ GTimeVal target_time;
+ g_get_current_time(&target_time);
+ g_time_val_add(&target_time, (long)duration * 1000);
+
+ chan->target_volume = target_volume;
+ chan->target_time_sec = target_time.tv_sec;
+ chan->target_time_usec = target_time.tv_usec;
+ chan->volume_notify = notify;
+
+ /* Set up a timer for the volume */
+ chan->volume_timer_id = g_timeout_add(VOLUME_TIMER_RESOLUTION, (GSourceFunc)volume_change_timeout, chan);
#endif
}
glui32 resource, notify;
/* How many times to repeat the last sound played (-1 = forever) */
glui32 repeats;
+ /* Whether channel is paused */
+ gboolean paused;
+ /* Volume change information */
+ double target_volume;
+ long target_time_sec, target_time_usec;
+ guint volume_timer_id;
+ glui32 volume_notify;
+
#ifdef GSTREAMER_SOUND
/* Each sound channel is represented as a GStreamer pipeline. */
GstElement *pipeline, *source, *typefind, *demux, *decode, *convert, *filter, *sink;
#endif
};
-#endif
\ No newline at end of file
+#endif
#include <unistd.h>
#include <string.h>
+#define NUM_CHANNELS 2
+
void
glk_main(void)
{
- if(!glk_gestalt(gestalt_Sound, 0)) {
+ if(!glk_gestalt(gestalt_Sound2, 0)) {
fprintf(stderr, "Sound not supported.\n");
return;
}
- if(!glk_gestalt(gestalt_SoundVolume, 0)) {
- fprintf(stderr, "Sound volume not supported.\n");
- return;
- }
- if(!glk_gestalt(gestalt_SoundNotify, 0)) {
- fprintf(stderr, "Sound notification not supported.\n");
- return;
- }
- schanid_t sc = glk_schannel_create(0);
- if(!sc) {
- fprintf(stderr, "Could not create sound channel.\n");
- return;
+ schanid_t sc[NUM_CHANNELS];
+ int count;
+ for(count = 0; count < NUM_CHANNELS; count++) {
+ sc[count] = glk_schannel_create(count);
+ if(!sc[count]) {
+ fprintf(stderr, "Could not create sound channel number %d.\n", count);
+ return;
+ }
}
/* Open the main window. */
glk_set_window(mainwin);
glk_put_string("Copy a sound file to the current directory and rename it "
"to SND3. Supported formats: AIFF, OGG, MOD, S3M, IT, XM. Type 'play' "
- "to play it.\n");
+ "to play it.\n\n"
+ "If you want to test multi-sound playing, copy another sound file and "
+ "rename it to SND4 as well. You can't stop it, so make it a short "
+ "sound effect.\n");
char buffer[1024];
int len;
int finish = 0;
int repeat = 1;
+ int ramp = 0;
event_t ev;
while(!finish) {
finish = 1;
} else if(strcmp(buffer, "play") == 0) {
glk_put_string("Playing sound.\n");
- if(!glk_schannel_play_ext(sc, 3, repeat, 1)) {
+ if(!glk_schannel_play_ext(sc[0], 3, repeat, 1)) {
fprintf(stderr, "Could not start sound channel.\n");
finish = 1;
}
} else if(strcmp(buffer, "stop") == 0) {
glk_put_string("Stopping sound.\n");
- glk_schannel_stop(sc);
+ glk_schannel_stop(sc[0]);
} else if(strcmp(buffer, "repeat") == 0) {
glk_put_string("Setting repeat to ");
if(repeat == 1) {
glk_put_string("ONCE.\n");
repeat = 1;
}
+ } else if(strcmp(buffer, "pause") == 0) {
+ glk_put_string("Pausing channel.\n");
+ glk_schannel_pause(sc[0]);
+ } else if(strcmp(buffer, "unpause") == 0) {
+ glk_put_string("Unpausing channel.\n");
+ glk_schannel_unpause(sc[0]);
+ } else if(strcmp(buffer, "ramp") == 0) {
+ glk_put_string("Ramping volume to ");
+ if(ramp == 0) {
+ glk_put_string("HALF.\n");
+ glk_schannel_set_volume_ext(sc[0], 0x8000, 3000, 42);
+ ramp = 1;
+ } else if(ramp == 1) {
+ glk_put_string("FULL.\n");
+ glk_schannel_set_volume_ext(sc[0], 0x10000, 3000, 69);
+ ramp = 0;
+ }
+ } else if(strcmp(buffer, "multi") == 0) {
+ glk_put_string("Playing two sounds. (These will not repeat.)\n");
+ glui32 sounds[NUM_CHANNELS] = { 3, 4 };
+ if(glk_schannel_play_multi(sc, NUM_CHANNELS, sounds, NUM_CHANNELS, 1) < 2) {
+ fprintf(stderr, "Tried to start %d sounds, but not all were successful.", NUM_CHANNELS);
+ finish = 1;
+ }
} else if(strcmp(buffer, "help") == 0) {
- glk_put_string("Type PLAY or REPEAT or STOP or QUIT.\n");
+ glk_put_string("Type PLAY or MULTI or REPEAT or PAUSE or UNPAUSE or RAMP or STOP or QUIT.\n");
}
break;
case evtype_SoundNotify:
glk_cancel_line_event(mainwin, NULL);
glk_put_string("\nGot sound notify event!\n");
break;
+ case evtype_VolumeNotify:
+ glk_cancel_line_event(mainwin, NULL);
+ glk_put_string("\nGot volume notify event!\n");
+ break;
default:
;
}
}
- glk_schannel_stop(sc);
- glk_schannel_destroy(sc);
+ for(count = 0; count < NUM_CHANNELS; count++) {
+ glk_schannel_stop(sc[count]);
+ glk_schannel_destroy(sc[count]);
+ }
}