diff --git a/ChangeLog b/ChangeLog
index 3057672..ed1002f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+Wed Oct 31 21:39:13 2007  Stefan Westerfeld  <stefan@space.twc.de>
+
+	* docs/architecture.doxi: Documented the new "fine-tune" xinfo.
+
 Wed Oct 31 20:07:30 2007  Stefan Westerfeld  <stefan@space.twc.de>
 
 	Fix bsewavetool info output for multichannel samples #491552.
diff --git a/bse/ChangeLog b/bse/ChangeLog
index 4dc2dc1..4e0aa9d 100644
--- a/bse/ChangeLog
+++ b/bse/ChangeLog
@@ -1,3 +1,23 @@
+Mon Nov  5 18:54:39 2007  Stefan Westerfeld  <stefan@space.twc.de>
+
+	Added necessary code for "fine-tune" xinfo, bug #450490.
+
+	* bsemathsignal.[hc]: Implemented bse_cent_tune(), which is similar to
+	bse_cent_tune_fast(), but accepts arbitary doubles as input because it
+	doesn't use a table internally.
+
+	* tests/misctests.cc: Added test for bse_cent_tune().
+
+	* gsldatahandle.[hc]: Implemented gsl_data_handle_fine_tune, which
+	returns a fine tuning measured in cent ("fine-tune" xinfo).
+
+	* gslwavechunk.[hc]: Cache the fine tune factor here, so that
+	GslWaveOsc can access it quickly.
+
+	* gslwaveosc.c (gsl_wave_osc_set_filter): Take fine tune factor of
+	the chunk into account for step computation (and thus for filter
+	computation).
+
 Mon Nov  5 17:28:04 2007  Stefan Westerfeld  <stefan@space.twc.de>
 
 	Merge BSE_FINE_TUNE_FACTOR() and bse_cent_factor(), bug #450490.
diff --git a/bse/bsemathsignal.c b/bse/bsemathsignal.c
index 7b376fb..48cf87d 100644
--- a/bse/bsemathsignal.c
+++ b/bse/bsemathsignal.c
@@ -213,6 +213,22 @@ static const double cent_table201[100 + 1 + 100] = {
 };
 const double * const bse_cent_table = cent_table201 + 100;
 
+/**
+ * @param fine_tune	fine tuning in cent
+ * @return		a factor corresponding to this
+ *
+ * This function computes a factor which corresponds to a given fine tuning in
+ * cent.  The result can be used as factor for the frequency or the play speed.
+ * It is similar to the bse_cent_tune_fast(). It doesn't use a table internally,
+ * so the input is not limited in any way (i.e. a double like 777.777 is a valid
+ * fine_tune argument). However, it is computationally more expensive.
+ */
+double
+bse_cent_tune (double fine_tune)
+{
+  return exp (fine_tune * BSE_LN_2_POW_1_DIV_1200_d);
+}
+
 /* --- musical tuning systems --- */
 #define SCALED_INTERVAL(scale, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12)       \
   scale * (F1), scale * (F2), scale * (F3), scale * (F4), scale * (F5), scale * (F6),   \
diff --git a/bse/bsemathsignal.h b/bse/bsemathsignal.h
index f2ffbd6..7a1958b 100644
--- a/bse/bsemathsignal.h
+++ b/bse/bsemathsignal.h
@@ -400,6 +400,8 @@ double        bse_transpose_factor           (BseMusicalTuningType musical_tunin
 
 /* --- cents (1/100th of a semitone) --- */
 
+double                  bse_cent_tune (double fine_tune);
+
 /**
  * @param fine_tune	fine tuning in cent between -100 and 100
  * @return		a factor corresponding to this
diff --git a/bse/gsldatahandle.c b/bse/gsldatahandle.c
index 1df1563..bb942d4 100644
--- a/bse/gsldatahandle.c
+++ b/bse/gsldatahandle.c
@@ -305,6 +305,19 @@ gsl_data_handle_volume (GslDataHandle *dhandle)
   return f;
 }
 
+gfloat
+gsl_data_handle_fine_tune (GslDataHandle *dhandle)
+{
+  g_return_val_if_fail (dhandle != NULL, 0);
+  g_return_val_if_fail (dhandle->open_count > 0, 0);
+
+  GSL_SPIN_LOCK (&dhandle->mutex);
+  gfloat f = bse_xinfos_get_float (dhandle->setup.xinfos, "fine-tune");
+  GSL_SPIN_UNLOCK (&dhandle->mutex);
+
+  return f;
+}
+
 const gchar*
 gsl_data_handle_name (GslDataHandle *dhandle)
 {
diff --git a/bse/gsldatahandle.h b/bse/gsldatahandle.h
index 7af0ea5..5b6f615 100644
--- a/bse/gsldatahandle.h
+++ b/bse/gsldatahandle.h
@@ -80,6 +80,7 @@ guint		  gsl_data_handle_bit_depth	    (GslDataHandle	  *data_handle);
 gfloat		  gsl_data_handle_mix_freq	    (GslDataHandle	  *data_handle);
 gfloat		  gsl_data_handle_osc_freq	    (GslDataHandle	  *data_handle);
 gfloat            gsl_data_handle_volume            (GslDataHandle        *data_handle);
+gfloat            gsl_data_handle_fine_tune         (GslDataHandle        *data_handle);
 const gchar*	  gsl_data_handle_name		    (GslDataHandle	  *data_handle);
 int64		  gsl_data_handle_read		    (GslDataHandle	  *data_handle,
 						     int64		   value_offset,
diff --git a/bse/gslwavechunk.c b/bse/gslwavechunk.c
index 33f3e5a..346199f 100644
--- a/bse/gslwavechunk.c
+++ b/bse/gslwavechunk.c
@@ -18,6 +18,7 @@
 #include "bsemain.h"
 #include "gslcommon.h"
 #include "gsldatahandle.h"
+#include "bsemathsignal.h"
 #include <string.h>
 
 
@@ -638,7 +639,8 @@ gsl_wave_chunk_new (GslDataCache   *dcache,
   wchunk->open_count = 0;
   wchunk->mix_freq = mix_freq;
   wchunk->osc_freq = osc_freq;
-  wchunk->volume_adjust = 0.0;  /* will be set in gsl_wave_chunk_open */
+  wchunk->volume_adjust = 0.0;	    /* will be set in gsl_wave_chunk_open */
+  wchunk->fine_tune_factor = 0.0;   /* will be set in gsl_wave_chunk_open */
   wchunk->requested_loop_type = loop_type;
   wchunk->requested_loop_first = loop_first;
   wchunk->requested_loop_last = loop_last;
@@ -697,6 +699,7 @@ gsl_wave_chunk_open (GslWaveChunk *wchunk)
       wchunk->length *= wchunk->n_channels;
       wchunk->n_pad_values = BSE_CONFIG (wave_chunk_padding) * wchunk->n_channels;
       wchunk->volume_adjust = gsl_data_handle_volume (wchunk->dcache->dhandle);
+      wchunk->fine_tune_factor = bse_cent_tune (gsl_data_handle_fine_tune (wchunk->dcache->dhandle));
       gsl_data_cache_open (wchunk->dcache);
       gsl_data_handle_close (wchunk->dcache->dhandle);
       g_return_val_if_fail (wchunk->dcache->padding >= wchunk->n_pad_values, BSE_ERROR_INTERNAL);
@@ -751,6 +754,7 @@ gsl_wave_chunk_close (GslWaveChunk *wchunk)
   wchunk->leave_end_norm = 0;
   wchunk->tail_start_norm = 0;
   wchunk->volume_adjust = 0.0;
+  wchunk->fine_tune_factor = 0.0;
   gsl_wave_chunk_unref (wchunk);
 }
 
diff --git a/bse/gslwavechunk.h b/bse/gslwavechunk.h
index 51aa42d..fda41c9 100644
--- a/bse/gslwavechunk.h
+++ b/bse/gslwavechunk.h
@@ -73,9 +73,10 @@ struct _GslWaveChunk
   guint           ref_count;
   guint           open_count;
   /* cached data handle setup */
-  gfloat         mix_freq;      /* recorded with mix_freq */
-  gfloat         osc_freq;      /* while oscillating at osc_freq */
-  gdouble        volume_adjust;	/* cache of dhandle->xinfo["volume"], only valid when opened */
+  gfloat         mix_freq;	    /* recorded with mix_freq */
+  gfloat         osc_freq;	    /* while oscillating at osc_freq */
+  gdouble        volume_adjust;	    /* cache of dhandle->xinfo["volume"], only valid when opened */
+  gdouble        fine_tune_factor;  /* cached factor from dhandle->xinfo["fine-tune"], valid when opened */
 };
 struct _GslWaveChunkBlock
 {
diff --git a/bse/gslwaveosc.c b/bse/gslwaveosc.c
index efa388a..ffa907d 100644
--- a/bse/gslwaveosc.c
+++ b/bse/gslwaveosc.c
@@ -250,7 +250,7 @@ gsl_wave_osc_set_filter (GslWaveOscData *wosc,
   if (UNLIKELY (!wosc->config.lookup_wchunk))
     return;
 
-  wosc->step_factor = zero_padding * wosc->wchunk->mix_freq;
+  wosc->step_factor = zero_padding * wosc->wchunk->mix_freq * wosc->wchunk->fine_tune_factor;
   wosc->step_factor /= wosc->wchunk->osc_freq * wosc->mix_freq;
   step = wosc->step_factor * play_freq;
   istep = step * (FRAC_MASK + 1.) + 0.5;
diff --git a/bse/tests/misctests.cc b/bse/tests/misctests.cc
index 33b8500..54335ac 100644
--- a/bse/tests/misctests.cc
+++ b/bse/tests/misctests.cc
@@ -38,6 +38,22 @@ check_cent_tune_fast (void)
 }
 
 static void
+check_cent_tune (void)
+{
+  TSTART ("Cent Tune Function");
+  const double epsilon = 1e-14;
+  int i = 0;
+  for (double fine_tune = -3600; fine_tune < 3600; fine_tune += g_random_double())  /* 3 octaves */
+    {
+      double expected = pow (2.0, 1. / 1200. * fine_tune);
+      TCHECK_CMP (fabs (bse_cent_tune (fine_tune) - expected), <, epsilon);
+      if (i++ % 500 == 0)
+        TOK();
+    }
+  TDONE();
+}
+
+static void
 check_equal_tempered_tuning (void)
 {
   TSTART ("Equal Temperament");
@@ -111,6 +127,7 @@ main (gint   argc,
 {
   bse_init_test (&argc, &argv, NULL);
 
+  check_cent_tune();
   check_cent_tune_fast();
   check_equal_tempered_tuning();
 
diff --git a/docs/architecture.doxi b/docs/architecture.doxi
index cab24ee..a4dd4db 100644
--- a/docs/architecture.doxi
+++ b/docs/architecture.doxi
@@ -114,6 +114,13 @@ of the lowercase letters 'a'-'z', the digits '0'-'9' and a dash '-'.
 	0.5 (play sample half as loud, corresponding to a -6dB adjustment) and
 	0.25 (corresponds to a -12dB adjustment). The range of valid volume
 	adjustments is: 0 < volume <= 1.0, any other value will be ignored.
+@item @strong{fine-tune}
+	The fine tune allows to change the speed and thus the pitch at which
+	the sample is replayed in cent. This does not affect the raw/compressed
+	audio data actually stored. The default is 0, which means no fine tune.
+	Examples of valid values are 100, which would pitch the sample up one
+	semitone (100 cents), 1200, which would pitch up the sample one octave
+	and -50 which would pitch the sample half a semitone down.
 @item @strong{gus-patch-envelope} -
 	Contains the envelope points for a GUS Patch envelope.
 @done
