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