libs/gst/controller/: Factor out the 'set' logic into gst_controller_set_unlocked...
authorSebastian Dröge <slomo@circular-chaos.org>
Wed, 6 Jun 2007 14:01:56 +0000 (14:01 +0000)
committerSebastian Dröge <slomo@circular-chaos.org>
Wed, 6 Jun 2007 14:01:56 +0000 (14:01 +0000)
Original commit message from CVS:
* libs/gst/controller/gstcontroller.c:
(gst_controlled_property_set_interpolation_mode),
(gst_controlled_property_prepend_default),
(gst_controlled_property_new), (gst_controller_set_unlocked),
(gst_controller_set), (gst_controller_set_from_list),
(gst_controller_unset), (gst_controller_unset_all):
* libs/gst/controller/gstcontrollerprivate.h:
* libs/gst/controller/gstinterpolation.c:
Factor out the 'set' logic into gst_controller_set_unlocked for the
gst_controller_set and gst_controller_set_from_list functions.
To make life of the interpolators easier always add a control point
at timestamp zero with the default value.
In the linear interpolator make things more obvious by better variable
naming (slope).
Implement cubic interpolation mode (by using a natural cubic spline)
and map the quadratic interpolation mode to this too (as quadratic
doesn't make much sense, see discussion on the list).
* tests/check/libs/controller.c: (GST_START_TEST),
(gst_controller_suite):
Add unit test for the cubic interpolation mode and check everywhere
if the interpolation mode could be set as expected.

ChangeLog
libs/gst/controller/gstcontroller.c
libs/gst/controller/gstcontrollerprivate.h
libs/gst/controller/gstinterpolation.c
tests/check/libs/controller.c

index b14d172..a45286d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,31 @@
+2007-06-06  Sebastian Dröge  <slomo@circular-chaos.org>
+
+       * libs/gst/controller/gstcontroller.c:
+       (gst_controlled_property_set_interpolation_mode),
+       (gst_controlled_property_prepend_default),
+       (gst_controlled_property_new), (gst_controller_set_unlocked),
+       (gst_controller_set), (gst_controller_set_from_list),
+       (gst_controller_unset), (gst_controller_unset_all):
+       * libs/gst/controller/gstcontrollerprivate.h:
+       * libs/gst/controller/gstinterpolation.c:
+       Factor out the 'set' logic into gst_controller_set_unlocked for the
+       gst_controller_set and gst_controller_set_from_list functions.
+
+       To make life of the interpolators easier always add a control point
+       at timestamp zero with the default value.
+
+       In the linear interpolator make things more obvious by better variable
+       naming (slope).
+
+       Implement cubic interpolation mode (by using a natural cubic spline)
+       and map the quadratic interpolation mode to this too (as quadratic
+       doesn't make much sense, see discussion on the list).
+
+       * tests/check/libs/controller.c: (GST_START_TEST),
+       (gst_controller_suite):
+       Add unit test for the cubic interpolation mode and check everywhere
+       if the interpolation mode could be set as expected.
+
 2007-06-06  Tim-Philipp Müller  <tim at centricular dot net>
 
        * gst/gstparamspecs.c: (gst_param_spec_fraction_get_type):
index 10de02b..d2b5008 100644 (file)
@@ -1,6 +1,7 @@
 /* GStreamer
  *
  * Copyright (C) <2005> Stefan Kost <ensonic at users dot sf dot net>
+ * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org>
  *
  * gstcontroller.c: dynamic parameter control subsystem
  *
@@ -240,21 +241,40 @@ gst_controlled_property_set_interpolation_mode (GstControlledProperty * self,
         self->get = NULL;
         self->get_value_array = NULL;
     }
-    if (!self->get) {           /* || !self->get_value_array) */
+    if (!self->get || !self->get_value_array) {
       GST_WARNING ("incomplete implementation for type %lu/%lu:'%s'/'%s'",
           self->type, self->base,
           g_type_name (self->type), g_type_name (self->base));
       res = FALSE;
     }
+    if (mode == GST_INTERPOLATE_QUADRATIC) {
+      GST_WARNING ("Quadratic interpolation mode is deprecated, using cubic"
+          "interpolation mode");
+    }
   } else {
     /* TODO shouldn't this also get a GstInterpolateMethod *user_method
        for the case mode==GST_INTERPOLATE_USER
      */
     res = FALSE;
   }
+
+  self->valid_cache = FALSE;
+
   return (res);
 }
 
+static void
+gst_controlled_property_prepend_default (GstControlledProperty * prop)
+{
+  GstControlPoint *cp = g_new0 (GstControlPoint, 1);
+
+  cp->timestamp = 0;
+  g_value_init (&cp->value, prop->type);
+  g_value_copy (&prop->default_value, &cp->value);
+  prop->values = g_list_prepend (prop->values, cp);
+  prop->nvalues++;
+}
+
 /*
  * gst_controlled_property_new:
  * @object: for which object the controlled property should be set up
@@ -374,13 +394,14 @@ gst_controlled_property_new (GObject * object, const gchar * name)
           GST_WARNING ("incomplete implementation for paramspec type '%s'",
               G_PARAM_SPEC_TYPE_NAME (pspec));
       }
-      /* TODO what about adding a control point with timestamp=0 and value=default
-       * a bit easier for interpolators, example:
-       * first timestamp is at 5
-       * requested value if for timestamp=3
-       * LINEAR and Co. would need to interpolate from default value to value
-       * at timestamp 5
-       */
+
+      prop->valid_cache = FALSE;
+      prop->nvalues = 0;
+
+      /* Add a control point at timestamp 0 with the default value
+       * to make the life of interpolators easier. */
+      gst_controlled_property_prepend_default (prop);
+
       signal_name = g_alloca (8 + 1 + strlen (name));
       g_sprintf (signal_name, "notify::%s", name);
       prop->notify_handler_id =
@@ -718,6 +739,42 @@ gst_controller_remove_properties (GstController * self, ...)
   return (res);
 }
 
+static gboolean
+gst_controller_set_unlocked (GstController * self, GstControlledProperty * prop,
+    GstClockTime timestamp, GValue * value)
+{
+  gboolean res = FALSE;
+
+  if (G_VALUE_TYPE (value) == prop->type) {
+    GstControlPoint *cp;
+    GList *node;
+
+    /* check if a control point for the timestamp already exists */
+    if ((node = g_list_find_custom (prop->values, &timestamp,
+                gst_control_point_find))) {
+      cp = node->data;
+      g_value_reset (&cp->value);
+      g_value_copy (value, &cp->value);
+    } else {
+      /* create a new GstControlPoint */
+      cp = g_new0 (GstControlPoint, 1);
+      cp->timestamp = timestamp;
+      g_value_init (&cp->value, prop->type);
+      g_value_copy (value, &cp->value);
+      /* and sort it into the prop->values list */
+      prop->values =
+          g_list_insert_sorted (prop->values, cp, gst_control_point_compare);
+      prop->nvalues++;
+    }
+    prop->valid_cache = FALSE;
+    res = TRUE;
+  } else {
+    GST_WARNING ("incompatible value type for property '%s'", prop->name);
+  }
+
+  return res;
+}
+
 /**
  * gst_controller_set:
  * @self: the controller object which handles the properties
@@ -743,30 +800,7 @@ gst_controller_set (GstController * self, gchar * property_name,
 
   g_mutex_lock (self->lock);
   if ((prop = gst_controller_find_controlled_property (self, property_name))) {
-    if (G_VALUE_TYPE (value) == prop->type) {
-      GstControlPoint *cp;
-      GList *node;
-
-      /* check if a control point for the timestamp already exists */
-      if ((node = g_list_find_custom (prop->values, &timestamp,
-                  gst_control_point_find))) {
-        cp = node->data;
-        g_value_reset (&cp->value);
-        g_value_copy (value, &cp->value);
-      } else {
-        /* create a new GstControlPoint */
-        cp = g_new0 (GstControlPoint, 1);
-        cp->timestamp = timestamp;
-        g_value_init (&cp->value, prop->type);
-        g_value_copy (value, &cp->value);
-        /* and sort it into the prop->values list */
-        prop->values =
-            g_list_insert_sorted (prop->values, cp, gst_control_point_compare);
-      }
-      res = TRUE;
-    } else {
-      GST_WARNING ("incompatible value type for property '%s'", prop->name);
-    }
+    res = gst_controller_set_unlocked (self, prop, timestamp, value);
   }
   g_mutex_unlock (self->lock);
 
@@ -792,7 +826,6 @@ gst_controller_set_from_list (GstController * self, gchar * property_name,
   GstControlledProperty *prop;
   GSList *node;
   GstTimedValue *tv;
-  GstControlPoint *cp;
 
   g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
   g_return_val_if_fail (property_name, FALSE);
@@ -801,22 +834,15 @@ gst_controller_set_from_list (GstController * self, gchar * property_name,
   if ((prop = gst_controller_find_controlled_property (self, property_name))) {
     for (node = timedvalues; node; node = g_slist_next (node)) {
       tv = node->data;
-      if (G_VALUE_TYPE (&tv->value) == prop->type) {
-        if (GST_CLOCK_TIME_IS_VALID (tv->timestamp)) {
-          cp = g_new0 (GstControlPoint, 1);
-          cp->timestamp = tv->timestamp;
-          g_value_init (&cp->value, prop->type);
-          g_value_copy (&tv->value, &cp->value);
-          prop->values =
-              g_list_insert_sorted (prop->values, cp,
-              gst_control_point_compare);
-          res = TRUE;
-        } else {
-          g_warning ("GstTimedValued with invalid timestamp passed to %s "
-              "for property '%s'", GST_FUNCTION, property_name);
-        }
+      if (!GST_CLOCK_TIME_IS_VALID (tv->timestamp)) {
+        GST_WARNING ("GstTimedValued with invalid timestamp passed to %s "
+            "for property '%s'", GST_FUNCTION, property_name);
+      } else if (!G_IS_VALUE (&tv->value)) {
+        GST_WARNING ("GstTimedValued with invalid value passed to %s "
+            "for property '%s'", GST_FUNCTION, property_name);
       } else {
-        GST_WARNING ("incompatible value type for property '%s'", prop->name);
+        res =
+            gst_controller_set_unlocked (self, prop, tv->timestamp, &tv->value);
       }
     }
   }
@@ -854,10 +880,20 @@ gst_controller_unset (GstController * self, gchar * property_name,
     /* check if a control point for the timestamp exists */
     if ((node = g_list_find_custom (prop->values, &timestamp,
                 gst_control_point_find))) {
-      if (node == prop->last_requested_value)
-        prop->last_requested_value = NULL;
-      gst_control_point_free (node->data);      /* free GstControlPoint */
-      prop->values = g_list_delete_link (prop->values, node);
+      GstControlPoint *cp = node->data;
+
+      if (cp->timestamp == 0) {
+        /* Restore the default node */
+        g_value_reset (&cp->value);
+        g_value_copy (&prop->default_value, &cp->value);
+      } else {
+        if (node == prop->last_requested_value)
+          prop->last_requested_value = NULL;
+        gst_control_point_free (node->data);    /* free GstControlPoint */
+        prop->values = g_list_delete_link (prop->values, node);
+        prop->nvalues--;
+      }
+      prop->valid_cache = FALSE;
       res = TRUE;
     }
   }
@@ -893,6 +929,12 @@ gst_controller_unset_all (GstController * self, gchar * property_name)
     g_list_free (prop->values);
     prop->last_requested_value = NULL;
     prop->values = NULL;
+    prop->nvalues = 0;
+    prop->valid_cache = FALSE;
+
+    /* Insert the default control point again */
+    gst_controlled_property_prepend_default (prop);
+
     res = TRUE;
   }
   g_mutex_unlock (self->lock);
@@ -1146,8 +1188,8 @@ gst_controller_get_value_array (GstController * self, GstClockTime timestamp,
  *
  * Sets the given interpolation mode on the given property.
  *
- * <note><para>Quadratic, cubic and user interpolation is not yet available.
- * </para></note>
+ * <note><para>User interpolation is not yet available and quadratic interpolation
+ * is deprecated and maps to cubic interpolation.</para></note>
  *
  * Returns: %TRUE if the property is handled by the controller, %FALSE otherwise
  */
index 398ae67..0bd4cde 100644 (file)
@@ -81,6 +81,14 @@ typedef struct _GstControlPoint
 
   /* internal fields */
 
+  /* Caches for the interpolators */
+  union {
+    struct {
+      gdouble h;
+      gdouble z;
+    } cubic;
+  } cache;
+
 } GstControlPoint;
 
 /**
@@ -106,10 +114,9 @@ typedef struct _GstControlledProperty
   InterpolateGetValueArray get_value_array;
 
   GList *values;                /* List of GstControlPoint */
+  gint nvalues;                 /* Number of control points */
   GList *last_requested_value;  /* last search result, can be used for incremental searches */
-
-  /*< private >*/
-  gpointer       _gst_reserved[GST_PADDING];
+  gboolean valid_cache;
 } GstControlledProperty;
 
 #define GST_CONTROLLED_PROPERTY(obj)    ((GstControlledProperty *)(obj))
index 76fa1e8..f1c6d93 100644 (file)
@@ -1,6 +1,7 @@
 /* GStreamer
  *
  * Copyright (C) <2005> Stefan Kost <ensonic at users dot sf dot net>
+ * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org>
  *
  * gstinterpolation.c: Interpolation methods for dynamic properties
  *
@@ -284,17 +285,16 @@ _interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime times
     \
     cp1 = node->data; \
     if ((node = g_list_next (node))) { \
-      gdouble timediff,valuediff; \
+      gdouble slope; \
       g##type value1,value2; \
       \
       cp2 = node->data; \
       \
-      timediff = gst_guint64_to_gdouble (cp2->timestamp - cp1->timestamp); \
       value1 = g_value_get_##type (&cp1->value); \
       value2 = g_value_get_##type (&cp2->value); \
-      valuediff = (gdouble) (value2 - value1); \
+      slope = (gdouble) (value2 - value1) / gst_guint64_to_gdouble (cp2->timestamp - cp1->timestamp); \
       \
-      return ((g##type) (value1 + valuediff * (gst_guint64_to_gdouble (timestamp - cp1->timestamp) / timediff))); \
+      return ((g##type) (value1 + gst_guint64_to_gdouble (timestamp - cp1->timestamp) * slope)); \
     } \
     else { \
       return (g_value_get_##type (&cp1->value)); \
@@ -360,13 +360,205 @@ static GstInterpolateMethod interpolate_linear = {
 
 /*  cubic interpolation */
 
+/* The following functions implement a natural cubic spline interpolator.
+ * For details look at http://en.wikipedia.org/wiki/Spline_interpolation
+ *
+ * Instead of using a real matrix with n^2 elements for the linear system
+ * of equations we use three arrays o, p, q to hold the tridiagonal matrix
+ * as following to save memory:
+ *
+ * p[0] q[0]    0    0    0
+ * o[1] p[1] q[1]    0    0
+ *    0 o[2] p[2] q[2]    .
+ *    .    .    .    .    .
+ */
+
+#define DEFINE_CUBIC_GET(type) \
+static void \
+_interpolate_cubic_update_cache_##type (GstControlledProperty *prop) \
+{ \
+  gint i, n = prop->nvalues; \
+  gdouble *o = g_new0 (gdouble, n); \
+  gdouble *p = g_new0 (gdouble, n); \
+  gdouble *q = g_new0 (gdouble, n); \
+  \
+  gdouble *h = g_new0 (gdouble, n); \
+  gdouble *b = g_new0 (gdouble, n); \
+  gdouble *z = g_new0 (gdouble, n); \
+  \
+  GList *node; \
+  GstControlPoint *cp; \
+  GstClockTime x_prev, x, x_next; \
+  g##type y_prev, y, y_next; \
+  \
+  /* Fill linear system of equations */ \
+  node = prop->values; \
+  cp = node->data; \
+  x = cp->timestamp; \
+  y = g_value_get_##type (&cp->value); \
+  \
+  p[0] = 1.0; \
+  \
+  node = node->next; \
+  cp = node->data; \
+  x_next = cp->timestamp; \
+  y_next = g_value_get_##type (&cp->value); \
+  h[0] = x_next - x; \
+  \
+  for (i = 1; i < n-1; i++) { \
+    /* Shuffle x and y values */ \
+    x_prev = x; \
+    y_prev = y; \
+    x = x_next; \
+    y = y_next; \
+    node = node->next; \
+    cp = node->data; \
+    x_next = cp->timestamp; \
+    y_next = g_value_get_##type (&cp->value); \
+    \
+    h[i] = x_next - x; \
+    o[i] = h[i-1]; \
+    p[i] = 2.0 * (h[i-1] + h[i]); \
+    q[i] = h[i]; \
+    b[i] = (y_next - y) / h[i] - (y - y_prev) / h[i-1]; \
+  } \
+  p[n-1] = 1.0; \
+  \
+  /* Use Gauss elimination to set everything below the \
+   * diagonal to zero */ \
+  for (i = 1; i < n-1; i++) { \
+    gdouble a = o[i] / p[i-1]; \
+    p[i] -= a * q[i-1]; \
+    b[i] -= a * b[i-1]; \
+  } \
+  \
+  /* Solve everything else from bottom to top */ \
+  for (i = n-2; i > 0; i--) \
+    z[i] = (b[i] - q[i] * z[i+1]) / p[i]; \
+  \
+  /* Save cache next in the GstControlPoint */ \
+  \
+  node = prop->values; \
+  for (i = 0; i < n; i++) { \
+    cp = node->data; \
+    cp->cache.cubic.h = h[i]; \
+    cp->cache.cubic.z = z[i]; \
+    node = node->next; \
+  } \
+  \
+  /* Free our temporary arrays */ \
+  g_free (o); \
+  g_free (p); \
+  g_free (q); \
+  g_free (h); \
+  g_free (b); \
+  g_free (z); \
+} \
+\
+static g##type \
+_interpolate_cubic_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
+{ \
+  GList *node; \
+  \
+  if (prop->nvalues <= 2) \
+    return _interpolate_linear_get_##type (prop, timestamp); \
+  \
+  if (!prop->valid_cache) { \
+    _interpolate_cubic_update_cache_##type (prop); \
+    prop->valid_cache = TRUE; \
+  } \
+  \
+  if ((node = gst_controlled_property_find_control_point_node (prop, timestamp))) { \
+    GstControlPoint *cp1, *cp2; \
+    \
+    cp1 = node->data; \
+    if ((node = g_list_next (node))) { \
+      gdouble diff1, diff2; \
+      g##type value1,value2; \
+      gdouble ret; \
+      \
+      cp2 = node->data; \
+      \
+      value1 = g_value_get_##type (&cp1->value); \
+      value2 = g_value_get_##type (&cp2->value); \
+      \
+      diff1 = gst_guint64_to_gdouble (timestamp - cp1->timestamp); \
+      diff2 = gst_guint64_to_gdouble (cp2->timestamp - timestamp); \
+      \
+      ret = (cp2->cache.cubic.z * diff1 * diff1 * diff1 + cp1->cache.cubic.z * diff2 * diff2 * diff2) / cp1->cache.cubic.h; \
+      ret += (value2 / cp1->cache.cubic.h - cp1->cache.cubic.h * cp2->cache.cubic.z) * diff1; \
+      ret += (value1 / cp1->cache.cubic.h - cp1->cache.cubic.h * cp1->cache.cubic.z) * diff2; \
+      \
+      return (g##type) ret; \
+    } \
+    else { \
+      return (g_value_get_##type (&cp1->value)); \
+    } \
+  } \
+  return (g_value_get_##type (&prop->default_value)); \
+} \
+\
+static GValue * \
+interpolate_cubic_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
+{ \
+  g_value_set_##type (&prop->result_value,_interpolate_cubic_get_##type (prop,timestamp)); \
+  return (&prop->result_value); \
+} \
+\
+static gboolean \
+interpolate_cubic_get_##type##_value_array (GstControlledProperty * prop, \
+    GstClockTime timestamp, GstValueArray * value_array) \
+{ \
+  gint i; \
+  GstClockTime ts = timestamp; \
+  g##type *values = (g##type *) value_array->values; \
+  \
+  for(i = 0; i < value_array->nbsamples; i++) { \
+    *values = _interpolate_cubic_get_##type (prop, ts); \
+    ts += value_array->sample_interval; \
+    values++; \
+  } \
+  return (TRUE); \
+}
+
+DEFINE_CUBIC_GET (int);
+
+DEFINE_CUBIC_GET (uint);
+DEFINE_CUBIC_GET (long);
+
+DEFINE_CUBIC_GET (ulong);
+DEFINE_CUBIC_GET (float);
+DEFINE_CUBIC_GET (double);
+
+static GstInterpolateMethod interpolate_cubic = {
+  interpolate_cubic_get_int,
+  interpolate_cubic_get_int_value_array,
+  interpolate_cubic_get_uint,
+  interpolate_cubic_get_uint_value_array,
+  interpolate_cubic_get_long,
+  interpolate_cubic_get_long_value_array,
+  interpolate_cubic_get_ulong,
+  interpolate_cubic_get_ulong_value_array,
+  interpolate_cubic_get_float,
+  interpolate_cubic_get_float_value_array,
+  interpolate_cubic_get_double,
+  interpolate_cubic_get_double_value_array,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+
 /*  register all interpolation methods */
 GstInterpolateMethod *interpolation_methods[] = {
   &interpolate_none,
   &interpolate_trigger,
   &interpolate_linear,
-  NULL,
-  NULL
+  &interpolate_cubic,
+  &interpolate_cubic
 };
 
 guint num_interpolation_methods = G_N_ELEMENTS (interpolation_methods);
index dd52ed2..b6cdc06 100644 (file)
@@ -524,7 +524,8 @@ GST_START_TEST (controller_interpolate_none)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_NONE);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_NONE));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -567,8 +568,8 @@ GST_START_TEST (controller_interpolate_trigger)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong",
-      GST_INTERPOLATE_TRIGGER);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_TRIGGER));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -612,7 +613,8 @@ GST_START_TEST (controller_interpolate_linear)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_LINEAR);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_LINEAR));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -638,6 +640,63 @@ GST_START_TEST (controller_interpolate_linear)
 
 GST_END_TEST;
 
+/* test timed value handling with cubic interpolation */
+GST_START_TEST (controller_interpolate_cubic)
+{
+  GstController *ctrl;
+  GstElement *elem;
+  gboolean res;
+  GValue val_double = { 0, };
+
+  gst_controller_init (NULL, NULL);
+
+  elem = gst_element_factory_make ("testmonosource", "test_source");
+
+  /* that property should exist and should be controllable */
+  ctrl = gst_controller_new (G_OBJECT (elem), "double", NULL);
+  fail_unless (ctrl != NULL, NULL);
+
+  /* set interpolation mode */
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "double",
+          GST_INTERPOLATE_CUBIC));
+
+  /* set control values */
+  g_value_init (&val_double, G_TYPE_DOUBLE);
+  g_value_set_double (&val_double, 0.0);
+  res = gst_controller_set (ctrl, "double", 0 * GST_SECOND, &val_double);
+  fail_unless (res, NULL);
+  g_value_set_double (&val_double, 5.0);
+  res = gst_controller_set (ctrl, "double", 1 * GST_SECOND, &val_double);
+  fail_unless (res, NULL);
+  g_value_set_double (&val_double, 2.0);
+  res = gst_controller_set (ctrl, "double", 2 * GST_SECOND, &val_double);
+  fail_unless (res, NULL);
+  g_value_set_double (&val_double, 8.0);
+  res = gst_controller_set (ctrl, "double", 4 * GST_SECOND, &val_double);
+  fail_unless (res, NULL);
+
+  /* now pull in values for some timestamps */
+  gst_controller_sync_values (ctrl, 0 * GST_SECOND);
+  fail_unless (GST_TEST_MONO_SOURCE (elem)->val_double == 0.0, NULL);
+  gst_controller_sync_values (ctrl, 1 * GST_SECOND);
+  fail_unless (GST_TEST_MONO_SOURCE (elem)->val_double == 5.0, NULL);
+  gst_controller_sync_values (ctrl, 2 * GST_SECOND);
+  fail_unless (GST_TEST_MONO_SOURCE (elem)->val_double == 2.0, NULL);
+  gst_controller_sync_values (ctrl, 3 * GST_SECOND);
+  fail_unless (GST_TEST_MONO_SOURCE (elem)->val_double > 2.0 &&
+      GST_TEST_MONO_SOURCE (elem)->val_double < 8.0, NULL);
+  gst_controller_sync_values (ctrl, 4 * GST_SECOND);
+  fail_unless (GST_TEST_MONO_SOURCE (elem)->val_double == 8.0, NULL);
+  gst_controller_sync_values (ctrl, 5 * GST_SECOND);
+  fail_unless (GST_TEST_MONO_SOURCE (elem)->val_double == 8.0, NULL);
+
+  GST_INFO ("controller->ref_count=%d", G_OBJECT (ctrl)->ref_count);
+  g_object_unref (ctrl);
+  gst_object_unref (elem);
+}
+
+GST_END_TEST;
+
 /* make sure we don't crash when someone sets an unsupported interpolation
  * mode */
 GST_START_TEST (controller_interpolate_unimplemented)
@@ -653,16 +712,9 @@ GST_START_TEST (controller_interpolate_unimplemented)
   ctrl = gst_controller_new (G_OBJECT (elem), "ulong", NULL);
   fail_unless (ctrl != NULL, NULL);
 
-  /* set unsupported interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_CUBIC);
-
-  /* set another unsupported interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong",
-      GST_INTERPOLATE_QUADRATIC);
-
   /* set completely bogus interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong",
-      (GstInterpolateMode) 93871);
+  fail_if (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          (GstInterpolateMode) 93871));
 
   g_object_unref (ctrl);
   gst_object_unref (elem);
@@ -687,7 +739,8 @@ GST_START_TEST (controller_unset)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_NONE);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_NONE));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -743,7 +796,8 @@ GST_START_TEST (controller_unset_all)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_NONE);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_NONE));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -792,7 +846,8 @@ GST_START_TEST (controller_live)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_LINEAR);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_LINEAR));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -866,7 +921,8 @@ GST_START_TEST (controller_misc)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_LINEAR);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_LINEAR));
 
   /* set control value */
   tval = g_new0 (GstTimedValue, 1);
@@ -876,7 +932,7 @@ GST_START_TEST (controller_misc)
 
   list = g_slist_append (list, tval);
 
-  ASSERT_WARNING (fail_if (gst_controller_set_from_list (ctrl, "ulong", list)));
+  fail_if (gst_controller_set_from_list (ctrl, "ulong", list));
 
   /* try again with a valid stamp, should work now */
   tval->timestamp = 0;
@@ -989,7 +1045,8 @@ GST_START_TEST (controller_interpolate_linear_value_array)
   fail_unless (ctrl != NULL, NULL);
 
   /* set interpolation mode */
-  gst_controller_set_interpolation_mode (ctrl, "ulong", GST_INTERPOLATE_LINEAR);
+  fail_unless (gst_controller_set_interpolation_mode (ctrl, "ulong",
+          GST_INTERPOLATE_LINEAR));
 
   /* set control values */
   g_value_init (&val_ulong, G_TYPE_ULONG);
@@ -1041,6 +1098,7 @@ gst_controller_suite (void)
   tcase_add_test (tc, controller_interpolate_none);
   tcase_add_test (tc, controller_interpolate_trigger);
   tcase_add_test (tc, controller_interpolate_linear);
+  tcase_add_test (tc, controller_interpolate_cubic);
   tcase_add_test (tc, controller_interpolate_unimplemented);
   tcase_add_test (tc, controller_unset);
   tcase_add_test (tc, controller_unset_all);