Datahandle State API changes



   Hi!

I've now implemented an API and implementation in datahandles to query
for the length of the resampler filter state (as brainstormed on IRC),
which should also be usable for other cases (such as an FIR lowpass
handle). Here are the changes - including documentation - so please let
me know if there is anything you'd like to see fixed.

As for the elegance of the test (which reads single samples), it could
be made more elegant by using a loop handle, but I think this can be
done as a seperate commit. I am not sure it is worth it, though. Its
just a test after all.

There is one issue I was unsure with, and that is the actual meaning of
the gsl_data_handle_get_source() function. I had assumed that this
function would return the datahandle which provided the data, and thus,
for the resampling case, it should return the unresampled datahandle.

So I added a get_source() method to the resampling datahandle. However,
some other datahandles where I find it obvious to implement get_source
in a similar way, for instance the reversed handle, do not have any
get_source function. Especially, there is no chain_handle_get_source()
function in gsldatahandle.c, and any handle derived from the chained
handle doesn't have a get_source function.

Additionally, there is no documentation for
gsl_data_handle_get_source(), so there is no way to look up whats the
intended meaning. So maybe I was wrong about implementing it for the
resampling handle, or maybe it should be implemented for more handles.

   Cu... Stefan

Index: bse/gsldatahandle-vorbis.c
===================================================================
--- bse/gsldatahandle-vorbis.c	(revision 4068)
+++ bse/gsldatahandle-vorbis.c	(working copy)
@@ -365,6 +365,7 @@ static GslDataHandleFuncs dh_vorbis_vtab
   dh_vorbis_read,
   dh_vorbis_close,
   NULL,
+  NULL,
   dh_vorbis_destroy,
 };
 
Index: bse/gsldatahandle-mad.c
===================================================================
--- bse/gsldatahandle-mad.c	(revision 4068)
+++ bse/gsldatahandle-mad.c	(working copy)
@@ -673,6 +673,7 @@ static GslDataHandleFuncs dh_mad_vtable 
   dh_mad_read,
   dh_mad_close,
   NULL,
+  NULL,
   dh_mad_destroy,
 };
 
Index: bse/ChangeLog
===================================================================
--- bse/ChangeLog	(revision 4068)
+++ bse/ChangeLog	(working copy)
@@ -1,3 +1,19 @@
+Mon Nov  6 13:12:50 2006  Stefan Westerfeld  <stefan space twc de>
+
+	* gsldatahandle.[hc]: Added new gsl_data_handle_get_state_length
+	function for datahandles, with a corresponding vtable entry. For
+	filtering datahandles (such as lowpass handles), which are usually
+	stateful, it returns the filter state length.
+
+	* gsldatahandle-vorbis.c:
+	* gsldatahandle-mad.c:
+	* bsedatahandle-resample.cc:
+	* tests/loophandle.c: Implement the get_state_length datahandle
+	method.
+
+	* tests/resamplehandle.cc: Test the resampler get_state_length
+	function.
+
 Sun Nov  5 04:23:07 2006  Tim Janik  <timj gtk org>
 
 	* tests/filtertest.cc (random_filter_tests): extended fixed orders to 32 
Index: bse/gsldatahandle.c
===================================================================
--- bse/gsldatahandle.c	(revision 4068)
+++ bse/gsldatahandle.c	(working copy)
@@ -195,6 +195,41 @@ gsl_data_handle_get_source (GslDataHandl
   return src_handle;
 }
 
+/**
+ * @param data_handle	a DataHandle
+ * @return		the state length of the data handle
+ *
+ * Some data handles produce output samples from an input data handle,
+ * like filtering and resampling datahandles. Usually, they have an internal
+ * state which means that the value of one input sample affects not only one
+ * output sample, but some samples before and some samples after the
+ * "corresponding" output sample.
+ *
+ * Often the state is symmetric, so that the number of output samples affected
+ * before and after the "corresponding" output sample is the same. Then the
+ * function returns this number. If the state is asymmetric, this function
+ * shall return the maximum of the two numbers.
+ *
+ * If multiple data handles are cascaded (for instance when resampling a
+ * filtered signal), the function propagates the state length, so that the
+ * state of the chain of all operations together is returned.
+ *
+ * Note: This function can only be used while the data handle is opened.
+ *
+ * This function is MT-safe and may be called from any thread.
+ */
+int64
+gsl_data_handle_get_state_length (GslDataHandle *dhandle)
+{
+  g_return_val_if_fail (dhandle != NULL, -1);
+  g_return_val_if_fail (dhandle->open_count > 0, -1);
+
+  GSL_SPIN_LOCK (&dhandle->mutex);
+  int64 state_length = dhandle->vtable->get_state_length ? dhandle->vtable->get_state_length (dhandle) : 0;
+  GSL_SPIN_UNLOCK (&dhandle->mutex);
+  return state_length;
+}
+
 int64
 gsl_data_handle_length (GslDataHandle *dhandle)
 {
@@ -355,6 +390,7 @@ gsl_data_handle_new_mem (guint         n
     mem_handle_read,
     mem_handle_close,
     NULL,
+    NULL,
     mem_handle_destroy,
   };
   MemHandle *mhandle;
@@ -500,6 +536,14 @@ xinfo_get_source_handle (GslDataHandle *
   return chandle->src_handle;
 }
 
+static int64
+xinfo_get_state_length (GslDataHandle *dhandle)
+{
+  XInfoHandle *chandle = (XInfoHandle*) dhandle;
+  return gsl_data_handle_get_state_length (chandle->src_handle);
+}
+
+
 static GslDataHandle*
 xinfo_data_handle_new (GslDataHandle *src_handle,
                        gboolean       clear_xinfos,
@@ -511,6 +555,7 @@ xinfo_data_handle_new (GslDataHandle *sr
     xinfo_handle_read,
     xinfo_handle_close,
     xinfo_get_source_handle,
+    xinfo_get_state_length,
     xinfo_handle_destroy,
   };
   SfiRing *dest_added = NULL, *dest_remove = NULL;
@@ -686,6 +731,12 @@ chain_handle_close (GslDataHandle *dhand
   gsl_data_handle_close (chandle->src_handle);
 }
 
+static int64
+chain_handle_get_state_length (GslDataHandle *dhandle)
+{
+  ChainHandle *chandle = (ChainHandle*) dhandle;
+  return gsl_data_handle_get_state_length (chandle->src_handle);
+}
 
 /* --- reversed handle --- */
 static void
@@ -745,6 +796,7 @@ gsl_data_handle_new_reverse (GslDataHand
     reverse_handle_read,
     chain_handle_close,
     NULL,
+    chain_handle_get_state_length,
     reverse_handle_destroy,
   };
   ReversedHandle *rhandle;
@@ -853,6 +905,7 @@ gsl_data_handle_new_translate (GslDataHa
     cut_handle_read,
     chain_handle_close,
     NULL,
+    chain_handle_get_state_length,
     cut_handle_destroy,
   };
   CutHandle *chandle;
@@ -1032,6 +1085,14 @@ insert_handle_read (GslDataHandle *dhand
   return orig_n_values - n_values;
 }
 
+static int64
+insert_handle_get_state_length (GslDataHandle *dhandle)
+{
+  InsertHandle *ihandle = (InsertHandle*) dhandle;
+  return gsl_data_handle_get_state_length (ihandle->src_handle);
+}
+
+
 GslDataHandle*
 gsl_data_handle_new_insert (GslDataHandle *src_handle,
 			    guint          paste_bit_depth,
@@ -1045,6 +1106,7 @@ gsl_data_handle_new_insert (GslDataHandl
     insert_handle_read,
     insert_handle_close,
     NULL,
+    insert_handle_get_state_length,
     insert_handle_destroy,
   };
   InsertHandle *ihandle;
@@ -1161,6 +1223,7 @@ gsl_data_handle_new_looped (GslDataHandl
     loop_handle_read,
     chain_handle_close,
     NULL,
+    chain_handle_get_state_length,
     loop_handle_destroy,
   };
   LoopHandle *lhandle;
@@ -1259,6 +1322,13 @@ dcache_handle_get_source_handle (GslData
   return chandle->dcache->dhandle;
 }
 
+static int64
+dcache_handle_get_state_length (GslDataHandle *dhandle)
+{
+  DCacheHandle *chandle = (DCacheHandle*) dhandle;
+  return gsl_data_handle_get_state_length (chandle->dcache->dhandle);
+}
+
 GslDataHandle*
 gsl_data_handle_new_dcached (GslDataCache *dcache)
 {
@@ -1267,6 +1337,7 @@ gsl_data_handle_new_dcached (GslDataCach
     dcache_handle_read,
     dcache_handle_close,
     dcache_handle_get_source_handle,
+    dcache_handle_get_state_length,
     dcache_handle_destroy,
   };
   DCacheHandle *dhandle;
@@ -1495,6 +1566,7 @@ gsl_wave_handle_new (const gchar      *f
     wave_handle_read,
     wave_handle_close,
     NULL,
+    NULL,
     wave_handle_destroy,
   };
   WaveHandle *whandle;
Index: bse/gsldatahandle.h
===================================================================
--- bse/gsldatahandle.h	(revision 4068)
+++ bse/gsldatahandle.h	(working copy)
@@ -63,6 +63,7 @@ struct _GslDataHandleFuncs
 					 gfloat			*values);
   void		 (*close)		(GslDataHandle		*data_handle);
   GslDataHandle* (*get_source)          (GslDataHandle          *data_handle);
+  int64          (*get_state_length)	(GslDataHandle	        *data_handle);
   void           (*destroy)		(GslDataHandle		*data_handle);
 };
 
@@ -85,6 +86,7 @@ int64		  gsl_data_handle_read		(GslDataH
 						 int64		   value_offset,
 						 int64		   n_values,
 						 gfloat		  *values);
+int64		  gsl_data_handle_get_state_length  (GslDataHandle    *dhandle);
 GslDataHandle*    gsl_data_handle_get_source    (GslDataHandle    *dhandle);
 GslDataHandle*	  gsl_data_handle_new_cut	(GslDataHandle	  *src_handle,
 						 int64		   cut_offset,
Index: bse/tests/loophandle.c
===================================================================
--- bse/tests/loophandle.c	(revision 4068)
+++ bse/tests/loophandle.c	(working copy)
@@ -105,6 +105,14 @@ loop_handle_reference_read (GslDataHandl
     }
 }
 
+static int64
+loop_handle_reference_get_state_length (GslDataHandle *dhandle)
+{
+  LoopHandleReference *lhandle = (LoopHandleReference*) dhandle;
+  return gsl_data_handle_get_state_length (lhandle->src_handle);
+}
+
+
 static GslDataHandle*
 gsl_data_handle_new_looped_reference (GslDataHandle *src_handle,
 			              GslLong        loop_first,
@@ -115,6 +123,7 @@ gsl_data_handle_new_looped_reference (Gs
     loop_handle_reference_read,
     loop_handle_reference_close,
     NULL,
+    loop_handle_reference_get_state_length,
     loop_handle_reference_destroy,
   };
   LoopHandleReference *lhandle;
Index: bse/tests/resamplehandle.cc
===================================================================
--- bse/tests/resamplehandle.cc	(revision 4068)
+++ bse/tests/resamplehandle.cc	(working copy)
@@ -371,6 +371,111 @@ test_delay_compensation (const char *run
   TDONE();
 }
 
+static void
+test_state_length (const char *run_type)
+{
+  TSTART ("Resampler State Length Info (%s)", run_type);
+
+  //-----------------------------------------------------------------------------------
+  // usampling
+  //-----------------------------------------------------------------------------------
+  {
+    const guint period_size = 107;
+
+    /* fill input with 2 periods of a sine wave, so that while at the start and
+     * at the end clicks occur (because the unwindowed signal is assumed to 0 by
+     * the resamplehandle), in the middle 1 period can be found that is clickless
+     */
+    vector<float> input (period_size * 2);
+    for (size_t i = 0; i < input.size(); i++)
+      input[i] = sin (i * 2 * M_PI / period_size);
+
+    const guint precision_bits = 16;
+    GslDataHandle *ihandle = gsl_data_handle_new_mem (1, 32, 44100, 440, input.size(), &input[0], NULL);
+    GslDataHandle *rhandle = bse_data_handle_new_upsample2 (ihandle, precision_bits);
+    BseErrorType open_error = gsl_data_handle_open (rhandle);
+    TASSERT (open_error == 0);
+    TASSERT (gsl_data_handle_get_state_length (ihandle) == 0);
+
+    // determine how much of the end of the signal is "unusable" due to the resampler state:
+    const int64 state_length = gsl_data_handle_get_state_length (rhandle);
+
+    /* read resampled signal in the range unaffected by the resampler state (that
+     * is: not at the directly at the beginning, and not directly at the end)
+     */
+    vector<float> output (input.size() * 3);
+    for (size_t values_done = 0; values_done < output.size(); values_done++)
+      {
+	/* NOTE: this is an inlined implementation of a loop, which you normally would
+	 * implement with a loop handle, and it is inefficient because we read the
+	 * samples one-by-one -> usually: don't use such code, always read in blocks */
+	int64 read_pos = (values_done + state_length) % (period_size * 2) + (period_size * 2 - state_length);
+	TCHECK (read_pos >= state_length);   /* check that input signal was long enough to be for this test */
+	int64 values_read = gsl_data_handle_read (rhandle, read_pos, 1, &output[values_done]);
+	TCHECK (values_read == 1);
+      }
+    double error = 0;
+    for (size_t i = 0; i < output.size(); i++)
+      {
+	double expected = sin (i * 2 * M_PI / (period_size * 2));
+	error = MAX (error, fabs (output[i] - expected));
+      }
+    double error_db = bse_db_from_factor (error, -200);
+    TASSERT (error_db < -97);
+  }
+
+  //-----------------------------------------------------------------------------------
+  // downsampling
+  //-----------------------------------------------------------------------------------
+
+  {
+    const guint period_size = 190;
+
+    /* fill input with 2 periods of a sine wave, so that while at the start and
+     * at the end clicks occur (because the unwindowed signal is assumed to 0 by
+     * the resamplehandle), in the middle 1 period can be found that is clickless
+     */
+    vector<float> input (period_size * 2);
+    for (size_t i = 0; i < input.size(); i++)
+      input[i] = sin (i * 2 * M_PI / period_size);
+
+    const guint precision_bits = 16;
+    GslDataHandle *ihandle = gsl_data_handle_new_mem (1, 32, 44100, 440, input.size(), &input[0], NULL);
+    GslDataHandle *rhandle = bse_data_handle_new_downsample2 (ihandle, precision_bits);
+    BseErrorType open_error = gsl_data_handle_open (rhandle);
+    TASSERT (open_error == 0);
+    TASSERT (gsl_data_handle_get_state_length (ihandle) == 0);
+
+    // determine how much of the end of the signal is "unusable" due to the resampler state:
+    const int64 state_length = gsl_data_handle_get_state_length (rhandle);
+
+    /* read resampled signal in the range unaffected by the resampler state (that
+     * is: not at the directly at the beginning, and not directly at the end)
+     */
+    vector<float> output (input.size() * 3 / 2);
+    for (size_t values_done = 0; values_done < output.size(); values_done++)
+      {
+	/* NOTE: this is an inlined implementation of a loop, which you normally would
+	 * implement with a loop handle, and it is inefficient because we read the
+	 * samples one-by-one -> usually: don't use such code, always read in blocks */
+	int64 read_pos = (values_done + state_length) % (period_size / 2) + (period_size / 2 - state_length);
+	TCHECK (read_pos >= state_length);   /* check that input signal was long enough to be for this test */
+	int64 values_read = gsl_data_handle_read (rhandle, read_pos, 1, &output[values_done]);
+	TCHECK (values_read == 1);
+      }
+    double error = 0;
+    for (size_t i = 0; i < output.size(); i++)
+      {
+	double expected = sin (i * 2 * M_PI / (period_size / 2));
+	error = MAX (error, fabs (output[i] - expected));
+      }
+    double error_db = bse_db_from_factor (error, -200);
+    TASSERT (error_db < -105);
+  }
+  TDONE();
+}
+
+
 int
 main (int   argc,
       char *argv[])
@@ -385,6 +490,7 @@ main (int   argc,
   
   test_c_api ("FPU");
   test_delay_compensation ("FPU");
+  test_state_length ("FPU");
   run_tests ("FPU");
 
   /* load plugins */
@@ -399,6 +505,7 @@ main (int   argc,
 
   test_c_api ("SSE");
   test_delay_compensation ("SSE");
+  test_state_length ("SSE");
   run_tests ("SSE");
 
   return 0;
Index: bse/bsedatahandle-resample.cc
===================================================================
--- bse/bsedatahandle-resample.cc	(revision 4068)
+++ bse/bsedatahandle-resample.cc	(working copy)
@@ -136,7 +136,7 @@ protected:
   }
 
   /* implemented by upsampling and downsampling datahandle */
-  virtual BseResampler2Mode mode	() = 0;
+  virtual BseResampler2Mode mode	() const = 0;
   virtual int64		    read_frame  (int64 frame) = 0;
 
 public:
@@ -222,18 +222,38 @@ public:
 
     return n_values;
   }
+  GslDataHandle*
+  get_source() const
+  {
+    return gsl_data_handle_get_source (m_src_handle);
+  }
+  int64
+  get_state_length() const
+  {
+    int64 source_state_length = gsl_data_handle_get_state_length (m_src_handle);
+    if (source_state_length < 0)
+      return source_state_length;
 
+    if (mode() == BSE_RESAMPLER2_MODE_UPSAMPLE)
+      source_state_length *= 2;
+    else
+      source_state_length = (source_state_length + 1) / 2;
+
+    if (m_resamplers.size())
+      {
+	/* For fractional delays, a delay of 10.5 for instance means that input[0]
+	 * affects samples 10 and 11 are affected, and thus the state length we
+	 * assume for that case is 11.
+	 */
+	int64 per_channel_state = ceil (m_resamplers[0]->delay());
+	return source_state_length + per_channel_state * m_dhandle.setup.n_channels;
+      }
+    // should never happen
+    return -1;
+  }
   static GslDataHandle*
   dh_create (DataHandleResample2 *cxx_dh)
   {
-    static GslDataHandleFuncs dh_vtable =
-    {
-      dh_open,
-      dh_read,
-      dh_close,
-      NULL,
-      dh_destroy,
-    };
     if (cxx_dh->m_init_ok)
       {
 	cxx_dh->m_dhandle.vtable = &dh_vtable;
@@ -246,9 +266,10 @@ public:
 	return NULL;
       }
   }
-
 private:
 /* for the "C" API (vtable) */
+  static GslDataHandleFuncs dh_vtable;
+
   static DataHandleResample2*
   dh_cast (GslDataHandle *dhandle)
   {
@@ -278,6 +299,26 @@ private:
   {
     return dh_cast (dhandle)->read (voffset, n_values, values);
   }
+  static GslDataHandle*
+  dh_get_source (GslDataHandle *dhandle)
+  {
+    return dh_cast (dhandle)->get_source();
+  }
+  static int64
+  dh_get_state_length (GslDataHandle *dhandle)
+  {
+    return dh_cast (dhandle)->get_state_length();
+  }
+};
+
+GslDataHandleFuncs DataHandleResample2::dh_vtable =
+{
+  dh_open,
+  dh_read,
+  dh_close,
+  dh_get_source,
+  dh_get_state_length,
+  dh_destroy,
 };
 
 class DataHandleUpsample2 : public DataHandleResample2
@@ -291,7 +332,7 @@ public:
       m_dhandle.name = g_strconcat (m_src_handle->name, "// #upsample2 /", NULL);
   }
   BseResampler2Mode
-  mode()
+  mode() const
   {
     return BSE_RESAMPLER2_MODE_UPSAMPLE;
   }
@@ -366,7 +407,7 @@ public:
   {
   }
   BseResampler2Mode
-  mode()
+  mode() const
   {
     return BSE_RESAMPLER2_MODE_DOWNSAMPLE;
   }
-- 
Stefan Westerfeld, Hamburg/Germany, http://space.twc.de/~stefan



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]