shithub: sox

ref: dd448e56da071aebba7572eb6ea81788038133e8
dir: /src/vol.c/

View raw version
/*
 * (c) 20/03/2000 Fabien COELHO <fabien@coelho.net> for sox.
 *
 * Change volume of 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 "st_i.h"

#include <math.h>   /* exp(), sqrt() */

static st_effect_t st_vol_effect;

#define LOG_10_20 ((double)(0.1151292546497022842009e0))

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

/*
 * Process options: gain (float) type (amplitude, power, dB)
 */
static int st_vol_getopts(eff_t effp, int n, char **argv) 
{
    vol_t vol = (vol_t) effp->priv; 
    vol->gain = 1.0; /* default is no change */
    vol->uselimiter = 0; /* default is no limiter */
    
    if (n && (!sscanf(argv[0], "%lf", &vol->gain)))
    {
        st_fail(st_vol_effect.usage);
        return ST_EOF;
    }

    /* adjust gain depending on type (what a great parser;-) */
    if (n>1) 
    {
        switch (argv[1][0]) 
        {
        case 'd': /* decibels to amplitude */
        case 'D':
            vol->gain = exp(vol->gain*LOG_10_20);
            break;
        case 'p':
        case 'P': /* power to amplitude, keep phase change */
            if (vol->gain > 0.0)
                vol->gain = sqrt(vol->gain);
            else
                vol->gain = -sqrt(-vol->gain);
            break;
        case 'a': /* amplitude */
        case 'A':
        default:
            break;
        }
    }
    

    if (n>2)
    {
        if ((fabs(vol->gain) < 1.0) || !sscanf(argv[2], "%lf", &vol->limitergain) || !((vol->limitergain > 0.0) && (vol->limitergain < 1.0)))
        {
                st_fail(st_vol_effect.usage);
                return ST_EOF;                  
        }
        
        vol->uselimiter = 1; /* ok, we'll use it */
        /* The following equation is derived so that there is no 
         * discontinuity in output amplitudes */
        /* and a ST_SAMPLE_MAX input always maps to a ST_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 = ST_SAMPLE_MAX * (1.0 - vol->limitergain) / (fabs(vol->gain) - vol->limitergain);
    }
    

    return ST_SUCCESS;
}

/*
 * Start processing
 */
static int st_vol_start(eff_t effp)
{
    vol_t vol = (vol_t) effp->priv;
    
    if (vol->gain == 1)
      return ST_EFF_NULL;

    if (effp->outinfo.channels != effp->ininfo.channels) {
        st_fail("vol cannot handle different channels (in %d, out %d)"
             " use avg or pan", effp->ininfo.channels, effp->outinfo.channels);
        return ST_EOF;
    }

    if (effp->outinfo.rate != effp->ininfo.rate) {
        st_fail("vol cannot handle different rates (in %ld, out %ld)"
             " use resample", effp->ininfo.rate, effp->outinfo.rate);
        return ST_EOF;
    }

    vol->limited = 0;
    vol->totalprocessed = 0;

    return ST_SUCCESS;
}

/*
 * Process data.
 */
static int st_vol_flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf, 
                st_size_t *isamp, st_size_t *osamp)
{
    vol_t vol = (vol_t) effp->priv;
    register double gain = vol->gain;
    register double limiterthreshhold = vol->limiterthreshhold;
    register double sample;
    register st_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 =  (ST_SAMPLE_MAX - vol->limitergain * (ST_SAMPLE_MAX - sample));
                        vol->limited++;
                }
                else if (sample < -limiterthreshhold)
                {
                        sample = -(ST_SAMPLE_MAX - vol->limitergain * (ST_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 < ST_SAMPLE_MIN)
                            sample = ST_SAMPLE_MIN;
                        vol->limited++;
                } else
                        sample = gain * sample;

                ST_SAMPLE_CLIP_COUNT(sample, effp->clips);
               *obuf++ = sample;
            }
    }
    else
    {
        /* quite basic, with clipping */
        for (;len>0; len--)
        {
                sample = gain * *ibuf++;
                ST_SAMPLE_CLIP_COUNT(sample, effp->clips);
                *obuf++ = sample;
        }
    }
    return ST_SUCCESS;
}

/*
 * Do anything required when you stop reading samples.  
 * Don't close input file! 
 */
static int st_vol_stop(eff_t effp)
{
    vol_t vol = (vol_t) effp->priv;
    if (vol->limited)
    {
        st_warn("limited %d values (%d percent).", 
             vol->limited, (int) (vol->limited * 100.0 / vol->totalprocessed));
    }
    return ST_SUCCESS;
}

static st_effect_t st_vol_effect = {
  "vol",
  "Usage: vol gain [ type [ limitergain ] ]"
  "       (default type=amplitude: 1.0 is constant, <0.0 change phase;\n"
  "       type=power 1.0 is constant; type=dB: 0.0 is constant, +6 doubles ampl.)\n"
  "       The peak limiter has a gain much less than 1.0 (ie 0.05 or 0.02) which is only\n"
  "       used on peaks to prevent clipping. (default is no limiter)",
  ST_EFF_MCHAN,
  st_vol_getopts,
  st_vol_start,
  st_vol_flow,
  st_effect_nothing_drain,
  st_vol_stop,
  st_effect_nothing
};

const st_effect_t *st_vol_effect_fn(void)
{
    return &st_vol_effect;
}