blob: b96031bc34119b29e55c8f9f199e21fd331a955b [file] [log] [blame]
/* Copyright (c) 20/03/2000 Fabien COELHO <fabien@coelho.net>
* Copyright (c) 2000-2007 SoX contributors
*
* SoX vol effect; change volume with basic linear amplitude formula.
* Beware of saturations! Clipping is checked and reported.
*
* FIXME: deprecate or remove the limiter in favour of compand.
*/
#define vol_usage \
"GAIN [TYPE [LIMITERGAIN]]\n" \
"\t(default TYPE=amplitude: 1 is constant, < 0 change phase;\n" \
"\tTYPE=power 1 is constant; TYPE=dB: 0 is constant, +6 doubles ampl.)\n" \
"\tThe peak limiter has a gain much less than 1 (e.g. 0.05 or 0.02) and\n" \
"\tis only used on peaks (to prevent clipping); default is no limiter."
#include "third_party/sox/src/src/sox_i.h"
typedef struct {
double gain; /* amplitude gain. */
sox_bool uselimiter;
double limiterthreshhold;
double limitergain;
uint64_t limited; /* number of limited values to report. */
uint64_t totalprocessed;
} priv_t;
enum {vol_amplitude, vol_dB, vol_power};
static lsx_enum_item const vol_types[] = {
LSX_ENUM_ITEM(vol_,amplitude)
LSX_ENUM_ITEM(vol_,dB)
LSX_ENUM_ITEM(vol_,power)
{0, 0}};
/*
* Process options: gain (float) type (amplitude, power, dB)
*/
static int getopts(sox_effect_t * effp, int argc, char **argv)
{
priv_t * vol = (priv_t *) effp->priv;
char type_string[11];
char * type_ptr = type_string;
char dummy; /* To check for extraneous chars. */
sox_bool have_type;
--argc, ++argv;
vol->gain = 1; /* Default is no change. */
vol->uselimiter = sox_false; /* Default is no limiter. */
/* Get the vol, and the type if it's in the same arg. */
if (!argc || (have_type = sscanf(argv[0], "%lf %10s %c", &vol->gain, type_string, &dummy) - 1) > 1)
return lsx_usage(effp);
++argv, --argc;
/* No type yet? Get it from the next arg: */
if (!have_type && argc) {
have_type = sox_true;
type_ptr = *argv;
++argv, --argc;
}
if (have_type) {
lsx_enum_item const * p = lsx_find_enum_text(type_ptr, vol_types, 0);
if (!p)
return lsx_usage(effp);
switch (p->value) {
case vol_dB: vol->gain = dB_to_linear(vol->gain); break;
case vol_power: /* power to amplitude, keep phase change */
vol->gain = vol->gain > 0 ? sqrt(vol->gain) : -sqrt(-vol->gain);
break;
}
}
if (argc) {
if (fabs(vol->gain) < 1 || sscanf(*argv, "%lf %c", &vol->limitergain, &dummy) != 1 || vol->limitergain <= 0 || vol->limitergain >= 1)
return lsx_usage(effp);
vol->uselimiter = sox_true;
/* The following equation is derived so that there is no
* discontinuity in output amplitudes */
/* and a SOX_SAMPLE_MAX input always maps to a SOX_SAMPLE_MAX output
* when the limiter is activated. */
/* (NOTE: There **WILL** be a discontinuity in the slope
* of the output amplitudes when using the limiter.) */
vol->limiterthreshhold = SOX_SAMPLE_MAX * (1.0 - vol->limitergain) / (fabs(vol->gain) - vol->limitergain);
}
lsx_debug("mult=%g limit=%g", vol->gain, vol->limitergain);
return SOX_SUCCESS;
}
/*
* Start processing
*/
static int start(sox_effect_t * effp)
{
priv_t * vol = (priv_t *) effp->priv;
if (vol->gain == 1)
return SOX_EFF_NULL;
vol->limited = 0;
vol->totalprocessed = 0;
return SOX_SUCCESS;
}
/*
* Process data.
*/
static int flow(sox_effect_t * effp, const sox_sample_t *ibuf, sox_sample_t *obuf,
size_t *isamp, size_t *osamp)
{
priv_t * vol = (priv_t *) effp->priv;
register double gain = vol->gain;
register double limiterthreshhold = vol->limiterthreshhold;
register double sample;
register size_t len;
len = min(*osamp, *isamp);
/* report back dealt with amount. */
*isamp = len; *osamp = len;
if (vol->uselimiter)
{
vol->totalprocessed += len;
for (;len>0; len--)
{
sample = *ibuf++;
if (sample > limiterthreshhold)
{
sample = (SOX_SAMPLE_MAX - vol->limitergain * (SOX_SAMPLE_MAX - sample));
vol->limited++;
}
else if (sample < -limiterthreshhold)
{
sample = -(SOX_SAMPLE_MAX - vol->limitergain * (SOX_SAMPLE_MAX + sample));
/* FIXME: MIN is (-MAX)-1 so need to make sure we
* don't go over that. Probably could do this
* check inside the above equation but I didn't
* think it thru.
*/
if (sample < SOX_SAMPLE_MIN)
sample = SOX_SAMPLE_MIN;
vol->limited++;
} else
sample = gain * sample;
SOX_SAMPLE_CLIP_COUNT(sample, effp->clips);
*obuf++ = sample;
}
}
else
{
/* quite basic, with clipping */
for (;len>0; len--)
{
sample = gain * *ibuf++;
SOX_SAMPLE_CLIP_COUNT(sample, effp->clips);
*obuf++ = sample;
}
}
return SOX_SUCCESS;
}
static int stop(sox_effect_t * effp)
{
priv_t * vol = (priv_t *) effp->priv;
if (vol->limited) {
lsx_warn("limited %" PRIu64 " values (%d percent).",
vol->limited, (int) (vol->limited * 100.0 / vol->totalprocessed));
}
return SOX_SUCCESS;
}
sox_effect_handler_t const * lsx_vol_effect_fn(void)
{
static sox_effect_handler_t handler = {
"vol", vol_usage, SOX_EFF_MCHAN | SOX_EFF_GAIN, getopts, start, flow, 0, stop, 0, sizeof(priv_t)
};
return &handler;
}