blob: a0fd3947cceba78be2f22cc43da20003288f0434 [file] [log] [blame]
/* libSoX Compander Transfer Function: (c) 2007 robs@users.sourceforge.net
*
* 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.1 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,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "third_party/sox/src/src/sox_i.h"
#include "third_party/sox/src/src/compandt.h"
#include <string.h>
#define LOG_TO_LOG10(x) ((x) * 20 / M_LN10)
sox_bool lsx_compandt_show(sox_compandt_t * t, sox_plot_t plot)
{
int i;
for (i = 1; t->segments[i-1].x; ++i)
lsx_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 == sox_plot_octave) {
printf(
"%% GNU Octave file (may also work with MATLAB(R) )\n"
"in=linspace(-99.5,0,200);\n"
"out=[");
for (i = -199; i <= 0; ++i) {
double in = i/2.;
double in_lin = pow(10., in/20);
printf("%g ", in + 20 * log10(lsx_compandt(t, in_lin)));
}
printf(
"];\n"
"plot(in,out)\n"
"title('SoX effect: compand')\n"
"xlabel('Input level (dB)')\n"
"ylabel('Output level (dB)')\n"
"grid on\n"
"disp('Hit return to continue')\n"
"pause\n");
return sox_false;
}
if (plot == sox_plot_gnuplot) {
printf(
"# gnuplot file\n"
"set title 'SoX effect: compand'\n"
"set xlabel 'Input level (dB)'\n"
"set ylabel 'Output level (dB)'\n"
"set grid xtics ytics\n"
"set key off\n"
"plot '-' with lines\n");
for (i = -199; i <= 0; ++i) {
double in = i/2.;
double in_lin = pow(10., in/20);
printf("%g %g\n", in, in + 20 * log10(lsx_compandt(t, in_lin)));
}
printf(
"e\n"
"pause -1 'Hit return to continue'\n");
return sox_false;
}
return sox_true;
}
static void prepare_transfer_fn(sox_compandt_t * t)
{
int i;
double radius = t->curve_dB * M_LN10 / 20;
for (i = 0; !i || t->segments[i-2].x; i += 2) {
t->segments[i].y += t->outgain_dB;
t->segments[i].x *= M_LN10 / 20; /* Convert to natural logs */
t->segments[i].y *= M_LN10 / 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 sox_bool parse_transfer_value(char const * text, double * value)
{
char dummy; /* To check for extraneous chars. */
if (!text) {
lsx_fail("syntax error trying to read transfer function value");
return sox_false;
}
if (!strcmp(text, "-inf"))
*value = -20 * log10(-(double)SOX_SAMPLE_MIN);
else if (sscanf(text, "%lf %c", value, &dummy) != 1) {
lsx_fail("syntax error trying to read transfer function value");
return sox_false;
}
else if (*value > 0) {
lsx_fail("transfer function values are relative to maximum volume so can't exceed 0dB");
return sox_false;
}
return sox_true;
}
sox_bool lsx_compandt_parse(sox_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 = lsx_calloc(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 sox_false;
if (i && s(i-1).x > s(i).x) {
lsx_fail("transfer function input values must be strictly increasing");
return sox_false;
}
if (i || (commas & 1)) {
text = strtok(NULL, ",");
if (!parse_transfer_value(text, &s(i).y))
return sox_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) {
lsx_fail("syntax error trying to read post-processing gain value");
return sox_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 sox_true;
}
void lsx_compandt_kill(sox_compandt_t * p)
{
free(p->segments);
}