shithub: sox

Download patch

ref: 6dd4e6deb76cafe5c7919706c99b698e20166265
parent: dd448e56da071aebba7572eb6ea81788038133e8
author: robs <robs>
date: Wed Feb 14 03:24:15 EST 2007

New compander transfer function.

--- a/src/compand.c
+++ b/src/compand.c
@@ -6,8 +6,8 @@
  *
  * Copyright 1999 Chris Bagwell And Nick Bailey
  * This source code is freely redistributable and may be used for
- * any purpose.  This copyright notice must be maintained. 
- * Chris Bagwell And Nick Bailey are not responsible for 
+ * any purpose.  This copyright notice must be maintained.
+ * Chris Bagwell And Nick Bailey are not responsible for
  * the consequences of using this software.
  */
 
@@ -17,7 +17,7 @@
 #include "st_i.h"
 
 /*
- * Compressor/expander effect for dsp.
+ * Compressor/expander effect for Sound Tools.
  *
  * Flow diagram for one channel:
  *
@@ -36,7 +36,7 @@
  * Usage:
  *   compand attack1,decay1[,attack2,decay2...]
  *                  in-dB1,out-dB1[,in-dB2,out-dB2...]
- *                 [ gain [ initial-volume [ delay ] ] ] 
+ *                 [ gain [ initial-volume [ delay ] ] ]
  *
  * Note: clipping can occur if the transfer function pushes things too
  * close to 0 dB.  In that case, use a negative gain, or reduce the
@@ -43,190 +43,124 @@
  * output level of the transfer function.
  */
 
-static st_effect_t st_compand_effect;
+#include "compand.h"
 
 typedef struct {
-  int expectedChannels; /* Also flags that channels aren't to be treated
-                           individually when = 1 and input not mono */
-  int transferPoints;   /* Number of points specified on the transfer
-                           function */
-  double *attackRate;   /* An array of attack rates */
-  double *decayRate;    /*    ... and of decay rates */
-  double *transferIns;  /*    ... and points on the transfer function */
-  double *transferOuts;
-  double *volume;       /* Current "volume" of each channel */
-  double outgain;       /* Post processor gain */
-  double delay;         /* Delay to apply before companding */
+  transfer_fn_t transfer_fn;
+
+  struct {
+    double attack_times[2]; /* 0:attack_time, 1:decay_time */
+    double volume;          /* Current "volume" of each channel */
+  } * channels;
+  unsigned expectedChannels;/* Also flags that channels aren't to be treated
+                               individually when = 1 and input not mono */
+  double delay;             /* Delay to apply before companding */
   st_sample_t *delay_buf;   /* Old samples, used for delay processing */
   st_ssize_t delay_buf_size;/* Size of delay_buf in samples */
-  st_ssize_t delay_buf_ptr; /* Index into delay_buf */
+  st_ssize_t delay_buf_index; /* Index into delay_buf */
   st_ssize_t delay_buf_cnt; /* No. of active entries in delay_buf */
-  short int delay_buf_full; /* Shows buffer situation (important for st_compand_drain) */
-} *compand_t;
+  int delay_buf_full;       /* Shows buffer situation (important for drain) */
+} * compand_t;
 
-/*
- * Process options
- *
- * Don't do initialization now.
- * The 'info' fields are not yet filled in.
- */
-static int st_compand_getopts(eff_t effp, int n, char **argv) 
+static int getopts(eff_t effp, int n, char * * argv)
 {
-    compand_t l = (compand_t) effp->priv;
+  compand_t l = (compand_t) effp->priv;
+  char * s;
+  char dummy;     /* To check for extraneous chars. */
+  unsigned pairs, i, j, commas;
 
-    if (n < 2 || n > 5)
-    {
-      st_fail (st_compand_effect.usage);
-      return (ST_EOF);
-    }
-    else { /* Right no. of args, but are they well formed? */
-      char *s;
-      int rates, tfers, i, commas;
+  if (n < 2 || n > 5) {
+    st_fail(effp->h->usage);
+    return ST_EOF;
+  }
 
-      /* Start by checking the attack and decay rates */
+  /* Start by checking the attack and decay rates */
+  for (s = argv[0], commas = 0; *s; ++s) if (*s == ',') ++commas;
+  if ((commas % 2) == 0) {
+    st_fail("there must be an even number of attack/decay parameters");
+    return ST_EOF;
+  }
+  pairs = 1 + commas/2;
+  l->channels = xcalloc(pairs, sizeof(*l->channels));
+  l->expectedChannels = pairs;
 
-      for (s = argv[0], commas = 0; *s; ++s)
-        if (*s == ',') ++commas;
-
-      if (commas % 2 == 0) /* There must be an even number of
-                              attack/decay parameters */
-      {
-        st_fail("compander: Odd number of attack & decay rate parameters");
-        return (ST_EOF);
+  /* Now tokenise the rates string and set up these arrays.  Keep
+     them in seconds at the moment: we don't know the sample rate yet. */
+  for (i = 0, s = strtok(argv[0], ","); s != NULL; ++i) {
+    for (j = 0; j < 2; ++j) {
+      if (sscanf(s, "%lf %c", &l->channels[i].attack_times[j], &dummy) != 1) {
+        st_fail("syntax error trying to read attack/decay time");
+        return ST_EOF;
+      } else if (l->channels[i].attack_times[j] < 0) {
+        st_fail("attack & decay times can't be less than 0 seconds");
+        return ST_EOF;
       }
+      s = strtok(NULL, ",");
+    }
+  }
 
-      rates = 1 + commas/2;
-      l->attackRate = (double *)xmalloc(sizeof(double) * rates);
-      l->decayRate = (double *)xmalloc(sizeof(double) * rates);
-      l->volume = (double *)xmalloc(sizeof(double) * rates);
-      l->expectedChannels = rates;
-      l->delay_buf = NULL;
+  if (!parse_transfer_fn(&l->transfer_fn, argv[1], n>2 ? argv[2] : 0))
+    return ST_EOF;
 
-      /* Now tokenise the rates string and set up these arrays.  Keep
-         them in seconds at the moment: we don't know the sample rate yet. */
+  /* Set the initial "volume" to be attibuted to the input channels.
+     Unless specified, choose 0dB otherwise clipping will
+     result if the user has seleced a long attack time */
+  for (i = 0; i < l->expectedChannels; ++i) {
+    double init_vol_dB = 0;
+    if (n > 3 && sscanf(argv[3], "%lf %c", &init_vol_dB, &dummy) != 1) {
+      st_fail("syntax error trying to read initial volume");
+      return ST_EOF;
+    } else if (init_vol_dB > 0) {
+      st_fail("initial volume is relative to maximum volume so can't exceed 0dB");
+      return ST_EOF;
+    }
+    l->channels[i].volume = pow(10., init_vol_dB / 20);
+  }
 
-      s = strtok(argv[0], ","); i = 0;
-      do {
-        l->attackRate[i] = atof(s); s = strtok(NULL, ",");
-        l->decayRate[i]  = atof(s); s = strtok(NULL, ",");
-        ++i;
-      } while (s != NULL);
+  /* If there is a delay, store it. */
+  if (n > 4 && sscanf(argv[4], "%lf %c", &l->delay, &dummy) != 1) {
+    st_fail("syntax error trying to read delay value");
+    return ST_EOF;
+  } else if (l->delay < 0) {
+    st_fail("delay can't be less than 0 seconds");
+    return ST_EOF;
+  }
 
-      /* Same business, but this time for the transfer function */
-
-      for (s = argv[1], commas = 0; *s; ++s)
-        if (*s == ',') ++commas;
-
-      if (commas % 2 == 0) /* There must be an even number of
-                              transfer parameters */
-      {
-        st_fail("compander: Odd number of transfer function parameters"
-             "Each input value in dB must have a corresponding output value");
-        return (ST_EOF);
-      }
-
-      tfers = 3 + commas/2; /* 0, 0 at start; 1, 1 at end */
-      l->transferIns = (double *)xmalloc(sizeof(double) * tfers);
-      l->transferOuts = (double *)xmalloc(sizeof(double) * tfers);
-      l->transferPoints = tfers;
-      l->transferIns[0] = 0.0; l->transferOuts[0] = 0.0;
-      l->transferIns[tfers-1] = 1.0; l->transferOuts[tfers-1] = 1.0;
-      s = strtok(argv[1], ","); i = 1;
-      do {
-        if (!strcmp(s, "-inf"))
-        {
-          st_fail("Input signals of zero level must always generate zero output");
-          return (ST_EOF);
-        }
-        l->transferIns[i]  = pow(10.0, atof(s)/20.0);
-        if (l->transferIns[i] > 1.0)
-        {
-          st_fail("dB values are relative to maximum input, and, ipso facto, "
-               "cannot exceed 0");
-          return (ST_EOF);
-        }
-        if (l->transferIns[i] == 1.0) /* Final point was explicit */
-          --(l->transferPoints);
-        if (i > 0 && l->transferIns[i] <= l->transferIns[i-1])
-        {
-          st_fail("Transfer function points don't have strictly ascending "
-               "input amplitude");
-          return (ST_EOF);
-        }
-        s = strtok(NULL, ",");
-        l->transferOuts[i] = strcmp(s, "-inf") ?
-                               pow(10.0, atof(s)/20.0) : 0;
-        s = strtok(NULL, ",");
-        ++i;
-      } while (s != NULL);
-      
-      /* If there is a postprocessor gain, store it */
-      if (n >= 3) l->outgain = pow(10.0, atof(argv[2])/20.0);
-      else l->outgain = 1.0;
-
-      /* Set the initial "volume" to be attibuted to the input channels.
-         Unless specified, choose 1.0 (maximum) otherwise clipping will
-         result if the user has seleced a long attack time */
-      for (i = 0; i < l->expectedChannels; ++i) {
-        double v = n>=4 ? pow(10.0, atof(argv[3])/20) : 1.0;
-        l->volume[i] = v;
-
-      /* If there is a delay, store it. */
-      if (n >= 5) l->delay = atof(argv[4]);
-      else l->delay = 0.0;
-      }
-    }
-    return (ST_SUCCESS);
+  return ST_SUCCESS;
 }
 
-/*
- * Prepare processing.
- * Do all initializations.
- */
-static int st_compand_start(eff_t effp)
+static int start(eff_t effp)
 {
   compand_t l = (compand_t) effp->priv;
-  int i;
+  unsigned i, j;
 
-  st_debug("Starting compand effect");
-  st_debug("Rate %ld, size %d, encoding %d, output gain %g.",
-         effp->outinfo.rate, effp->outinfo.size, effp->outinfo.encoding,
-         l->outgain);
-  st_debug("%d input channel(s) expected: actually %d",
-         l->expectedChannels, effp->outinfo.channels);
+  st_debug("Starting compand effect; rate %i", effp->outinfo.rate);
+  st_debug("%i input channel(s) expected: actually %i",
+      l->expectedChannels, effp->outinfo.channels);
   for (i = 0; i < l->expectedChannels; ++i)
-    st_debug("Channel %d: attack = %-12g decay = %-12g",
-           i, l->attackRate[i], l->decayRate[i]);
-  for (i = 0; i < l->transferPoints; ++i)
-    st_debug("Transfer fn (linear): %12g -> %-12g",
-           l->transferIns[i], l->transferOuts[i]);
-  
+    st_debug("Channel %i: attack = %g decay = %g", i,
+        l->channels[i].attack_times[0], l->channels[i].attack_times[1]);
+  if (!show_transfer_fn(&l->transfer_fn, effp->globalinfo->octave_plot_effect))
+    return ST_EOF;
+
   /* Convert attack and decay rates using number of samples */
+  for (i = 0; i < l->expectedChannels; ++i)
+    for (j = 0; j < 2; ++j)
+      if (l->channels[i].attack_times[j] > 1.0/effp->outinfo.rate)
+        l->channels[i].attack_times[j] = 1.0 -
+          exp(-1.0/(effp->outinfo.rate * l->channels[i].attack_times[j]));
+      else
+        l->channels[i].attack_times[j] = 1.0;
 
-  for (i = 0; i < l->expectedChannels; ++i) {
-    if (l->attackRate[i] > 1.0/effp->outinfo.rate)
-      l->attackRate[i] = 1.0 -
-        exp(-1.0/(effp->outinfo.rate * l->attackRate[i]));
-    else
-      l->attackRate[i] = 1.0;
-    if (l->decayRate[i] > 1.0/effp->outinfo.rate)
-      l->decayRate[i] = 1.0 -
-        exp(-1.0/(effp->outinfo.rate * l->decayRate[i]));
-    else
-      l->decayRate[i] = 1.0;
-  }
-
   /* Allocate the delay buffer */
   l->delay_buf_size = l->delay * effp->outinfo.rate * effp->outinfo.channels;
   if (l->delay_buf_size > 0)
-    l->delay_buf = (st_sample_t *)xmalloc(sizeof(long) * l->delay_buf_size);
-  for (i = 0;  i < l->delay_buf_size;  i++)
-    l->delay_buf[i] = 0;
-  l->delay_buf_ptr = 0;
+    l->delay_buf = xcalloc((st_size_t)l->delay_buf_size, sizeof(*l->delay_buf));
+  l->delay_buf_index = 0;
   l->delay_buf_cnt = 0;
   l->delay_buf_full= 0;
 
-  return (ST_SUCCESS);
+  return ST_SUCCESS;
 }
 
 /*
@@ -233,7 +167,6 @@
  * Update a volume value using the given sample
  * value, the attack rate and decay rate
  */
-
 static void doVolume(double *v, double samp, compand_t l, int chan)
 {
   double s = -samp / ST_SAMPLE_MIN;
@@ -240,16 +173,12 @@
   double delta = s - *v;
 
   if (delta > 0.0) /* increase volume according to attack rate */
-    *v += delta * l->attackRate[chan];
+    *v += delta * l->channels[chan].attack_times[0];
   else             /* reduce volume according to decay rate */
-    *v += delta * l->decayRate[chan];
+    *v += delta * l->channels[chan].attack_times[1];
 }
 
-/*
- * Processed signed long samples from ibuf to obuf.
- * Return number of samples processed.
- */
-static int st_compand_flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf, 
+static int flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf,
                     st_size_t *isamp, st_size_t *osamp)
 {
   compand_t l = (compand_t) effp->priv;
@@ -256,13 +185,12 @@
   int len =  (*isamp > *osamp) ? *osamp : *isamp;
   int filechans = effp->outinfo.channels;
   int idone,odone;
-  int64_t checkbuf; /* if st_sample_t of type int32_t */
+  double checkbuf;
 
   for (idone = 0,odone = 0; idone < len; ibuf += filechans) {
     int chan;
 
     /* Maintain the volume fields by simulating a leaky pump circuit */
-
     for (chan = 0; chan < filechans; ++chan) {
       if (l->expectedChannels == 1 && filechans > 1) {
         /* User is expecting same compander for all channels */
@@ -269,62 +197,42 @@
         int i;
         double maxsamp = 0.0;
         for (i = 0; i < filechans; ++i) {
-          double rect = fabs(ibuf[i]);
+          double rect = fabs((double)ibuf[i]);
           if (rect > maxsamp) maxsamp = rect;
         }
-        doVolume(&l->volume[0], maxsamp, l, 0);
+        doVolume(&l->channels[0].volume, maxsamp, l, 0);
         break;
       } else
-        doVolume(&l->volume[chan], fabs(ibuf[chan]), l, chan);
+        doVolume(&l->channels[chan].volume, fabs((double)ibuf[chan]), l, chan);
     }
 
     /* Volume memory is updated: perform compand */
+    for (chan = 0; chan < filechans; ++chan)
+    {
+      int c = l->expectedChannels > 1 ? chan : 0;
+      double level_in_lin = l->channels[c].volume;
+      double level_out_lin = transfer_fn(&l->transfer_fn, level_in_lin);
 
-    for (chan = 0; chan < filechans; ++chan) {
-      double v = l->volume[l->expectedChannels > 1 ? chan : 0];
-      double outv;
-
-      if (v == 0)
-        outv = 1;
-      else {
-        int piece;
-
-        for (piece = 1; v > l->transferIns[piece]; ++piece);
-        outv = (l->transferOuts[piece-1] +
-          (l->transferOuts[piece] - l->transferOuts[piece-1]) *
-          (v - l->transferIns[piece-1]) /
-          (l->transferIns[piece] - l->transferIns[piece-1])) / v;
-      }
-      outv *= l->outgain;
-
-      if (l->delay_buf_size <= 0)
-      {
-        checkbuf = ibuf[chan] * outv;
+      if (l->delay_buf_size <= 0) {
+        checkbuf = ibuf[chan] * level_out_lin;
         ST_SAMPLE_CLIP_COUNT(checkbuf, effp->clips);
         obuf[odone] = checkbuf;
-
         idone++;
         odone++;
-      }
-      else
-      {
-        if (l->delay_buf_cnt >= l->delay_buf_size)
-        {
-            l->delay_buf_full=1; /* delay buffer is now definetly full */
-            checkbuf = l->delay_buf[l->delay_buf_ptr] * outv;
-            ST_SAMPLE_CLIP_COUNT(checkbuf, effp->clips);
-            obuf[odone] = checkbuf;
-
-            odone++;
-            idone++;
+      } else {
+        if (l->delay_buf_cnt >= l->delay_buf_size) {
+          l->delay_buf_full=1; /* delay buffer is now definitely full */
+          checkbuf = l->delay_buf[l->delay_buf_index] * level_out_lin;
+          ST_SAMPLE_CLIP_COUNT(checkbuf, effp->clips);
+          obuf[odone] = checkbuf;
+          odone++;
+          idone++;
+        } else {
+          l->delay_buf_cnt++;
+          idone++; /* no "odone++" because we did not fill obuf[...] */
         }
-        else
-        {
-            l->delay_buf_cnt++;
-            idone++; /* no "odone++" because we did not fill obuf[...] */
-        }
-        l->delay_buf[l->delay_buf_ptr++] = ibuf[chan];
-        l->delay_buf_ptr %= l->delay_buf_size;
+        l->delay_buf[l->delay_buf_index++] = ibuf[chan];
+        l->delay_buf_index %= l->delay_buf_size;
       }
     }
   }
@@ -333,83 +241,51 @@
   return (ST_SUCCESS);
 }
 
-/*
- * Drain out compander delay lines.
- */
-static int st_compand_drain(eff_t effp, st_sample_t *obuf, st_size_t *osamp)
+static int drain(eff_t effp, st_sample_t *obuf, st_size_t *osamp)
 {
   compand_t l = (compand_t) effp->priv;
-  st_size_t done;
+  st_size_t chan, done = 0;
 
-  /*
-   * Drain out delay samples.  Note that this loop does all channels.
-   */
-  if(l->delay_buf_full==0) l->delay_buf_ptr=0;
-  for (done = 0;  done < *osamp  &&  l->delay_buf_cnt > 0;  done++) {
-    obuf[done] = l->delay_buf[l->delay_buf_ptr++];
-    l->delay_buf_ptr %= l->delay_buf_size;
-    l->delay_buf_cnt--;
-  }
-
-  /* tell caller number of samples played */
+  if (l->delay_buf_full == 0)
+    l->delay_buf_index = 0;
+  while (done < *osamp && l->delay_buf_cnt > 0)
+    for (chan = 0; chan < effp->outinfo.channels; ++chan) {
+      int c = l->expectedChannels > 1 ? chan : 0;
+      double level_in_lin = l->channels[c].volume;
+      double level_out_lin = transfer_fn(&l->transfer_fn, level_in_lin);
+      obuf[done++] = l->delay_buf[l->delay_buf_index++] * level_out_lin;
+      l->delay_buf_index %= l->delay_buf_size;
+      l->delay_buf_cnt--;
+    }
   *osamp = done;
-
-  if (l->delay_buf_cnt > 0)
-      return ST_SUCCESS;
-  else
-      return ST_EOF;
+  return l->delay_buf_cnt > 0 ? ST_SUCCESS : ST_EOF;
 }
 
-
-/*
- * Clean up compander effect.
- */
-static int st_compand_stop(eff_t effp)
+static int stop(eff_t effp)
 {
   compand_t l = (compand_t) effp->priv;
 
-  free((char *) l->delay_buf);
-
-  l->delay_buf = NULL;
-
-  return (ST_SUCCESS);
+  free(l->delay_buf);
+  return ST_SUCCESS;
 }
 
-static int delete(eff_t effp)
+static int kill(eff_t effp)
 {
   compand_t l = (compand_t) effp->priv;
 
-  free((char *) l->transferOuts);
-  free((char *) l->transferIns);
-  free((char *) l->volume);
-  free((char *) l->decayRate);
-  free((char *) l->attackRate);
-
-  l->transferOuts = NULL;
-  l->transferIns = NULL;
-  l->volume = NULL;
-  l->decayRate = NULL;
-  l->attackRate = NULL;
-
-  return (ST_SUCCESS);
+  kill_transfer_fn(&l->transfer_fn);
+  free(l->channels);
+  return ST_SUCCESS;
 }
 
-static st_effect_t st_compand_effect = {
-   "compand",
-   "Usage: {<attack_time>,<decay_time>}+ {<dB_in>,<db_out>}+ [<dB_postamp> [<initial-volume> [<delay_time]]]\n"
-   "       where {}+ means e or more in a comma-separated, white-space-free list'\n"
-   "       and [] indications possible omission.  dB values are floating\n"
-   "       point or -inf'; times are in seconds.",
-   ST_EFF_MCHAN,
-   st_compand_getopts,
-   st_compand_start,
-   st_compand_flow,
-   st_compand_drain,
-   st_compand_stop,
-  delete
-};
-
-const st_effect_t *st_compand_effect_fn(void)
+st_effect_t const * st_compand_effect_fn(void)
 {
-    return &st_compand_effect;
+  static st_effect_t driver = {
+    "compand",
+    "Usage: compand attack1,decay1{,attack2,decay2} in-dB1,out-dB1{,in-dB2,out-dB2} [gain [initial-volume-dB [delay]]]\n"
+    "\twhere {} means optional and repeatable and [] means optional.\n"
+    "\tdB values are floating point or -inf'; times are in seconds.",
+    ST_EFF_MCHAN, getopts, start, flow, drain, stop, kill
+  };
+  return &driver;
 }
--- /dev/null
+++ b/src/compand.h
@@ -1,0 +1,236 @@
+/*
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, write to the Free Software Foundation,
+ * Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301, USA.
+ *
+ * Compander Transfer Function: (c) 2007 robs@users.sourceforge.net
+ */
+
+typedef struct {
+  double x, y;              /* 1st point in segment */
+  double a, b;              /* Quadratic coeffecients for rest of segment */
+} * segment_t;
+
+typedef struct {
+  segment_t segments;
+  double in_min_lin;
+  double out_min_lin;
+  double outgain_dB;        /* Post processor gain */
+  double curve_dB;
+} transfer_fn_t;
+
+static double transfer_fn(transfer_fn_t * t, double in_lin)
+{
+  segment_t s;
+  double in_log, out_log;
+
+  if (in_lin <= t->in_min_lin)
+    return t->out_min_lin;
+
+  in_log = log(in_lin);
+
+  for (s = t->segments + 1; in_log > s[1].x; ++s);
+
+  in_log -= s->x;
+  out_log = s->y + in_log * (s->a * in_log + s->b);
+
+  return exp(out_log);
+}
+
+#define LOG_TO_LOG10(x) ((x) * 20 / log(10.))
+
+static int show_transfer_fn(transfer_fn_t * t, st_bool plot)
+{
+  int i;
+
+  for (i = 1; t->segments[i-1].x; ++i)
+    st_debug("TF: %g %g %g %g",
+       LOG_TO_LOG10( t->segments[i].x),
+       LOG_TO_LOG10( t->segments[i].y),
+       LOG_TO_LOG10( t->segments[i].a),
+       LOG_TO_LOG10( t->segments[i].b));
+
+  if (!plot)
+    return st_true;
+  printf(
+    "title('SoX effect: compand')\n"
+    "xlabel('Input level (dB)')\n"
+    "ylabel('Output level (dB)')\n"
+    "%%axis([-100 0 -100 0])\n"
+    "in=linspace(-99.5,0,200);\n"
+    "grid on\n"
+    "out=[");
+  for (i = -199; i <= 0; ++i) {
+    double in = i/2.;
+    double in_lin = pow(10., in/20);
+    printf("%g ", in + 20 * log10(transfer_fn(t, in_lin)));
+  }
+  printf(
+    "];\n"
+    "%%plot(in,out,'b') %% hmm.. doesn't work :(\n"
+    "semilogx(exp(in),out,'b')\n"
+    "pause\n");
+  return st_false;
+}
+
+static void prepare_transfer_fn(transfer_fn_t * t)
+{
+  int i;
+  double radius = t->curve_dB * log(10.) / 20;
+
+  for (i = 0; !i || t->segments[i-2].x; i += 2) {
+    t->segments[i].y += t->outgain_dB;
+    t->segments[i].x *= log(10.) / 20; /* Convert to natural logs */
+    t->segments[i].y *= log(10.) / 20;
+  }
+
+#define line1 t->segments[i - 4]
+#define curve t->segments[i - 3]
+#define line2 t->segments[i - 2]
+#define line3 t->segments[i - 0]
+  for (i = 4; t->segments[i - 2].x; i += 2) {
+    double x, y, cx, cy, in1, in2, out1, out2, theta, len, r;
+
+    line1.a = 0;
+    line1.b = (line2.y - line1.y) / (line2.x - line1.x);
+
+    line2.a = 0;
+    line2.b = (line3.y - line2.y) / (line3.x - line2.x);
+
+    theta = atan2(line2.y - line1.y, line2.x - line1.x);
+    len = sqrt(pow(line2.x - line1.x, 2.) + pow(line2.y - line1.y, 2.));
+    r = min(radius, len);
+    curve.x = line2.x - r * cos(theta);
+    curve.y = line2.y - r * sin(theta);
+
+    theta = atan2(line3.y - line2.y, line3.x - line2.x);
+    len = sqrt(pow(line3.x - line2.x, 2.) + pow(line3.y - line2.y, 2.));
+    r = min(radius, len / 2);
+    x = line2.x + r * cos(theta);
+    y = line2.y + r * sin(theta);
+
+    cx = (curve.x + line2.x + x) / 3;
+    cy = (curve.y + line2.y + y) / 3;
+
+    line2.x = x;
+    line2.y = y;
+
+    in1 = cx - curve.x;
+    out1 = cy - curve.y;
+    in2 = line2.x - curve.x;
+    out2 = line2.y - curve.y;
+    curve.a = (out2/in2 - out1/in1) / (in2-in1);
+    curve.b = out1/in1 - curve.a*in1;
+  }
+#undef line1
+#undef curve
+#undef line2
+#undef line3
+  t->segments[i - 3].x = 0;
+  t->segments[i - 3].y = t->segments[i - 2].y;
+
+  t->in_min_lin = exp(t->segments[1].x);
+  t->out_min_lin= exp(t->segments[1].y);
+}
+
+static st_bool parse_transfer_value(char const * text, double * value)
+{
+  char dummy;     /* To check for extraneous chars. */
+
+  if (!strcmp(text, "-inf"))
+    *value = -20 * log10(-(double)ST_SAMPLE_MIN);
+  else if (sscanf(text, "%lf %c", value, &dummy) != 1) {
+    st_fail("syntax error trying to read transfer function value");
+    return st_false;
+  }
+  else if (*value > 0) {
+    st_fail("transfer function values are relative to maximum volume so can't exceed 0dB");
+    return st_false;
+  }
+  return st_true;
+}
+
+static st_bool parse_transfer_fn(transfer_fn_t * t, char * points, char * gain)
+{
+  char const * text = points;
+  unsigned i, j, num, pairs, commas = 0;
+  char dummy;     /* To check for extraneous chars. */
+
+  if (sscanf(points, "%lf %c", &t->curve_dB, &dummy) == 2 && dummy == ':') {
+    points = strchr(points, ':') + 1;
+    t->curve_dB = max(t->curve_dB, .01);
+  } else
+    t->curve_dB = 5;
+
+  while (*text) commas += *text++ == ',';
+  pairs = 1 + commas / 2;
+  ++pairs;    /* allow room for extra pair at the beginning */
+  pairs *= 2; /* allow room for the auto-curves */
+  ++pairs;    /* allow room for 0,0 at end */
+  t->segments = xcalloc(pairs, sizeof(*t->segments));
+
+#define s(n) t->segments[2*((n)+1)]
+  for (i = 0, text = strtok(points, ","); text != NULL; ++i) {
+    if (!parse_transfer_value(text, &s(i).x))
+      return st_false;
+    if (i && s(i-1).x > s(i).x) {
+      st_fail("transfer function input values must be strictly increasing");
+      return st_false;
+    }
+    if (i || (commas & 1)) {
+      text = strtok(NULL, ",");
+      if (!parse_transfer_value(text, &s(i).y))
+        return st_false;
+      s(i).y -= s(i).x;
+      if (!i && fabs(s(i).y)) { /* fabs stops epsilon problems */
+        st_fail("first point in transfer function must have 0dB gain");
+        return st_false;
+      }
+    }
+    text = strtok(NULL, ",");
+  }
+  num = i;
+
+  if (num == 0 || s(num-1).x) /* Add 0,0 if necessary */
+    ++num;
+#undef s
+
+  if (gain && sscanf(gain, "%lf %c", &t->outgain_dB, &dummy) != 1) {
+    st_fail("syntax error trying to read post-processing gain value");
+    return st_false;
+  }
+
+#define s(n) t->segments[2*(n)]
+  s(0).x = s(1).x - 2 * t->curve_dB; /* Add a unity gain segment at the start */
+  ++num;
+
+  for (i = 2; i < num; ++i) { /* Join adjacent colinear segments */
+    double g1 = (s(i-1).y - s(i-2).y) * (s(i-0).x - s(i-1).x);
+    double g2 = (s(i-0).y - s(i-1).y) * (s(i-1).x - s(i-2).x);
+    if (fabs(g1 - g2)) /* fabs stops epsilon problems */
+      continue;
+    --num;
+    for (j = --i; j < num; ++j)
+      s(j) = s(j+1);
+  }
+#undef s
+
+  prepare_transfer_fn(t);
+  return st_true;
+}
+
+static void kill_transfer_fn(transfer_fn_t * p)
+{
+  free(p->segments);
+}
+