Implement volume change notifications
[projects/chimara/chimara.git] / libchimara / schannel.c
index d8c924d51373fb52330631449282e5b7f1226c72..f4c7bd63a43bdc7f62b1d3826e170f5f07fa4ab5 100644 (file)
@@ -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
@@ -678,6 +680,21 @@ glk_schannel_unpause(schanid_t chan)
        }
 }
 
+static double
+volume_glk_to_gstreamer(glui32 volume_glk)
+{
+       return CLAMP(((double)volume_glk / 0x10000), 0.0, 10.0);
+}
+
+static void
+channel_set_volume_immediately(schanid_t chan, double volume, glui32 notify)
+{
+       g_object_set(chan->filter, "volume", volume, NULL);
+
+       if(notify != 0)
+               event_throw(chan->glk, evtype_VolumeNotify, NULL, 0, notify);
+}
+
 /**
  * glk_schannel_set_volume:
  * @chan: Channel to set the volume of.
@@ -710,7 +727,43 @@ glk_schannel_unpause(schanid_t chan)
 void 
 glk_schannel_set_volume(schanid_t chan, glui32 vol)
 {
-       glk_schannel_set_volume_ext(chan, vol, 0, 0);
+       VALID_SCHANNEL(chan, return);
+       /* Silently ignore out-of-range volume values */
+
+#ifdef GSTREAMER_SOUND
+       double volume = volume_glk_to_gstreamer(vol);
+       channel_set_volume_immediately(chan, volume, 0);
+#endif
+}
+
+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);
+
+               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", &current_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;
 }
 
 /**
@@ -758,11 +811,25 @@ glk_schannel_set_volume_ext(schanid_t chan, glui32 vol, glui32 duration, glui32
        /* Silently ignore out-of-range volume values */
        
 #ifdef GSTREAMER_SOUND
-       gdouble volume_gst = (gdouble)vol / 0x10000;
-       g_object_set(chan->filter, "volume", CLAMP(volume_gst, 0.0, 10.0), NULL);
-#endif
+       double target_volume = volume_glk_to_gstreamer(vol);
+
+       if(duration == 0) {
+               channel_set_volume_immediately(chan, target_volume, notify);
+               return;
+       }
 
-       /* Not implemented */
+       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 */
+       g_timeout_add(VOLUME_TIMER_RESOLUTION, (GSourceFunc)volume_change_timeout, chan);
+#endif
 }
 
 /**