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);
+}
+