flightgear/3rdparty/iaxclient/lib/sox/compand.c

398 lines
11 KiB
C
Raw Normal View History

2022-10-20 20:29:11 +08:00
/*
* Compander effect
*
* Written by Nick Bailey (nick@bailey-family.org.uk or
* n.bailey@elec.gla.ac.uk)
*
* 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
* the consequences of using this software.
*/
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "sox.h"
/*
* Compressor/expander effect for dsp.
*
* Flow diagram for one channel:
*
* ------------ ---------------
* | | | | ---
* ibuff ---+---| integrator |--->| transfer func |--->| |
* | | | | | | |
* | ------------ --------------- | | * gain
* | | * |----------->obuff
* | ------- | |
* | | | | |
* +----->| delay |-------------------------->| |
* | | ---
* -------
*
* Usage:
* compand attack1,decay1[,attack2,decay2...]
* in-dB1,out-dB1[,in-dB2,out-dB2...]
* [ 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
* output level of the transfer function.
*/
/*
* Process options
*
* Don't do initialization now.
* The 'info' fields are not yet filled in.
*/
int st_compand_getopts(compand_t l, int n, char **argv)
{
if (n < 2 || n > 5)
{
st_fail("Wrong number of arguments for the compander effect\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.");
return (ST_EOF);
}
else { /* Right no. of args, but are they well formed? */
char *s;
int rates, tfers, i, commas;
/* Start by checking the attack and decay rates */
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);
}
rates = 1 + commas/2;
if ((l->attackRate = malloc(sizeof(double) * rates)) == NULL ||
(l->decayRate = malloc(sizeof(double) * rates)) == NULL ||
(l->volume = malloc(sizeof(double) * rates)) == NULL)
{
st_fail("Out of memory");
return (ST_EOF);
}
l->expectedChannels = rates;
l->delay_buf = NULL;
/* 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. */
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);
/* 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\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 */
if ((l->transferIns = malloc(sizeof(double) * tfers)) == NULL ||
(l->transferOuts = malloc(sizeof(double) * tfers)) == NULL)
{
st_fail("Out of memory");
return (ST_EOF);
}
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);
}
/*
* Prepare processing.
* Do all initializations.
*/
int st_compand_start(compand_t *lH, char **opts, int nopts)
{
int i;
compand_t l;
*lH = malloc(sizeof (struct compand));
l = *lH;
st_compand_getopts(l, nopts, opts);
# ifdef DEBUG
{
fprintf(stderr, "Starting compand effect\n");
fprintf(stderr, "\nRate %ld, size %d, encoding %d, output gain %g.\n",
ST_SAMPLE_RATE, effp->outinfo.size, effp->outinfo.encoding,
l->outgain);
fprintf(stderr, "%d input channel(s) expected: actually %d\n",
l->expectedChannels, ST_CHANNELS);
fprintf(stderr, "\nAttack and decay rates\n"
"======================\n");
for (i = 0; i < l->expectedChannels; ++i)
fprintf(stderr, "Channel %d: attack = %-12g decay = %-12g\n",
i, l->attackRate[i], l->decayRate[i]);
fprintf(stderr, "\nTransfer function (linear values)\n"
"================= =============\n");
for (i = 0; i < l->transferPoints; ++i)
fprintf(stderr, "%12g -> %-12g\n",
l->transferIns[i], l->transferOuts[i]);
}
# endif
/* Convert attack and decay rates using number of samples */
for (i = 0; i < l->expectedChannels; ++i) {
if (l->attackRate[i] > 1.0/ST_SAMPLE_RATE)
l->attackRate[i] = 1.0 -
exp(-1.0/(ST_SAMPLE_RATE * l->attackRate[i]));
else
l->attackRate[i] = 1.0;
if (l->decayRate[i] > 1.0/ST_SAMPLE_RATE)
l->decayRate[i] = 1.0 -
exp(-1.0/(ST_SAMPLE_RATE * l->decayRate[i]));
else
l->decayRate[i] = 1.0;
}
/* Allocate the delay buffer */
l->delay_buf_size = (int) (l->delay * ST_SAMPLE_RATE * ST_CHANNELS);
if (l->delay_buf_size > 0
&& (l->delay_buf = malloc(sizeof(long) * l->delay_buf_size)) == NULL) {
st_fail("Out of memory");
return (ST_EOF);
}
for (i = 0; i < l->delay_buf_size; i++)
l->delay_buf[i] = 0;
l->delay_buf_ptr = 0;
l->delay_buf_cnt = 0;
l->delay_buf_full= 0;
return (ST_SUCCESS);
}
/*
* 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_MAX;
double delta = s - *v;
if (delta > 0.0) /* increase volume according to attack rate */
*v += delta * l->attackRate[chan];
else /* reduce volume according to decay rate */
*v += delta * l->decayRate[chan];
}
/*
* Processed signed long samples from ibuf to obuf.
* Return number of samples processed.
*/
int st_compand_flow(compand_t l, st_sample_t *ibuf, st_sample_t *obuf,
st_size_t *isamp, st_size_t *osamp)
{
int len = (*isamp > *osamp) ? *osamp : *isamp;
int filechans = ST_CHANNELS;
int idone,odone;
long checkbuf; //if st_sample_t of type int32_t
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 */
int i;
double maxsamp = 0.0;
for (i = 0; i < filechans; ++i) {
double rect = fabs(ibuf[i]);
if (rect > maxsamp) maxsamp = rect;
}
doVolume(&l->volume[0], maxsamp, l, 0);
break;
} else
doVolume(&l->volume[chan], fabs(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;
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 (l->delay_buf_size <= 0)
{
checkbuf = (long int) (ibuf[chan]*(outv/v)*l->outgain);
if(checkbuf > ST_SAMPLE_MAX)
obuf[odone] = ST_SAMPLE_MAX;
else if(checkbuf < ST_SAMPLE_MIN)
obuf[odone] = ST_SAMPLE_MIN;
else
obuf[odone] = (st_sample_t) 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 = (long int) (l->delay_buf[l->delay_buf_ptr]*(outv/v)*l->outgain);
if(checkbuf > ST_SAMPLE_MAX)
obuf[odone] = ST_SAMPLE_MAX;
else if(checkbuf < ST_SAMPLE_MIN)
obuf[odone] = ST_SAMPLE_MIN;
else
obuf[odone] = (st_sample_t) checkbuf;
odone++;
idone++;
}
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;
}
}
}
*isamp = idone; *osamp = odone;
return (ST_SUCCESS);
}
/*
* Drain out compander delay lines.
*/
int st_compand_drain(compand_t l, st_sample_t *obuf, st_size_t *osamp)
{
int done;
/*
* 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 < (int) *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 */
*osamp = done;
return (ST_SUCCESS);
}
/*
* Clean up compander effect.
*/
int st_compand_stop(compand_t l)
{
free((char *) l->delay_buf);
free((char *) l->transferOuts);
free((char *) l->transferIns);
free((char *) l->volume);
free((char *) l->decayRate);
free((char *) l->attackRate);
l->delay_buf = NULL;
l->transferOuts = NULL;
l->transferIns = NULL;
l->volume = NULL;
l->decayRate = NULL;
l->attackRate = NULL;
return (ST_SUCCESS);
}