Merge branch 'sound'
authorP. F. Chimento <philip.chimento@gmail.com>
Sun, 30 Jan 2011 16:10:37 +0000 (17:10 +0100)
committerP. F. Chimento <philip.chimento@gmail.com>
Sun, 30 Jan 2011 16:10:37 +0000 (17:10 +0100)
Conflicts:
tests/Makefile.am

12 files changed:
.gitignore
configure.ac
libchimara/chimara-glk-private.h
libchimara/dispatch.c
libchimara/gestalt.c
libchimara/init.c
libchimara/schannel.c
libchimara/schannel.h
tests/Makefile.am
tests/Sound Test.gblorb [new file with mode: 0644]
tests/plugin-loader.c
tests/soundtest.c [new file with mode: 0644]

index 16deb0cbb4a87350346d3e9efbb59cee66e45cab..449d2775b1bc0299ed9e52775a823ee9bc330053 100644 (file)
@@ -19,3 +19,6 @@ m4
 missing
 stamp-h1
 ylwrap
+*.anjuta*
+TODO.tasks
+*~
index 81677ccaf92e1936506ca3e593179a373bbdea2f..5611efdf52df997dcfce8ba2be4c447d55e7d1a5 100644 (file)
@@ -105,6 +105,17 @@ AC_ARG_ENABLE([rpm],
 )
 AM_CONDITIONAL([BUILDING_RPM], [$TEST "x$rpm" = xtrue])
 
+### SOUND LIBRARY TO USE ######################################################
+
+AC_ARG_WITH([gstreamer],
+       [AS_HELP_STRING([--without-gstreamer], [Disable GStreamer sound])],
+       [],
+       [with_gstreamer=yes])
+SOUND_MODULE=
+AS_IF([$TEST "x$with_gstreamer" != xno], 
+       [AC_DEFINE([GSTREAMER_SOUND], [1], [Define to enable sound support with GStreamer])
+       SOUND_MODULE="gstreamer-0.10 >= 0.10.12 gstreamer-plugins-base-0.10 gstreamer-plugins-bad-0.10"])
+
 ### CHECK FOR LIBRARIES #######################################################
 
 # Libraries needed to build Chimara library
@@ -114,6 +125,7 @@ PKG_CHECK_MODULES([CHIMARA], [
        gthread-2.0 
        gmodule-2.0
        pango
+       $SOUND_MODULE
 ])
 CHIMARA_LIBS="$CHIMARA_LIBS -lm"
 AC_SUBST(CHIMARA_LIBS)
index e4f4123f5a00e9bd8dc00ed3817532a78718d456..67366707f92af252c0354a97e5dc08041a934ec7 100644 (file)
@@ -83,6 +83,8 @@ struct _ChimaraGlkPrivate {
     strid_t current_stream;
     /* List of streams currently in existence */
     GList *stream_list;
+       /* List of sound channels currently in existence */
+       GList *schannel_list;
        /* Current timer */
        guint timer_id;
        /* Current resource blorb map */
index f15472750d12de0518c488330fcf0ca6448e9b54..c428fbb7e4a39943f13b0e7047b92cdf2478f93d 100644 (file)
@@ -3,6 +3,7 @@
 #include "window.h"
 #include "stream.h"
 #include "fileref.h"
+#include "schannel.h"
 
 extern GPrivate *glk_data_key;
 
@@ -117,6 +118,8 @@ gidispatch_get_objrock(void *obj, glui32 objclass)
                        return ((strid_t)obj)->disprock;
                case gidisp_Class_Fileref:
                        return ((frefid_t)obj)->disprock;
+               case gidisp_Class_Schannel:
+                       return ((schanid_t)obj)->disprock;
                default: 
                {
                        gidispatch_rock_t dummy;
index e3529094233d4f88cf055abc7af0023b76562421..ebeb765f20a9f59d00697fa5758d345f1f6f33c1 100644 (file)
@@ -1,4 +1,5 @@
 #include <stddef.h> /* Surprisingly, the only symbol needed is NULL */
+#include <config.h>
 #include "glk.h"
 
 /* Version of the Glk specification implemented by this library */
@@ -124,11 +125,18 @@ glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen)
 
                case gestalt_GraphicsTransparency:
                        return 1;
-                       
-               /* Unsupported capabilities */
+
+               /* Capabilities supported if compiled with GStreamer */
                case gestalt_Sound:
                case gestalt_SoundVolume:
                case gestalt_SoundNotify:
+#ifdef GSTREAMER_SOUND
+                       return 1;
+#else
+                       return 0;
+#endif
+                       
+               /* Unsupported capabilities */
                case gestalt_SoundMusic:
                /* Selector not supported */    
                default:
index bfc67dd033bb4d994a9d2fc9cb21c29ea89c228f..b0da4c435947186794551f36dedf71d4697b71aa 100644 (file)
@@ -1,6 +1,9 @@
 #include <config.h>
 #include <glib.h>
 #include <glib/gi18n-lib.h>
+#ifdef GSTREAMER_SOUND
+#include <gst/gst.h>
+#endif
 
 static gboolean chimara_initialized = FALSE;
 
@@ -21,7 +24,15 @@ chimara_init(void)
                                " the thread system by calling g_threads_init() and "
                                "gdk_threads_init() BEFORE the initial call to gtk_init() in "
                                "your main program."));
-               
+
+#ifdef GSTREAMER_SOUND
+               /* Make sure GStreamer has been initialized if it hasn't been already;
+               in particular, if you want your program to parse GStreamer command line
+               options then you should do it yourself, before gtk_init(). */
+               if( !gst_is_initialized() )
+                       gst_init(NULL, NULL);
+#endif
+
                /* Initialize thread-private data */
                extern GPrivate *glk_data_key;
                glk_data_key = g_private_new(NULL);
index d8bfefc9945b685828f5e33033d923a8b4f0a5c2..637ec96d2376721dc6ffd7a1dac563bd6b522456 100644 (file)
@@ -1,8 +1,165 @@
+#include <config.h>
 #include <glib.h>
+#include <glib/gi18n.h>
 #include <libchimara/glk.h>
-
+#ifdef GSTREAMER_SOUND
+#include <gst/gst.h>
+#endif
 #include "magic.h"
 #include "schannel.h"
+#include "chimara-glk-private.h"
+#include "gi_dispa.h"
+#include "gi_blorb.h"
+#include "resource.h"
+#include "event.h"
+
+extern GPrivate *glk_data_key;
+
+#ifdef GSTREAMER_SOUND
+/* Stop any currently playing sound on this channel, and remove any
+ format-specific GStreamer elements from the channel. */
+static void
+clean_up_after_playing_sound(schanid_t chan)
+{
+       if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
+               WARNING_S(_("Could not set GstElement state to"), "NULL");
+       if(chan->demux)
+       {
+               gst_bin_remove(GST_BIN(chan->pipeline), chan->demux);
+               chan->demux = NULL;
+       }
+       if(chan->decode)
+       {
+               gst_bin_remove(GST_BIN(chan->pipeline), chan->decode);
+               chan->decode = NULL;
+       }
+}
+
+/* This signal is thrown whenever the GStreamer pipeline generates a message.
+ Most messages are harmless. */
+static void
+on_pipeline_message(GstBus *bus, GstMessage *message, schanid_t s)
+{
+       /* g_printerr("Got %s message\n", GST_MESSAGE_TYPE_NAME(message)); */
+
+       GError *err;
+       gchar *debug_message;
+       
+       switch(GST_MESSAGE_TYPE(message)) {
+       case GST_MESSAGE_ERROR: 
+       {
+               gst_message_parse_error(message, &err, &debug_message);
+               IO_WARNING(_("GStreamer error"), err->message, debug_message);
+               g_error_free(err);
+               g_free(debug_message);
+               clean_up_after_playing_sound(s);
+       }
+               break;
+       case GST_MESSAGE_WARNING:
+       {
+               gst_message_parse_warning(message, &err, &debug_message);
+               IO_WARNING(_("GStreamer warning"), err->message, debug_message);
+               g_error_free(err);
+               g_free(debug_message);
+       }
+               break;
+       case GST_MESSAGE_INFO:
+       {
+               gst_message_parse_info(message, &err, &debug_message);
+               g_message("GStreamer info \"%s\": %s", err->message, debug_message);
+               g_error_free(err);
+               g_free(debug_message);
+       }
+               break;
+       case GST_MESSAGE_EOS: /* End of stream */
+               /* Decrease repeats if not set to forever */
+               if(s->repeats != (glui32)-1)
+                       s->repeats--;
+               if(s->repeats > 0) {
+                       if(!gst_element_seek_simple(s->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 0)) {
+                               WARNING(_("Could not execute GStreamer seek"));
+                               clean_up_after_playing_sound(s);
+                       }
+               } else {
+                       clean_up_after_playing_sound(s);
+                       /* Sound ended normally, send a notification if requested */
+                       if(s->notify)
+                               event_throw(s->glk, evtype_SoundNotify, NULL, s->resource, s->notify);
+               }
+               break;
+       default:
+               /* unhandled message */
+               break;
+       }
+}
+
+/* This signal is thrown when the OGG demuxer element has decided what kind of
+ outputs it will output. We connect the decoder element dynamically. */
+static void
+on_ogg_demuxer_pad_added(GstElement *demux, GstPad *pad, schanid_t s)
+{
+       GstPad *sinkpad;
+       
+       /* We can now link this pad with the vorbis-decoder sink pad */
+       sinkpad = gst_element_get_static_pad(s->decode, "sink");
+       if(gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK)
+               WARNING(_("Could not link OGG demuxer with Vorbis decoder"));
+       gst_object_unref(sinkpad);
+}
+
+/* This signal is thrown when the typefinder element has found the type of its
+ input. Now that we know what kind of input stream we have, we can connect the
+ proper demuxer/decoder elements. */
+static void
+on_type_found(GstElement *typefind, guint probability, GstCaps *caps, schanid_t s)
+{
+       gchar *type = gst_caps_to_string(caps);
+       if(strcmp(type, "application/ogg") == 0) {
+               s->demux = gst_element_factory_make("oggdemux", NULL);
+               s->decode = gst_element_factory_make("vorbisdec", NULL);
+               if(!s->demux || !s->decode) {
+                       WARNING(_("Could not create one or more GStreamer elements"));
+                       goto finally;
+               }
+               gst_bin_add_many(GST_BIN(s->pipeline), s->demux, s->decode, NULL);
+               if(!gst_element_link(s->typefind, s->demux) || !gst_element_link(s->decode, s->convert)) {
+                       WARNING(_("Could not link GStreamer elements"));
+                       goto finally;
+               }
+               /* We link the demuxer and decoder together dynamically, since the
+                demuxer doesn't know what source pads it will have until it starts
+                demuxing the stream */
+               g_signal_connect(s->demux, "pad-added", G_CALLBACK(on_ogg_demuxer_pad_added), s);
+       } else if(strcmp(type, "audio/x-aiff") == 0) {
+               s->decode = gst_element_factory_make("aiffparse", NULL);
+               if(!s->decode) {
+                       WARNING(_("Could not create 'aiffparse' GStreamer element"));
+                       goto finally;
+               }
+               gst_bin_add(GST_BIN(s->pipeline), s->decode);
+               if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
+                       WARNING(_("Could not link GStreamer elements"));
+                       goto finally;
+               }
+       } else if(strcmp(type, "audio/x-mod") == 0) {
+               s->decode = gst_element_factory_make("modplug", NULL);
+               if(!s->decode) {
+                       WARNING(_("Could not create 'modplug' GStreamer element"));
+                       goto finally;
+               }
+               gst_bin_add(GST_BIN(s->pipeline), s->decode);
+               if(!gst_element_link_many(s->typefind, s->decode, s->convert, NULL)) {
+                       WARNING(_("Could not link GStreamer elements"));
+                       goto finally;
+               }
+       } else {
+               WARNING_S(_("Unexpected audio type in blorb"), type);
+       }
+
+finally:
+       g_free(type);
+}
+#endif /* GSTREAMER_SOUND */
 
 /**
  * glk_schannel_create:
  * Remember that it is possible that the library will be unable to create a new
  * channel, in which case glk_schannel_create() will return %NULL.
  *
- * <warning><para>This function is not implemented yet.</para></warning>
- *
  * Returns: A new sound channel, or %NULL.
  */
 schanid_t 
 glk_schannel_create(glui32 rock)
 {
+#ifdef GSTREAMER_SOUND
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+       schanid_t s = g_new0(struct glk_schannel_struct, 1);
+       s->magic = MAGIC_SCHANNEL;
+       s->rock = rock;
+       if(glk_data->register_obj)
+               s->disprock = (*glk_data->register_obj)(s, gidisp_Class_Schannel);
+
+       /* Add it to the global sound channel list */
+       glk_data->schannel_list = g_list_prepend(glk_data->schannel_list, s);
+       s->schannel_list = glk_data->schannel_list;
+
+       /* Add a pointer to the ChimaraGlk widget, for convenience */
+       s->glk = glk_data->self;
+
+       /* Create a GStreamer pipeline for the sound channel */
+       gchar *pipeline_name = g_strdup_printf("pipeline-%p", s);
+       s->pipeline = gst_pipeline_new(pipeline_name);
+       g_free(pipeline_name);
+
+       /* Watch for messages from the pipeline */
+       GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(s->pipeline));
+       gst_bus_add_signal_watch(bus);
+       g_signal_connect(bus, "message", G_CALLBACK(on_pipeline_message), s);
+       gst_object_unref(bus);
+
+       /* Create GStreamer elements to put in the pipeline */
+       s->source = gst_element_factory_make("giostreamsrc", NULL);
+       s->typefind = gst_element_factory_make("typefind", NULL);
+       s->convert = gst_element_factory_make("audioconvert", NULL);
+       s->filter = gst_element_factory_make("volume", NULL);
+       s->sink = gst_element_factory_make("autoaudiosink", NULL);
+       if(!s->source || !s->typefind || !s->convert || !s->filter || !s->sink) {
+               WARNING(_("Could not create one or more GStreamer elements"));
+               goto fail;
+       }
+
+       /* 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);
+       /* Link elements: Source -> typefinder -> ??? -> Converter -> Volume filter -> Sink */
+       if(!gst_element_link(s->source, s->typefind) || !gst_element_link_many(s->convert, s->filter, s->sink, NULL)) {
+               WARNING(_("Could not link GStreamer elements"));
+               goto fail;
+       }
+       g_signal_connect(s->typefind, "have-type", G_CALLBACK(on_type_found), s);
+       
+       return s;
+
+fail:
+       glk_schannel_destroy(s);
+       return NULL;
+#else
        return NULL;
+#endif /* GSTREAMER_SOUND */
 }
 
 /**
@@ -29,13 +239,33 @@ glk_schannel_create(glui32 rock)
  *
  * Destroys the channel. If the channel is playing a sound, the sound stops 
  * immediately (with no notification event).
- *
- * <warning><para>This function is not implemented yet.</para></warning>
  */
 void 
 glk_schannel_destroy(schanid_t chan)
 {
        VALID_SCHANNEL(chan, return);
+
+#ifdef GSTREAMER_SOUND
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+       if(!gst_element_set_state(chan->pipeline, GST_STATE_NULL))
+               WARNING_S(_("Could not set GstElement state to"), "NULL");
+       
+       glk_data->schannel_list = g_list_delete_link(glk_data->schannel_list, chan->schannel_list);
+
+       if(glk_data->unregister_obj)
+       {
+               (*glk_data->unregister_obj)(chan, gidisp_Class_Schannel, chan->disprock);
+               chan->disprock.ptr = NULL;
+       }
+
+       /* This also frees all the objects inside the pipeline */
+       if(chan->pipeline)
+               gst_object_unref(chan->pipeline);
+       
+       chan->magic = MAGIC_FREE;
+       g_free(chan);
+#endif
 }
 
 /**
@@ -50,15 +280,31 @@ glk_schannel_destroy(schanid_t chan)
  * As that section describes, the order in which channels are returned is 
  * arbitrary.
  *
- * <warning><para>This function is not implemented yet.</para></warning>
- *
  * Returns: the next sound channel, or %NULL if there are no more.
  */
 schanid_t 
 glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
 {
        VALID_SCHANNEL_OR_NULL(chan, return NULL);
+
+#ifdef GSTREAMER_SOUND
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       GList *retnode;
+       
+       if(chan == NULL)
+               retnode = glk_data->schannel_list;
+       else
+               retnode = chan->schannel_list->next;
+       schanid_t retval = retnode? (schanid_t)retnode->data : NULL;
+               
+       /* Store the sound channel's rock in rockptr */
+       if(retval && rockptr)
+               *rockptr = glk_schannel_get_rock(retval);
+               
+       return retval;
+#else
        return NULL;
+#endif /* GSTREAMER_SOUND */
 }
 
 /**
@@ -68,15 +314,13 @@ glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
  * Retrieves the channel's rock value. See <link 
  * linkend="chimara-Rocks">Rocks</link>.
  *
- * <warning><para>This function is not implemented yet.</para></warning>
- *
  * Returns: A rock value.
  */
 glui32 
 glk_schannel_get_rock(schanid_t chan)
 {
        VALID_SCHANNEL(chan, return 0);
-       return 0;
+       return chan->rock;
 }
 
 /**
@@ -97,15 +341,12 @@ glk_schannel_get_rock(schanid_t chan)
  *   in which case playing a MOD resource would fail if one was already playing.
  * </para></note>
  *
- * <warning><para>This function is not implemented yet.</para></warning>
- *
  * Returns: 1 on success, 0 on failure.
  */
 glui32 
 glk_schannel_play(schanid_t chan, glui32 snd)
 {
-       VALID_SCHANNEL(chan, return 0);
-       return 0;
+       return glk_schannel_play_ext(chan, snd, 1, 0);
 }
 
 /**
@@ -141,8 +382,6 @@ glk_schannel_play(schanid_t chan, glui32 snd)
  * %gestalt_SoundNotify selector before you rely on it; see <link
  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound 
  * Capabilities</link>.
- *
- * <warning><para>This function is not implemented yet.</para></warning>
  * 
  * Returns: 1 on success, 0 on failure.
  */
@@ -150,7 +389,67 @@ 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;
+
+       /* Stop the previous sound */
+       clean_up_after_playing_sound(chan);
+
+       /* Don't play if repeats = 0 */
+       if(repeats == 0) {
+               chan->repeats = 0;
+               return 1;
+       }
+
+       /* Load the sound into a GInputStream, by whatever method */
+       if(!glk_data->resource_map) {
+               if(!glk_data->resource_load_callback) {
+                       WARNING(_("No resource map has been loaded yet."));
+                       return 0;
+               }
+               gchar *filename = glk_data->resource_load_callback(CHIMARA_RESOURCE_SOUND, snd, glk_data->resource_load_callback_data);
+               if(!filename) {
+                       WARNING(_("Error loading resource from alternative location."));
+                       return 0;
+               }
+
+               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);
+                       return 0;
+               }
+               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, snd);
+               if(result != giblorb_err_None) {
+                       WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
+                       return 0;
+               }
+               stream = g_memory_input_stream_new_from_data(resource.data.ptr, resource.length, NULL);
+       }
+
+       chan->repeats = repeats;
+       chan->resource = snd;
+       chan->notify = 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");
+               return 0;
+       }
+       return 1;
+#else
        return 0;
+#endif
 }
 
 /**
@@ -159,13 +458,14 @@ glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
  *
  * Stops any sound playing in the channel. No notification event is generated,
  * even if you requested one. If no sound is playing, this has no effect.
- *
- * <warning><para>This function is not implemented yet.</para></warning>
  */
 void 
 glk_schannel_stop(schanid_t chan)
 {
        VALID_SCHANNEL(chan, return);
+#ifdef GSTREAMER_SOUND
+       clean_up_after_playing_sound(chan);
+#endif
 }
 
 /**
@@ -192,12 +492,20 @@ glk_schannel_stop(schanid_t chan)
  * linkend="chimara-Testing-for-Sound-Capabilities">Testing for Sound
  * Capabilities</link>.
  *
- * <warning><para>This function is not implemented yet.</para></warning>
+ * <note><title>Chimara</title>
+ *   <para>Chimara supports volumes from 0 to 1000&percnt;, that is, values of
+ *   @vol up to 0xA0000.</para>
+ * </note>
  */
 void 
 glk_schannel_set_volume(schanid_t chan, glui32 vol)
 {
        VALID_SCHANNEL(chan, return);
+#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);
+#endif
 }
 
 /**
@@ -212,10 +520,41 @@ glk_schannel_set_volume(schanid_t chan, glui32 vol)
  * @flag is zero, the library may release memory or other resources associated
  * with the sound. Calling this function is always optional, and it has no
  * effect on what the library actually plays.
- *
- * <warning><para>This function is not implemented yet.</para></warning>
  */
 void 
 glk_sound_load_hint(glui32 snd, glui32 flag)
 {
+#ifdef GSTREAMER_SOUND
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       giblorb_result_t resource;
+       giblorb_err_t result;
+
+       /* Sound load hints only work for Blorb resource maps */
+       if(!glk_data->resource_map)
+               return;
+
+       if(flag) {
+               /* The sound load hint simply loads the resource from the resource map;
+                loading a chunk more than once does nothing */
+               result = giblorb_load_resource(glk_data->resource_map, giblorb_method_Memory, &resource, giblorb_ID_Snd, snd);
+               if(result != giblorb_err_None) {
+                       WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
+                       return;
+               }
+       } else {
+               /* Get the Blorb chunk number by loading the resource with
+                method_DontLoad, then unload that chunk - has no effect if the chunk
+                isn't loaded */
+               result = giblorb_load_resource(glk_data->resource_map, giblorb_method_DontLoad, &resource, giblorb_ID_Snd, snd);
+               if(result != giblorb_err_None) {
+                       WARNING_S( _("Error loading resource"), giblorb_get_error_message(result) );
+                       return;
+               }
+               result = giblorb_unload_chunk(glk_data->resource_map, resource.chunknum);
+               if(result != giblorb_err_None) {
+                       WARNING_S( _("Error unloading chunk"), giblorb_get_error_message(result) );
+                       return;
+               }
+       }
+#endif /* GSTREAMER_SOUND */
 }
index 6ec0ce31e7e6daec57a23428d0eb97819fca1d66..465e7dc51fe9b7095dbf870ffec104dd0a14bc2a 100644 (file)
@@ -1,9 +1,14 @@
 #ifndef __SCHANNEL_H__
 #define __SCHANNEL_H__
 
+#include <config.h>
 #include <glib.h>
 #include "glk.h"
 #include "gi_dispa.h"
+#include "chimara-glk.h"
+#ifdef GSTREAMER_SOUND
+#include <gst/gst.h>
+#endif
 
 struct glk_schannel_struct
 {
@@ -13,6 +18,18 @@ struct glk_schannel_struct
        /* Pointer to the list node in the global sound channel list that contains 
         this sound channel */
        GList *schannel_list;
+       /* Pointer to the GTK widget this sound channel belongs to, for convenience */
+       ChimaraGlk *glk;
+
+       /* Resource number and notification ID of last played sound */
+       glui32 resource, notify;
+       /* How many times to repeat the last sound played (-1 = forever) */
+       glui32 repeats;
+       
+#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
index 5a55db20e75c0a57e615c2a82b56cfaf9cd92381..d00d91149de4035983209657221920497096bb1f 100644 (file)
@@ -30,7 +30,7 @@ test_close_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS)
 test_close_LDADD = @TEST_LIBS@ $(top_builddir)/libchimara/libchimara.la
 
 noinst_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la \
-       styletest.la test-userstyle.la
+       styletest.la soundtest.la test-userstyle.la
 
 first_la_SOURCES = first.c
 first_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS)
@@ -52,3 +52,7 @@ styletest_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS)
 
 test_userstyle_la_SOURCES = test-userstyle.c
 test_userstyle_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS)
+
+soundtest_la_SOURCES = soundtest.c
+soundtest_la_LDFLAGS = $(TEST_PLUGIN_LIBTOOL_FLAGS)
+
diff --git a/tests/Sound Test.gblorb b/tests/Sound Test.gblorb
new file mode 100644 (file)
index 0000000..02d2e5d
Binary files /dev/null and b/tests/Sound Test.gblorb differ
index 1c560ac93875e5d4ddb054cef2d8a45ac0cf7b32..ab47f8967dad72bc6a3f68d28327995a3d914d38 100644 (file)
@@ -65,6 +65,19 @@ create_window(void)
        gtk_container_add(GTK_CONTAINER(window), glk);
 }
 
+static gchar *
+resource_load(ChimaraResourceType usage, guint32 resnum)
+{
+       char *resstr;
+       if(usage == CHIMARA_RESOURCE_IMAGE)
+               resstr = "PIC";
+       else if(usage == CHIMARA_RESOURCE_SOUND)
+               resstr = "SND";
+       else
+               resstr = "FCK";
+       return g_strdup_printf("%s%d", resstr, resnum);
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -86,6 +99,8 @@ main(int argc, char *argv[])
 
        if(argc < 2)
                g_error("Must provide a plugin\n");
+
+       chimara_glk_set_resource_load_callback(CHIMARA_GLK(glk), (ChimaraResourceLoadFunc)resource_load, NULL);
        
     if( !chimara_glk_run(CHIMARA_GLK(glk), argv[1], argc - 1, argv + 1, &error) )
                g_error("Error starting Glk library: %s\n", error->message);
diff --git a/tests/soundtest.c b/tests/soundtest.c
new file mode 100644 (file)
index 0000000..e89dcf3
--- /dev/null
@@ -0,0 +1,107 @@
+#include <libchimara/glk.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+void
+glk_main(void)
+{
+       if(!glk_gestalt(gestalt_Sound, 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;
+       }
+
+       /* Open the main window. */
+    winid_t mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
+    if (!mainwin) {
+        /* It's possible that the main window failed to open. There's
+            nothing we can do without it, so exit. */
+        return;
+    }
+       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");
+
+       char buffer[1024];
+       int len;
+       int finish = 0;
+       int repeat = 1;
+
+       event_t ev;
+       while(!finish) {
+               glk_put_string("\nprompt> ");
+               glk_request_line_event(mainwin, buffer, 1024, 0);
+               glk_select(&ev);
+               printf("Received event:\n");
+               printf("Type: %d\n", ev.type);
+               printf("Win: ");
+               if(ev.win)
+                       printf( "%d\n", glk_window_get_rock(ev.win) );
+               else
+                       printf("NULL\n");
+               printf("Var1: %d\n", ev.val1);
+               printf("Var2: %d\n", ev.val2);
+               switch(ev.type) {
+                       case evtype_LineInput:
+                               // Null-terminate string
+                               len = ev.val1;
+                               buffer[len] = '\0';
+
+                               if(strcmp(buffer, "quit") == 0) {
+                                       glk_put_string("That's all, folks.\n");
+                                       finish = 1;
+                               } else if(strcmp(buffer, "play") == 0) {
+                                       glk_put_string("Playing sound.\n");
+                                       if(!glk_schannel_play_ext(sc, 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);
+                               } else if(strcmp(buffer, "repeat") == 0) {
+                                       glk_put_string("Setting repeat to ");
+                                       if(repeat == 1) {
+                                               glk_put_string("TWICE.\n");
+                                               repeat = 2;
+                                       } else if(repeat == 2) {
+                                               glk_put_string("INFINITE.\n");
+                                               repeat = -1;
+                                       } else if(repeat == -1) {
+                                               glk_put_string("DON'T PLAY.\n");
+                                               repeat = 0;
+                                       } else if(repeat == 0) {
+                                               glk_put_string("ONCE.\n");
+                                               repeat = 1;
+                                       }
+                               } else if(strcmp(buffer, "help") == 0) {
+                                       glk_put_string("Type PLAY or REPEAT or STOP or QUIT.\n");
+                               }
+                               break;
+                       case evtype_SoundNotify:
+                               glk_cancel_line_event(mainwin, NULL);
+                               glk_put_string("\nGot sound notify event!\n");
+                               break;
+                       default:
+                               ;
+               }
+       }
+
+       glk_schannel_stop(sc);
+       glk_schannel_destroy(sc);
+}