shithub: sox

ref: f723862e5e5d3b70b1db0e91ced9077899686f21
dir: /src/pan.c/

View raw version
/*
 * (c) 20/03/2000 Fabien COELHO <fabien@coelho.net> for sox.
 *
 * Same copyright as SOX. See Sox (and Sun?;-)
 *
 * Change panorama of sound file with basic linear volume interpolation.
 * The human ear is not sensible to phases? What about delay? too short?
 * 
 * Volume is kept constant (?). 
 * Beware of saturations!
 * Operations are carried out on floats.
 * Can handle different number of channels.
 * Cannot handle rate change.
 *
 * Initially based on avg effect. 
 * pan 0.0 basically behaves as avg.
 */

#include "st.h"

/* should be taken care in st.h? */
#include <limits.h> /* LONG_MAX */

/* type for computations.
   float mantissa is okay for 8 or 16 bits signals.
   maybe a little bit short for 24 bits.
   switch to double if you're not happy.
 */
#ifndef PAN_FLOAT
#define PAN_FLOAT float /* float or double */
#define PAN_FLOAT_SCAN "%f" /* "%f" or "%lf" */
#endif

/* constants */

#define TWO      ((PAN_FLOAT)(2.0e0))
#define ONEHALF  ((PAN_FLOAT)(1.5e0))
#define ONE      ((PAN_FLOAT)(1.0e0))
#define MONE     ((PAN_FLOAT)(-1.0e0))
#define HALF     ((PAN_FLOAT)(0.5e0))
#define QUARTER  ((PAN_FLOAT)(0.25e0))
#define ZERO     ((PAN_FLOAT)(0.0e0))

/* error message */

#define PAN_USAGE "Usage: pan direction (in [-1.0 .. 1.0])"

/* structure to hold pan parameter */

typedef struct {
    /* pan option
     */
    PAN_FLOAT dir; /* direction, from left (-1.0) to right (1.0) */

    /* local data
     */
    int clipped;   /* number of clipped values */
} * pan_t;

/*
 * Process options
 */
int st_pan_getopts(effp, n, argv) 
eff_t effp;
int n;
char **argv;
{
    pan_t pan = (pan_t) effp->priv; 
    
    pan->dir = ZERO; /* default is no change */
    pan->clipped = 0;
    
    if (n && (!sscanf(argv[0], PAN_FLOAT_SCAN, &pan->dir) || 
	      pan->dir < MONE || pan->dir > ONE))
    {
	st_fail(PAN_USAGE);
	return ST_EOF;
    }

    return ST_SUCCESS;
}

/*
 * Start processing
 */
int st_pan_start(effp)
eff_t effp;
{
    if (effp->outinfo.channels==1)
	st_warn("PAN onto a mono channel...");

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

    return ST_SUCCESS;
}


/* clip value if necessary. see comments about such a function in vol.c
 * Okay, it might be quite slow to have such a function for so small
 * a task. Hopefully the function can be inlined by the compiler?
 */
static LONG clip(pan_t pan, PAN_FLOAT value)
{
    if (value < -LONG_MAX) 
    {
	pan->clipped++;
	return -LONG_MAX;
    }
    else if (value > LONG_MAX) 
    {
	pan->clipped++;
	return LONG_MAX;
    } /* else */

    return (LONG) value;
}

#ifndef MIN
#define MIN(s1,s2) ((s1)<(s2)?(s1):(s2))
#endif

#define UNEXPECTED_CHANNELS \
    st_fail("unexpected number of channels (in=%d, out=%d)", ich, och); \
    return ST_EOF

/*
 * Process either isamp or osamp samples, whichever is smaller.
 */
int st_pan_flow(effp, ibuf, obuf, isamp, osamp)
eff_t effp;
LONG *ibuf, *obuf;
LONG *isamp, *osamp;
{
    pan_t pan = (pan_t) effp->priv;
    register LONG len;
    register int done, ich, och;
    register PAN_FLOAT left, right, dir, hdir;
    
    dir   = pan->dir;    /* -1   <=  dir  <= 1   */
    hdir  = HALF * dir;  /* -0.5 <=  hdir <= 0.5 */
    left  = HALF - hdir; /*  0   <=  left <= 1   */
    right = HALF + hdir; /*  0   <= right <= 1   */

    ich = effp->ininfo.channels;
    och = effp->outinfo.channels;

    len = MIN(*osamp/och,*isamp/ich);

    /* report back how much is processed. */
    *isamp = len*ich;
    *osamp = len*och;
    
    /* 9 different cases to handle: (1,2,4) X (1,2,4) */
    switch (och) {
    case 1: /* pan on mono channel... not much sense. just avg. */
	switch (ich) {
	case 1: /* simple copy */
	    for (done=0; done<len; done++)
		*obuf++ = *ibuf++;
	    break;
	case 2: /* average 2 */
	    for (done=0; done<len; done++)
		*obuf++ = clip(pan, HALF*ibuf[0] + HALF*ibuf[1]),
		    ibuf += 2;
	    break;
	case 4: /* average 4 */
	    for (done=0; done<len; done++)
		*obuf++ = clip(pan, 
			       QUARTER*ibuf[0] + QUARTER*ibuf[1] + 
			       QUARTER*ibuf[2] + QUARTER*ibuf[3]),
		    ibuf += 4;
	    break;
	default:
	    UNEXPECTED_CHANNELS;
	    break;
	} /* end first switch in channel */
	break;
    case 2:
	switch (ich) {
	case 1: /* linear */
	    for (done=0; done<len; done++)
	    {
		obuf[0] = clip(pan, left * ibuf[0]);
		obuf[1] = clip(pan, right * ibuf[0]);
		obuf += 2;
		ibuf++;
	    }
	    break;
	case 2: /* linear panorama. 
		 * I'm not sure this is the right way to do it.
		 */
	    if (dir <= ZERO) /* to the left */
	    {
		register PAN_FLOAT volume, cll, clr, cr;

		volume = ONE - HALF*dir;
		cll = volume*(ONEHALF-left);
		clr = volume*(left-HALF);
		cr  = volume*(ONE+dir);

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cll * ibuf[0] + clr * ibuf[1]);
		    obuf[1] = clip(pan, cr * ibuf[1]);
		    obuf += 2;
		    ibuf += 2;
		}
	    }
	    else /* to the right */
	    {
		register PAN_FLOAT volume, cl, crl, crr;

		volume = ONE + HALF*dir;
		cl  = volume*(ONE-dir);
		crl = volume*(right-HALF);
		crr = volume*(ONEHALF-right);

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cl * ibuf[0]);
		    obuf[1] = clip(pan, crl * ibuf[0] + crr * ibuf[1]);
		    obuf += 2;
		    ibuf += 2;
		}
	    }
	    break;
	case 4:
	    if (dir <= ZERO) /* to the left */
	    {
		register PAN_FLOAT volume, cll, clr, cr;

		volume = ONE - HALF*dir;
		cll = volume*(ONEHALF-left);
		clr = volume*(left-HALF);
		cr  = volume*(ONE+dir);

		for (done=0; done<len; done++)
		{
		    register PAN_FLOAT ibuf0, ibuf1;

		    /* build stereo signal */
		    ibuf0 = HALF*ibuf[0] + HALF*ibuf[2];
		    ibuf1 = HALF*ibuf[1] + HALF*ibuf[3];

		    /* pan it */
		    obuf[0] = clip(pan, cll * ibuf0 + clr * ibuf1);
		    obuf[1] = clip(pan, cr * ibuf1);
		    obuf += 2;
		    ibuf += 4;
		}
	    }
	    else /* to the right */
	    {
		register PAN_FLOAT volume, cl, crl, crr;

		volume = ONE + HALF*dir;
		cl  = volume*(ONE-dir);
		crl = volume*(right-HALF);
		crr = volume*(ONEHALF-right);

		for (done=0; done<len; done++)
		{
		    register PAN_FLOAT ibuf0, ibuf1;

		    ibuf0 = HALF*ibuf[0] + HALF*ibuf[2];
		    ibuf1 = HALF*ibuf[1] + HALF*ibuf[3];

		    obuf[0] = clip(pan, cl * ibuf0);
		    obuf[1] = clip(pan, crl * ibuf0 + crr * ibuf1);
		    obuf += 2;
		    ibuf += 4;
		}
	    }
	    break;
	default:
	    UNEXPECTED_CHANNELS;
	    break;
	} /* end second switch in channel */
	break;
    case 4:
	switch (ich) {
	case 1: /* linear */
	    {
		register PAN_FLOAT cr, cl;

		cl = HALF*left;
		cr = HALF*right;

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cl * ibuf[0]);
		    obuf[2] = obuf[0];
		    obuf[1] = clip(pan, cr * ibuf[0]);
		    ibuf[3] = obuf[1];
		    obuf += 4;
		    ibuf++;
		}
	    }
	    break;
	case 2: /* simple linear panorama */
	    if (dir <= ZERO) /* to the left */
	    {
		register PAN_FLOAT volume, cll, clr, cr;

		volume = HALF - QUARTER*dir;
		cll = volume * (ONEHALF-left);
		clr = volume * (left-HALF);
		cr  = volume * (ONE+dir);

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cll * ibuf[0] + clr * ibuf[1]);
		    obuf[2] = obuf[0];
		    obuf[1] = clip(pan, cr * ibuf[1]);
		    ibuf[3] = obuf[1];
		    obuf += 4;
		    ibuf += 2;
		}
	    }
	    else /* to the right */
	    {
		register PAN_FLOAT volume, cl, crl, crr;

		volume = HALF + QUARTER*dir;
		cl  = volume * (ONE-dir);
		crl = volume * (right-HALF);
		crr = volume * (ONEHALF-right);

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cl * ibuf[0]);
		    obuf[2] = obuf[0];
		    obuf[1] = clip(pan, crl * ibuf[0] + crr * ibuf[1]);
		    ibuf[3] = obuf[1];
		    obuf += 4;
		    ibuf += 2;
		}
	    }
	    break;
	case 4:
	    /* maybe I could improve the formula to reverse...
	       also, turn only by quarters.
	     */
	    if (dir <= ZERO) /* to the left */
	    {
		register PAN_FLOAT cown, cright;

		cright = -dir;
		cown = ONE + dir;

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cown*ibuf[0] + cright*ibuf[1]);
		    obuf[1] = clip(pan, cown*ibuf[1] + cright*ibuf[3]);
		    obuf[2] = clip(pan, cown*ibuf[2] + cright*ibuf[0]);
		    obuf[3] = clip(pan, cown*ibuf[3] + cright*ibuf[2]);
		    obuf += 4;
		    ibuf += 4;		    
		}
	    }
	    else /* to the right */
	    {
		register PAN_FLOAT cleft, cown;

		cleft = dir;
		cown = ONE - dir;

		for (done=0; done<len; done++)
		{
		    obuf[0] = clip(pan, cleft*ibuf[2] + cown*ibuf[0]);
		    obuf[1] = clip(pan, cleft*ibuf[0] + cown*ibuf[1]);
		    obuf[2] = clip(pan, cleft*ibuf[3] + cown*ibuf[2]);
		    obuf[3] = clip(pan, cleft*ibuf[1] + cown*ibuf[3]);
		    obuf += 4;
		    ibuf += 4;
		}
	    }
	    break;
	default:
	    UNEXPECTED_CHANNELS;
	    break;
	} /* end third switch in channel */
	break;
    default:
	UNEXPECTED_CHANNELS;
	break;
    } /* end switch out channel */

    return ST_SUCCESS;
}

/*
 * Do anything required when you stop reading samples.  
 * Don't close input file! 
 *
 * Should have statistics on right, left, and output amplitudes.
 */
int st_pan_stop(effp)
eff_t effp;
{
    pan_t pan = (pan_t) effp->priv;
    if (pan->clipped) {
	st_warn("PAN clipped %d values, maybe adjust volume?",
	     pan->clipped);
    }
    return ST_SUCCESS;
}