From: Philip Chimento Date: Wed, 18 Jan 2012 12:59:59 +0000 (+0100) Subject: Merge branch 'new-sound-api' X-Git-Tag: v0.9~47 X-Git-Url: https://git.stderr.nl/gitweb?a=commitdiff_plain;h=1461483626f28b52f8f9f3cc350f9cb330579285;hp=28c6b69cb8e971a9959066fe896edcc4ae412935;p=projects%2Fchimara%2Fchimara.git Merge branch 'new-sound-api' Conflicts: docs/reference/chimara-docs.sgml --- diff --git a/docs/reference/chimara-sections.txt b/docs/reference/chimara-sections.txt index 809f3a5..994f21b 100644 --- a/docs/reference/chimara-sections.txt +++ b/docs/reference/chimara-sections.txt @@ -130,6 +130,7 @@ gestalt_MouseInput gestalt_Timer gestalt_Graphics gestalt_DrawImage +gestalt_Sound2 gestalt_Sound gestalt_SoundVolume gestalt_SoundNotify @@ -494,6 +495,7 @@ GLK_MODULE_IMAGE glk-sound-channels Creating and Destroying Sound Channels glk_schannel_create +glk_schannel_create_ext glk_schannel_destroy @@ -502,8 +504,12 @@ glk_schannel_destroy Playing Sounds 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 @@ -518,6 +524,7 @@ glk_schannel_get_rock glk-sound-testing Testing for Sound Capabilities GLK_MODULE_SOUND +GLK_MODULE_SOUND2
diff --git a/libchimara/doc.c b/libchimara/doc.c index 4b0a273..f058e27 100644 --- a/libchimara/doc.c +++ b/libchimara/doc.c @@ -1045,10 +1045,15 @@ * 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. */ /** @@ -1577,10 +1582,24 @@ * 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 this chapter. 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); @@ -1592,7 +1611,7 @@ * 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. */ /** @@ -1605,7 +1624,7 @@ * 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. */ /** @@ -1617,7 +1636,7 @@ * 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. */ /** diff --git a/libchimara/gestalt.c b/libchimara/gestalt.c index 6dbffa1..46e640d 100644 --- a/libchimara/gestalt.c +++ b/libchimara/gestalt.c @@ -130,6 +130,7 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) case gestalt_SoundVolume: case gestalt_SoundNotify: case gestalt_SoundMusic: + case gestalt_Sound2: #ifdef GSTREAMER_SOUND return 1; #else diff --git a/libchimara/gi_dispa.c b/libchimara/gi_dispa.c index 4564ae4..f0099b9 100644 --- a/libchimara/gi_dispa.c +++ b/libchimara/gi_dispa.c @@ -1,4 +1,4 @@ -/* 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 http://eblong.com/zarf/glk/ @@ -49,6 +49,7 @@ static gidispatch_intconst_t intconstant_table[] = { { "evtype_Redraw", (6) }, { "evtype_SoundNotify", (7) }, { "evtype_Timer", (1) }, + { "evtype_VolumeNotify", (9) }, { "filemode_Read", (0x02) }, { "filemode_ReadWrite", (0x03) }, @@ -80,6 +81,7 @@ static gidispatch_intconst_t intconstant_table[] = { { "gestalt_LineTerminators", (18) }, { "gestalt_MouseInput", (4) }, { "gestalt_Sound", (8) }, + { "gestalt_Sound2", (21) }, { "gestalt_SoundMusic", (13) }, { "gestalt_SoundNotify", (10) }, { "gestalt_SoundVolume", (9) }, @@ -260,6 +262,13 @@ static gidispatch_function_t function_table[] = { { 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" }, @@ -544,6 +553,19 @@ char *gidispatch_prototype(glui32 funcnum) 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 @@ -1081,6 +1103,31 @@ void gidispatch_call(glui32 funcnum, glui32 numargs, gluniversal_t *arglist) 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 diff --git a/libchimara/gi_dispa.h b/libchimara/gi_dispa.h index 6ea045c..6ec3b96 100644 --- a/libchimara/gi_dispa.h +++ b/libchimara/gi_dispa.h @@ -1,7 +1,7 @@ #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 http://www.eblong.com/zarf/glk/index.html diff --git a/libchimara/glk.h b/libchimara/glk.h index d6ca327..3bcbc3d 100644 --- a/libchimara/glk.h +++ b/libchimara/glk.h @@ -1,41 +1,43 @@ #ifndef GLK_H #define GLK_H -/* glk.h: Header file for Glk API, version 0.7.2. - Designed by Andrew Plotkin - 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 + 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 , 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 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; @@ -65,6 +67,7 @@ typedef struct glk_schannel_struct *schanid_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) @@ -75,6 +78,7 @@ typedef struct glk_schannel_struct *schanid_t; #define evtype_Redraw (6) #define evtype_SoundNotify (7) #define evtype_Hyperlink (8) +#define evtype_VolumeNotify (9) typedef struct event_struct { glui32 type; @@ -185,7 +189,7 @@ typedef struct stream_result_struct { #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); @@ -194,21 +198,21 @@ extern void glk_tick(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); @@ -223,9 +227,9 @@ extern strid_t glk_window_get_echo_stream(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); @@ -248,19 +252,19 @@ extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len); 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); @@ -273,7 +277,7 @@ extern void glk_select_poll(event_t *event); 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); @@ -287,17 +291,17 @@ extern void glk_set_echo_line_event(winid_t win, glui32 val); #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); @@ -311,22 +315,22 @@ extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); 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 */ @@ -340,15 +344,15 @@ extern glui32 glk_buffer_canon_normalize_uni(glui32 *buf, glui32 len, 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 */ @@ -362,12 +366,25 @@ extern glui32 glk_schannel_get_rock(schanid_t chan); 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 @@ -403,9 +420,9 @@ extern glsi32 glk_current_simple_time(glui32 factor); 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); diff --git a/libchimara/schannel.c b/libchimara/schannel.c index 8cd7b52..d176d7d 100644 --- a/libchimara/schannel.c +++ b/libchimara/schannel.c @@ -13,6 +13,8 @@ #include "resource.h" #include "event.h" +#define VOLUME_TIMER_RESOLUTION 1.0 /* In milliseconds */ + extern GPrivate *glk_data_key; #ifdef GSTREAMER_SOUND @@ -174,6 +176,24 @@ finally: */ 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); @@ -213,6 +233,9 @@ glk_schannel_create(glui32 rock) 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); @@ -389,7 +412,6 @@ glui32 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; @@ -442,8 +464,9 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify) 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; @@ -452,6 +475,122 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify) #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 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. + * + * + * 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. @@ -468,6 +607,79 @@ glk_schannel_stop(schanid_t chan) #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 not 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. @@ -499,12 +711,120 @@ glk_schannel_stop(schanid_t chan) */ 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 } diff --git a/libchimara/schannel.h b/libchimara/schannel.h index 465e7dc..a0eba87 100644 --- a/libchimara/schannel.h +++ b/libchimara/schannel.h @@ -25,11 +25,19 @@ struct glk_schannel_struct 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 diff --git a/tests/soundtest.c b/tests/soundtest.c index e89dcf3..5540624 100644 --- a/tests/soundtest.c +++ b/tests/soundtest.c @@ -3,26 +3,24 @@ #include #include +#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. */ @@ -35,12 +33,16 @@ glk_main(void) 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) { @@ -67,13 +69,13 @@ glk_main(void) 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) { @@ -89,19 +91,49 @@ glk_main(void) 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]); + } }