ref: f41f8eda4c3a99aaeffd9be0b808dd30beff239e
dir: /src/pan.c/
/* (c) 20/03/2000 Fabien COELHO <fabien@coelho.net> for sox.
*
* 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
*
* 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 doubles.
* Can handle different number of channels.
* Cannot handle rate change.
*
* Initially based on avg effect.
* pan 0.0 basically behaves as avg.
*/
#include "sox_i.h"
#include <string.h>
/* structure to hold pan parameter */
typedef struct {double direction;} priv_t; /* from left (-1.0) to right (1.0) */
/*
* Process options
*/
static int sox_pan_getopts(sox_effect_t * effp, int argc, char **argv)
{
priv_t * pan = (priv_t *) effp->priv;
--argc, ++argv;
pan->direction = 0.0; /* default is no change */
if (argc && (!sscanf(argv[0], "%lf", &pan->direction) ||
pan->direction < -1.0 || pan->direction > 1.0))
return lsx_usage(effp);
return SOX_SUCCESS;
}
/*
* Start processing
*/
static int sox_pan_start(sox_effect_t * effp)
{
if (effp->out_signal.channels==1)
lsx_warn("PAN onto a mono channel...");
return SOX_SUCCESS;
}
#define UNEXPECTED_CHANNELS \
lsx_fail("unexpected number of channels (in=%d, out=%d)", ich, och); \
free(ibuf_copy); \
return SOX_EOF
/*
* Process either isamp or osamp samples, whichever is smaller.
*/
static int sox_pan_flow(sox_effect_t * effp, const sox_sample_t *ibuf, sox_sample_t *obuf,
size_t *isamp, size_t *osamp)
{
priv_t * pan = (priv_t *) effp->priv;
size_t len, done;
sox_sample_t *ibuf_copy;
char ich, och;
double left, right, direction, hdir;
ibuf_copy = lsx_malloc(*isamp * sizeof(sox_sample_t));
memcpy(ibuf_copy, ibuf, *isamp * sizeof(sox_sample_t));
direction = pan->direction; /* -1 <= direction <= 1 */
hdir = 0.5 * direction; /* -0.5 <= hdir <= 0.5 */
left = 0.5 - hdir; /* 0 <= left <= 1 */
right = 0.5 + hdir; /* 0 <= right <= 1 */
ich = effp->in_signal.channels;
och = effp->out_signal.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_copy++;
break;
case 2: /* average 2 */
for (done=0; done<len; done++)
{
double f;
f = 0.5*ibuf_copy[0] + 0.5*ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
*obuf++ = f;
ibuf_copy += 2;
}
break;
case 4: /* average 4 */
for (done=0; done<len; done++)
{
double f;
f = 0.25*ibuf_copy[0] + 0.25*ibuf_copy[1] +
0.25*ibuf_copy[2] + 0.25*ibuf_copy[3];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
*obuf++ = f;
ibuf_copy += 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++)
{
double f;
f = left * ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = right * ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
obuf += 2;
ibuf_copy++;
}
break;
case 2: /* linear panorama.
* I'm not sure this is the right way to do it.
*/
if (direction <= 0.0) /* to the left */
{
register double volume, cll, clr, cr;
volume = 1.0 - 0.5*direction;
cll = volume*(1.5-left);
clr = volume*(left-0.5);
cr = volume*(1.0+direction);
for (done=0; done<len; done++)
{
double f;
f = cll * ibuf_copy[0] + clr * ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = cr * ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
obuf += 2;
ibuf_copy += 2;
}
}
else /* to the right */
{
register double volume, cl, crl, crr;
volume = 1.0 + 0.5*direction;
cl = volume*(1.0-direction);
crl = volume*(right-0.5);
crr = volume*(1.5-right);
for (done=0; done<len; done++)
{
double f;
f = cl * ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = crl * ibuf_copy[0] + crr * ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
obuf += 2;
ibuf_copy += 2;
}
}
break;
case 4:
if (direction <= 0.0) /* to the left */
{
register double volume, cll, clr, cr;
volume = 1.0 - 0.5*direction;
cll = volume*(1.5-left);
clr = volume*(left-0.5);
cr = volume*(1.0+direction);
for (done=0; done<len; done++)
{
register double ibuf0, ibuf1, f;
/* build stereo signal */
ibuf0 = 0.5*ibuf_copy[0] + 0.5*ibuf_copy[2];
ibuf1 = 0.5*ibuf_copy[1] + 0.5*ibuf_copy[3];
/* pan it */
f = cll * ibuf0 + clr * ibuf1;
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = cr * ibuf1;
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
obuf += 2;
ibuf_copy += 4;
}
}
else /* to the right */
{
register double volume, cl, crl, crr;
volume = 1.0 + 0.5*direction;
cl = volume*(1.0-direction);
crl = volume*(right-0.5);
crr = volume*(1.5-right);
for (done=0; done<len; done++)
{
register double ibuf0, ibuf1, f;
ibuf0 = 0.5*ibuf_copy[0] + 0.5*ibuf_copy[2];
ibuf1 = 0.5*ibuf_copy[1] + 0.5*ibuf_copy[3];
f = cl * ibuf0;
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = crl * ibuf0 + crr * ibuf1;
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
obuf += 2;
ibuf_copy += 4;
}
}
break;
default:
UNEXPECTED_CHANNELS;
break;
} /* end second switch in channel */
break;
case 4:
switch (ich) {
case 1: /* linear */
{
register double cr, cl;
cl = 0.5*left;
cr = 0.5*right;
for (done=0; done<len; done++)
{
double f;
f = cl * ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[2] = obuf[0] = f;
f = cr * ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
ibuf_copy[3] = obuf[1] = f;
obuf += 4;
ibuf_copy++;
}
}
break;
case 2: /* simple linear panorama */
if (direction <= 0.0) /* to the left */
{
register double volume, cll, clr, cr;
volume = 0.5 - 0.25*direction;
cll = volume * (1.5-left);
clr = volume * (left-0.5);
cr = volume * (1.0+direction);
for (done=0; done<len; done++)
{
double f;
f = cll * ibuf_copy[0] + clr * ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[2] = obuf[0] = f;
f = cr * ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
ibuf_copy[3] = obuf[1] = f;
obuf += 4;
ibuf_copy += 2;
}
}
else /* to the right */
{
register double volume, cl, crl, crr;
volume = 0.5 + 0.25*direction;
cl = volume * (1.0-direction);
crl = volume * (right-0.5);
crr = volume * (1.5-right);
for (done=0; done<len; done++)
{
double f;
f = cl * ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[2] = obuf[0] =f ;
f = crl * ibuf_copy[0] + crr * ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
ibuf_copy[3] = obuf[1] = f;
obuf += 4;
ibuf_copy += 2;
}
}
break;
case 4:
/* maybe I could improve the formula to reverse...
also, turn only by quarters.
*/
if (direction <= 0.0) /* to the left */
{
register double cown, cright;
cright = -direction;
cown = 1.0 + direction;
for (done=0; done<len; done++)
{
double f;
f = cown*ibuf_copy[0] + cright*ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = cown*ibuf_copy[1] + cright*ibuf_copy[3];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
f = cown*ibuf_copy[2] + cright*ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[2] = f;
f = cown*ibuf_copy[3] + cright*ibuf_copy[2];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[3] = f;
obuf += 4;
ibuf_copy += 4;
}
}
else /* to the right */
{
register double cleft, cown;
cleft = direction;
cown = 1.0 - direction;
for (done=0; done<len; done++)
{
double f;
f = cleft*ibuf_copy[2] + cown*ibuf_copy[0];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[0] = f;
f = cleft*ibuf_copy[0] + cown*ibuf_copy[1];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[1] = f;
f = cleft*ibuf_copy[3] + cown*ibuf_copy[2];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[2] = f;
f = cleft*ibuf_copy[1] + cown*ibuf_copy[3];
SOX_SAMPLE_CLIP_COUNT(f, effp->clips);
obuf[3] = f;
obuf += 4;
ibuf_copy += 4;
}
}
break;
default:
UNEXPECTED_CHANNELS;
break;
} /* end third switch in channel */
break;
default:
UNEXPECTED_CHANNELS;
break;
} /* end switch out channel */
free(ibuf_copy - len * ich);
return SOX_SUCCESS;
}
/*
* FIXME: Add a stop function with statistics on right, left, and output amplitudes.
*/
static sox_effect_handler_t sox_pan_effect = {
"pan",
"direction (in [-1.0 .. 1.0])",
SOX_EFF_MCHAN | SOX_EFF_CHAN | SOX_EFF_DEPRECATED,
sox_pan_getopts,
sox_pan_start,
sox_pan_flow,
NULL,
NULL,
NULL, sizeof(priv_t)
};
const sox_effect_handler_t *lsx_pan_effect_fn(void)
{
return &sox_pan_effect;
}