shithub: sox

Download patch

ref: f54982203e17a06ba080a28723382bd04c09d303
parent: be9a51455733edcb87e4753829545d900982d0b9
author: robs <robs>
date: Wed Feb 14 18:25:37 EST 2007

Ongoing compander improvements.

--- a/ChangeLog
+++ b/ChangeLog
@@ -7,8 +7,24 @@
 sox-13.0.1
 ----------
 
+  File formats:
+
   o Added support for ADPCM-encoded PRC files, based on Danny Smith's
     rec2wav and sndcmp.
+
+  Effects:
+
+  o Add soft-knee companding.  (robs)
+
+  Other new features:
+
+  Bug fixes:
+
+  o Fix (m)compand transfer function non-linearities; fix compand
+    drain volume.  (robs)
+
+  Internal improvements:
+
 
 sox-13.0.0
 ----------
--- a/sox.1
+++ b/sox.1
@@ -1245,9 +1245,9 @@
 (\fB\-t\fR).  Gain-out is the volume of the output.
 .TP
 \fBcompand \fIattack1\fB,\fIdecay1\fR{\fB,\fIattack2\fB,\fIdecay2\fR}
-\fIin-dB1\fB,\fIout-dB1\fR{\fB,\fIin-dB2\fB,\fIout-dB2\fR}
+[\fIsoft-knee-dB\fB:\fR]\fIin-dB1\fR[\fB,\fIout-dB1\fR]{\fB,\fIin-dB2\fB,\fIout-dB2\fR}
 .br
-[\fIgain\fR [\fIinitial-volume\fR [\fIdelay\fR]]]
+[\fIgain\fR [\fIinitial-volume-dB\fR [\fIdelay\fR]]]
 .SP
 Compand (compress or expand) the dynamic range of the audio.  The
 attack and decay time specify the integration time over which the
@@ -1280,6 +1280,8 @@
 Specifying a delay approximately equal to the attack/decay times
 allows the compander to effectively operate in a `predictive' rather than a
 reactive mode.
+.SP
+This effect supports the \fB\-\-octave\fR global option (for the transfer function).
 .SP
 See also
 .B mcompand
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
 ## Process this file with automake to produce Makefile.in
 
-AM_CFLAGS = @SNDFILE_CFLAGS@ @SAMPLERATE_CFLAGS@
+AM_CFLAGS = @SNDFILE_CFLAGS@ @SAMPLERATE_CFLAGS@ -Wconversion
 AM_LDFLAGS = @SNDFILE_LIBS@ @SAMPLERATE_LIBS@
 
 # Pass flags from --enable-silent-libtool
@@ -18,7 +18,8 @@
 	  sndfile.c sndrtool.c sphere.c tx16w.c voc.c vorbis.c vox.c wav.c \
 	  wav.h wve.c xa.c
 
-effects = band.h biquad.c biquad.h biquads.c chorus.c compand.c dcshift.c\
+effects = band.h biquad.c biquad.h biquads.c chorus.c compand.c \
+	  compandt.c compandt.h dcshift.c\
 	  deemph.h dither.c earwax.c echo.c echos.c fade.c FFT.c FFT.h filter.c\
 	  flanger.c mcompand.c mixer.c noiseprof.c \
 	  noisered.c noisered.h pad.c pan.c phaser.c pitch.c polyphas.c \
--- a/src/compand.c
+++ b/src/compand.c
@@ -13,8 +13,7 @@
 
 #include <string.h>
 #include <stdlib.h>
-#include <math.h>
-#include "st_i.h"
+#include "compandt.h"
 
 /*
  * Compressor/expander effect for Sound Tools.
@@ -32,21 +31,19 @@
  *          +----->| delay |-------------------------->|   |
  *                 |       |                            ---
  *                  -------
- *
- * Usage:
- *   compand attack1,decay1[,attack2,decay2...]
- *                  in-dB1,out-dB1[,in-dB2,out-dB2...]
- *                 [ gain [ initial-volume [ delay ] ] ]
- *
+ */
+#define compand_usage \
+  "Usage: compand attack1,decay1{,attack2,decay2} [soft-knee-dB:]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."
+/*
  * 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
  * output level of the transfer function.
  */
 
-#include "compand.h"
-
 typedef struct {
-  transfer_fn_t transfer_fn;
+  st_compandt_t transfer_fn;
 
   struct {
     double attack_times[2]; /* 0:attack_time, 1:decay_time */
@@ -99,7 +96,7 @@
     }
   }
 
-  if (!parse_transfer_fn(&l->transfer_fn, argv[1], n>2 ? argv[2] : 0))
+  if (!st_compandt_parse(&l->transfer_fn, argv[1], n>2 ? argv[2] : 0))
     return ST_EOF;
 
   /* Set the initial "volume" to be attibuted to the input channels.
@@ -140,7 +137,7 @@
   for (i = 0; i < l->expectedChannels; ++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))
+  if (!st_compandt_show(&l->transfer_fn, effp->globalinfo->octave_plot_effect))
     return ST_EOF;
 
   /* Convert attack and decay rates using number of samples */
@@ -185,7 +182,6 @@
   int len =  (*isamp > *osamp) ? *osamp : *isamp;
   int filechans = effp->outinfo.channels;
   int idone,odone;
-  double checkbuf;
 
   for (idone = 0,odone = 0; idone < len; ibuf += filechans) {
     int chan;
@@ -207,18 +203,17 @@
     }
 
     /* 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) {
+      int ch = l->expectedChannels > 1 ? chan : 0;
+      double level_in_lin = l->channels[ch].volume;
+      double level_out_lin = st_compandt(&l->transfer_fn, level_in_lin);
+      double checkbuf;
 
       if (l->delay_buf_size <= 0) {
         checkbuf = ibuf[chan] * level_out_lin;
         ST_SAMPLE_CLIP_COUNT(checkbuf, effp->clips);
-        obuf[odone] = checkbuf;
+        obuf[odone++] = checkbuf;
         idone++;
-        odone++;
       } else {
         if (l->delay_buf_cnt >= l->delay_buf_size) {
           l->delay_buf_full=1; /* delay buffer is now definitely full */
@@ -252,7 +247,7 @@
     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);
+      double level_out_lin = st_compandt(&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--;
@@ -273,7 +268,7 @@
 {
   compand_t l = (compand_t) effp->priv;
 
-  kill_transfer_fn(&l->transfer_fn);
+  st_compandt_kill(&l->transfer_fn);
   free(l->channels);
   return ST_SUCCESS;
 }
@@ -281,11 +276,8 @@
 st_effect_t const * st_compand_effect_fn(void)
 {
   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
+    "compand", compand_usage, ST_EFF_MCHAN,
+    getopts, start, flow, drain, stop, kill
   };
   return &driver;
 }
--- a/src/compand.h
+++ /dev/null
@@ -1,236 +1,0 @@
-/*
- * 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);
-}
-
--- /dev/null
+++ b/src/compandt.c
@@ -1,0 +1,204 @@
+/*
+ * 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
+ */
+
+#include "compandt.h"
+#include <string.h>
+
+#define LOG_TO_LOG10(x) ((x) * 20 / log(10.))
+
+st_bool st_compandt_show(st_compandt_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(st_compandt(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(st_compandt_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;
+}
+
+st_bool st_compandt_parse(st_compandt_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;
+  else t->curve_dB = 0;
+  t->curve_dB = max(t->curve_dB, .01);
+
+  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;
+    }
+    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 tail off segment at the start */
+  s(0).y = s(1).y;
+  ++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;
+}
+
+void st_compandt_kill(st_compandt_t * p)
+{
+  free(p->segments);
+}
+
--- /dev/null
+++ b/src/compandt.h
@@ -1,0 +1,54 @@
+/*
+ * 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
+ */
+
+#include "st_i.h"
+#include <math.h>
+
+typedef struct {
+  struct st_compandt_segment {
+    double x, y;              /* 1st point in segment */
+    double a, b;              /* Quadratic coeffecients for rest of segment */
+  } * segments;
+  double in_min_lin;
+  double out_min_lin;
+  double outgain_dB;        /* Post processor gain */
+  double curve_dB;
+} st_compandt_t;
+
+st_bool st_compandt_parse(st_compandt_t * t, char * points, char * gain);
+st_bool st_compandt_show(st_compandt_t * t, st_bool plot);
+void    st_compandt_kill(st_compandt_t * p);
+
+/* Place in header to allow in-lining */
+static double st_compandt(st_compandt_t * t, double in_lin)
+{
+  struct st_compandt_segment * 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);
+}
--- a/src/mcompand.c
+++ b/src/mcompand.c
@@ -16,8 +16,7 @@
 
 #include <string.h>
 #include <stdlib.h>
-#include <math.h>
-#include "st_i.h"
+#include "compandt.h"
 
 /*
  * Usage:
@@ -91,7 +90,7 @@
   double bandwidth;
 } *butterworth_crossover_t;
 
-static int lowpass_setup (butterworth_crossover_t butterworth, double frequency, st_rate_t rate, int nchan) {
+static int lowpass_setup (butterworth_crossover_t butterworth, double frequency, st_rate_t rate, st_size_t nchan) {
   double c;
 
   butterworth->xy_low = (struct xy *)xcalloc(nchan, sizeof(struct xy));
@@ -124,12 +123,12 @@
   return (ST_SUCCESS);
 }
 
-static int lowpass_flow(eff_t effp, butterworth_crossover_t butterworth, int nChan, st_sample_t *ibuf, st_sample_t *lowbuf, st_sample_t *highbuf,
-                         int len) {
-  int chan;
+static int lowpass_flow(eff_t effp, butterworth_crossover_t butterworth, st_size_t nChan, st_sample_t *ibuf, st_sample_t *lowbuf, st_sample_t *highbuf,
+                         st_size_t len) {
+  st_size_t chan;
   double in, out;
 
-  int done;
+  st_size_t done;
 
   st_sample_t *ibufptr, *lowbufptr, *highbufptr;
 
@@ -191,16 +190,13 @@
 }
 
 typedef struct comp_band {
+  st_compandt_t transfer_fn;
+
   st_size_t 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 */
   double topfreq;       /* upper bound crossover frequency */
   struct butterworth_crossover filter;
@@ -211,9 +207,9 @@
 } *comp_band_t;
 
 typedef struct {
-  int nBands;
+  st_size_t nBands;
   st_sample_t *band_buf1, *band_buf2, *band_buf3;
-  int band_buf_len;
+  st_size_t band_buf_len;
   st_size_t delay_buf_size;/* Size of delay_buf in samples */
   struct comp_band *bands;
 } *compand_t;
@@ -224,10 +220,10 @@
  * Don't do initialization now.
  * The 'info' fields are not yet filled in.
  */
-static int st_mcompand_getopts_1(comp_band_t l, int n, char **argv)
+static int st_mcompand_getopts_1(comp_band_t l, st_size_t n, char **argv)
 {
       char *s;
-      st_size_t rates, tfers, i, commas;
+      st_size_t rates, i, commas;
 
       /* Start by checking the attack and decay rates */
 
@@ -258,58 +254,9 @@
         ++i;
       } while (s != NULL);
 
-      /* Same business, but this time for the transfer function */
+      if (!st_compandt_parse(&l->transfer_fn, argv[1], n>2 ? argv[2] : 0))
+        return ST_EOF;
 
-      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\n"
-             "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, \n"
-               "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 \n"
-               "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 */
@@ -324,7 +271,7 @@
     return (ST_SUCCESS);
 }
 
-static int parse_subarg(char *s, char **subargv, int *subargc) {
+static int parse_subarg(char *s, char **subargv, st_size_t *subargc) {
   char **ap;
   char *s_p;
 
@@ -344,13 +291,10 @@
 
   if (*subargc < 2 || *subargc > 5)
     {
-      st_fail("Wrong number of arguments for the compander effect within mcompand\n"
-           "Use: {<attack_time>,<decay_time>}+ {<dB_in>,<db_out>}+ "
-           "[<dB_postamp> [<initial-volume> [<delay_time]]]\n"
-           "where {}+ means `one 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_fail("Wrong number of parameters for the compander effect within mcompand; usage:\n"
+  "\tattack1,decay1{,attack2,decay2} [soft-knee-dB:]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.");
       return (ST_EOF);
     } else
       return ST_SUCCESS;
@@ -359,7 +303,7 @@
 static int st_mcompand_getopts(eff_t effp, int n, char **argv) 
 {
   char *subargv[6], *cp;
-  int subargc, i, len;
+  st_size_t subargc, i, len;
 
   compand_t c = (compand_t) effp->priv;
 
@@ -409,7 +353,7 @@
   compand_t c = (compand_t) effp->priv;
   comp_band_t l;
   st_size_t i;
-  int band;
+  st_size_t band;
   
   for (band=0;band<c->nBands;++band) {
     l = &c->bands[band];
@@ -452,7 +396,7 @@
  * value, the attack rate and decay rate
  */
 
-static void doVolume(double *v, double samp, comp_band_t l, int chan)
+static void doVolume(double *v, double samp, comp_band_t l, st_size_t chan)
 {
   double s = samp/(~((st_sample_t)1<<31));
   double delta = s - *v;
@@ -463,9 +407,9 @@
     *v += delta * l->decayRate[chan];
 }
 
-static int st_mcompand_flow_1(compand_t c, comp_band_t l, const st_sample_t *ibuf, st_sample_t *obuf, int len, int filechans)
+static int st_mcompand_flow_1(eff_t effp, compand_t c, comp_band_t l, const st_sample_t *ibuf, st_sample_t *obuf, st_size_t len, st_size_t filechans)
 {
-  int done, chan;
+  st_size_t done, chan;
 
   for (done = 0; done < len; ibuf += filechans) {
 
@@ -475,7 +419,7 @@
       /* User is expecting same compander for all channels */
       double maxsamp = 0.0;
       for (chan = 0; chan < filechans; ++chan) {
-        double rect = fabs(ibuf[chan]);
+        double rect = fabs((double)ibuf[chan]);
         if (rect > maxsamp)
           maxsamp = rect;
       }
@@ -482,32 +426,22 @@
       doVolume(&l->volume[0], maxsamp, l, 0);
     } else {
       for (chan = 0; chan < filechans; ++chan)
-        doVolume(&l->volume[chan], fabs(ibuf[chan]), l, chan);
+        doVolume(&l->volume[chan], fabs((double)ibuf[chan]), l, chan);
     }
 
     /* Volume memory is updated: perform compand */
-
     for (chan = 0; chan < filechans; ++chan) {
-      double v = l->expectedChannels > 1 ? 
-        l->volume[chan] : l->volume[0];
-      double outv;
-      int piece;
+      int ch = l->expectedChannels > 1 ? chan : 0;
+      double level_in_lin = l->volume[ch];
+      double level_out_lin = st_compandt(&l->transfer_fn, level_in_lin);
+      double checkbuf;
 
-      for (piece = 1 /* yes, 1 */;
-           piece < l->transferPoints;
-           ++piece)
-        if (v >= l->transferIns[piece - 1] &&
-            v < l->transferIns[piece])
-          break;
-      
-      outv = l->transferOuts[piece-1] +
-        (l->transferOuts[piece] - l->transferOuts[piece-1]) *
-        (v - l->transferIns[piece-1]) /
-        (l->transferIns[piece] - l->transferIns[piece-1]);
+      if (c->delay_buf_size <= 0) {
+        checkbuf = ibuf[chan] * level_out_lin;
+        ST_SAMPLE_CLIP_COUNT(checkbuf, effp->clips);
+        obuf[done++] = checkbuf;
 
-      if (c->delay_buf_size <= 0)
-        obuf[done++] = ibuf[chan]*(outv/v)*l->outgain;
-      else {
+      } else {
         /* FIXME: note that this lookahead algorithm is really lame:
            the response to a peak is released before the peak
            arrives. */
@@ -522,7 +456,9 @@
            band's delay and the longest delay of all the bands. */
 
         if (l->delay_buf_cnt >= l->delay_size)
-          l->delay_buf[(l->delay_buf_ptr + c->delay_buf_size - l->delay_size)%c->delay_buf_size] *= (outv/v)*l->outgain;
+          checkbuf = l->delay_buf[(l->delay_buf_ptr + c->delay_buf_size - l->delay_size)%c->delay_buf_size] * level_out_lin;
+          ST_SAMPLE_CLIP_COUNT(checkbuf, effp->clips);
+          l->delay_buf[(l->delay_buf_ptr + c->delay_buf_size - l->delay_size)%c->delay_buf_size] = checkbuf;
         if (l->delay_buf_cnt >= c->delay_buf_size)
           obuf[done++] = l->delay_buf[l->delay_buf_ptr];
         else
@@ -544,8 +480,8 @@
                      st_size_t *isamp, st_size_t *osamp) {
   compand_t c = (compand_t) effp->priv;
   comp_band_t l;
-  int len = min(*isamp, *osamp);
-  int band, i;
+  st_size_t len = min(*isamp, *osamp);
+  st_size_t band, i;
   st_sample_t *abuf, *bbuf, *cbuf, *oldabuf, *ibuf_copy;
   double out;
 
@@ -573,7 +509,7 @@
     }
     if (abuf == ibuf_copy)
       abuf = c->band_buf3;
-    (void)st_mcompand_flow_1(c,l,bbuf,abuf,len,effp->outinfo.channels);
+    (void)st_mcompand_flow_1(effp, c,l,bbuf,abuf,len,effp->outinfo.channels);
     for (i=0;i<len;++i)
     {
       out = obuf[i] + abuf[i];
@@ -592,9 +528,9 @@
   return ST_SUCCESS;
 }
 
-static int st_mcompand_drain_1(eff_t effp, compand_t c, comp_band_t l, st_sample_t *obuf, int maxdrain)
+static int st_mcompand_drain_1(eff_t effp, compand_t c, comp_band_t l, st_sample_t *obuf, st_size_t maxdrain)
 {
-  int done;
+  st_size_t done;
   double out;
 
   /*
@@ -618,7 +554,7 @@
  */
 static int st_mcompand_drain(eff_t effp, st_sample_t *obuf, st_size_t *osamp)
 {
-  int band, drained, mostdrained = 0;
+  st_size_t band, drained, mostdrained = 0;
   compand_t c = (compand_t)effp->priv;
   comp_band_t l;
 
@@ -645,7 +581,7 @@
 {
   compand_t c = (compand_t) effp->priv;
   comp_band_t l;
-  int band;
+  st_size_t band;
 
   free(c->band_buf1);
   c->band_buf1 = NULL;
@@ -670,12 +606,11 @@
 {
   compand_t c = (compand_t) effp->priv;
   comp_band_t l;
-  int band;
+  st_size_t band;
 
   for (band = 0; band < c->nBands; band++) {
     l = &c->bands[band];
-    free(l->transferOuts);
-    free(l->transferIns);
+    st_compandt_kill(&l->transfer_fn);
     free(l->decayRate);
     free(l->attackRate);
     free(l->volume);