/* libSoX dcshift.c
 *
 * Copyright (c) 2000 Chris Ausbrooks <weed@bucket.pp.ualr.edu>
 *
 * 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
 *
 * based on vol.c which is
 * Copyright (c) 20/03/2000 Fabien COELHO <fabien@coelho.net> for sox.
 * This source code is freely redistributable and may be used for any purpose.
 */

/*
 * DC shift a sound file, with basic linear amplitude formula.
 * Beware of saturations! clipping is checked and reported.
 * Cannot handle different number of channels.
 * Cannot handle rate change.
 */
#include "sox_i.h"

typedef struct {
    double dcshift; /* DC shift. */
    int uselimiter; /* boolean: are we using the limiter? */
    double limiterthreshhold;
    double limitergain; /* limiter gain. */
    uint64_t limited; /* number of limited values to report. */
    uint64_t totalprocessed;
} priv_t;

/*
 * Process options: dcshift (double) type (amplitude, power, dB)
 */
static int sox_dcshift_getopts(sox_effect_t * effp, int argc, char **argv)
{
    priv_t * dcs = (priv_t *) effp->priv;
    char dummy;

    dcs->dcshift = 1.0; /* default is no change */
    dcs->uselimiter = 0; /* default is no limiter */

    --argc, ++argv;
    if (argc < 1 || argc > 2)
        return lsx_usage(effp);

    if (argc > 0) {
        if (sscanf(argv[0], "%lf %c", &dcs->dcshift, &dummy) != 1) {
            lsx_fail("cannot parse the DC shift `%s'", argv[0]);
	    return SOX_EOF;
        }
        if (dcs->dcshift < -2.0 || dcs->dcshift > 2.0) {
            lsx_fail("shift must be from -2 to +2");
	    return SOX_EOF;
	}
    }

    if (argc > 1)
    {
        if (sscanf(argv[1], "%lf %c", &dcs->limitergain, &dummy) != 1) {
          lsx_fail("cannot parse the limiter gain `%s'", argv[1]);
	  return SOX_EOF;
        }

        dcs->uselimiter = 1; /* ok, we'll use it */
        /* 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.) */
        dcs->limiterthreshhold = SOX_SAMPLE_MAX * (1.0 - (fabs(dcs->dcshift) - dcs->limitergain));
    }

    return SOX_SUCCESS;
}

/*
 * Start processing
 */
static int sox_dcshift_start(sox_effect_t * effp)
{
    priv_t * dcs = (priv_t *) effp->priv;

    if (dcs->dcshift == 0)
      return SOX_EFF_NULL;

    dcs->limited = 0;
    dcs->totalprocessed = 0;

    return SOX_SUCCESS;
}

/*
 * Process data.
 */
static int sox_dcshift_flow(sox_effect_t * effp, const sox_sample_t *ibuf, sox_sample_t *obuf,
                    size_t *isamp, size_t *osamp)
{
    priv_t * dcs = (priv_t *) effp->priv;
    double dcshift = dcs->dcshift;
    double limitergain = dcs->limitergain;
    double limiterthreshhold = dcs->limiterthreshhold;
    double sample;
    size_t len;

    len = min(*osamp, *isamp);

    /* report back dealt with amount. */
    *isamp = len; *osamp = len;

    if (dcs->uselimiter)
    {
        dcs->totalprocessed += len;

        for (;len>0; len--)
            {
                sample = *ibuf++;

                if (sample > limiterthreshhold && dcshift > 0)
                {
                        sample =  (sample - limiterthreshhold) * limitergain / (SOX_SAMPLE_MAX - limiterthreshhold) + limiterthreshhold + dcshift;
                        dcs->limited++;
                }
                else if (sample < -limiterthreshhold && dcshift < 0)
                {
                        /* Note this should really be SOX_SAMPLE_MIN but
                         * the clip() below will take care of the overflow.
                         */
                        sample =  (sample + limiterthreshhold) * limitergain / (SOX_SAMPLE_MAX - limiterthreshhold) - limiterthreshhold + dcshift;
                        dcs->limited++;
                }
                else
                {
                        /* Note this should consider SOX_SAMPLE_MIN but
                         * the clip() below will take care of the overflow.
                         */
                        sample = dcshift * SOX_SAMPLE_MAX + sample;
                }

                SOX_SAMPLE_CLIP_COUNT(sample, effp->clips);
                *obuf++ = sample;
            }
    }
    else for (; len > 0; --len) {            /* quite basic, with clipping */
      double d = dcshift * (SOX_SAMPLE_MAX + 1.) + *ibuf++;
      *obuf++ = SOX_ROUND_CLIP_COUNT(d, effp->clips);
    }
    return SOX_SUCCESS;
}

/*
 * Do anything required when you stop reading samples.
 * Don't close input file!
 */
static int sox_dcshift_stop(sox_effect_t * effp)
{
    priv_t * dcs = (priv_t *) effp->priv;

    if (dcs->limited)
    {
        lsx_warn("DCSHIFT limited %" PRIu64 " values (%d percent).",
             dcs->limited, (int) (dcs->limited * 100.0 / dcs->totalprocessed));
    }
    return SOX_SUCCESS;
}

static const char usage[] = "shift [limiter-gain(none)]";

static char const * const extra_usage[] = {
   "shift is in the range -2 to +2;",
   "limitergain should be much less than 1.0 (ie 0.05 or 0.02)",
   "and is only used on peaks to prevent clipping.",
   NULL
};

static sox_effect_handler_t sox_dcshift_effect = {
   "dcshift",
   usage, extra_usage,
   SOX_EFF_MCHAN | SOX_EFF_GAIN,
   sox_dcshift_getopts,
   sox_dcshift_start,
   sox_dcshift_flow,
   NULL,
   sox_dcshift_stop,
  NULL, sizeof(priv_t)
};

const sox_effect_handler_t *lsx_dcshift_effect_fn(void)
{
    return &sox_dcshift_effect;
}
