ref: a043cf72d4c034c7a6b8bfd94e6e22588d952281
dir: /src/stretch.c/
/*
* (c) march/april 2000 Fabien COELHO <fabien@coelho.net> for sox.
*
* Basic time stretcher.
* cross fade samples so as to go slower of faster.
*
* The automaton is based on 6 parameters:
* - stretch factor f
* - window size w
* - input step i
* output step o=f*i
* - steady state of window s, ss = s*w
* - type of cross fading
*
* I decided of the default values of these parameters based
* on some small non extensive tests. maybe better defaults
* can be suggested.
*
* It cannot handle different number of channels.
* It cannot handle rate change.
*/
#include "st_i.h"
#include <stdlib.h> /* malloc and free */
#include <string.h> /* memcpy() */
static st_effect_t st_stretch_effect;
#ifndef STRETCH_FLOAT
#define STRETCH_FLOAT float
#define STRETCH_FLOAT_SCAN "%f"
#endif
/* ok, it looks stupid to have such constant.
this is because of the cast, if floats are switched to doubles.
*/
#define ZERO ((STRETCH_FLOAT)(0.0e0))
#define HALF ((STRETCH_FLOAT)(0.5e0))
#define ONE ((STRETCH_FLOAT)(1.0e0))
#define MONE ((STRETCH_FLOAT)(-1.0e0))
#define ONETHOUSANDS ((STRETCH_FLOAT)(0.001e0))
#define DEFAULT_SLOW_SHIFT_RATIO ((STRETCH_FLOAT)(0.8e0))
#define DEFAULT_FAST_SHIFT_RATIO ONE
#define DEFAULT_STRETCH_WINDOW ((STRETCH_FLOAT)(20.0e0)) /* ms */
/* I'm planing to put some common fading stuff outside.
It's also used in pitch.c
*/
typedef enum { st_linear_fading } st_fading_t;
#define DEFAULT_FADING st_linear_fading
typedef enum { input_state, output_state } stretch_status_t;
typedef struct
{
/* options
* Q: maybe shift could be allowed > 1.0 with factor < 1.0 ???
*/
STRETCH_FLOAT factor; /* strech factor. 1.0 means copy. */
STRETCH_FLOAT window; /* window in ms */
st_fading_t fade; /* type of fading */
STRETCH_FLOAT shift; /* shift ratio wrt window. <1.0 */
STRETCH_FLOAT fading; /* fading ratio wrt window. <0.5 */
/* internal stuff
*/
stretch_status_t state; /* automaton status */
int size; /* buffer size */
int index; /* next available element */
st_sample_t *ibuf; /* input buffer */
int ishift; /* input shift */
int oindex; /* next evailable element */
STRETCH_FLOAT * obuf; /* output buffer */
int oshift; /* output shift */
int fsize; /* fading size */
STRETCH_FLOAT * fbuf; /* fading, 1.0 -> 0.0 */
} * stretch_t;
/*
static void debug(stretch_t s, char * where)
{
st_debug(
"%s: (f=%.2f w=%.2f r=%.2f f=%.2f)"
" st=%d s=%d ii=%d is=%d oi=%d os=%d fs=%d\n",
where, s->factor, s->window, s->shift, s->fading,
s->state, s->size, s->index, s->ishift,
s->oindex, s->oshift, s->fsize);
}
*/
/*
* Process options
*/
int st_stretch_getopts(eff_t effp, int n, char **argv)
{
char usage[1024];
stretch_t stretch = (stretch_t) effp->priv;
/* default options */
stretch->factor = ONE; /* default is no change */
stretch->window = DEFAULT_STRETCH_WINDOW;
stretch->fade = st_linear_fading;
if (n>0 && !sscanf(argv[0], STRETCH_FLOAT_SCAN, &stretch->factor))
{
sprintf (usage, "%s\n\terror while parsing factor", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
if (n>1 && !sscanf(argv[1], STRETCH_FLOAT_SCAN, &stretch->window))
{
sprintf (usage, "%s\n\terror while parsing window size", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
if (n>2)
{
switch (argv[2][0])
{
case 'l':
case 'L':
stretch->fade = st_linear_fading;
break;
default:
sprintf (usage, "%s\n\terror while parsing fade type", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
}
/* default shift depends whether we go slower or faster */
stretch->shift = (stretch->factor <= ONE) ?
DEFAULT_FAST_SHIFT_RATIO: DEFAULT_SLOW_SHIFT_RATIO;
if (n>3 && !sscanf(argv[3], STRETCH_FLOAT_SCAN, &stretch->shift))
{
sprintf (usage, "%s\n\terror while parsing shift ratio", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
if (stretch->shift > ONE || stretch->shift <= ZERO)
{
sprintf (usage, "%s\n\terror with shift ratio value", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
/* default fading stuff...
it makes sense for factor >= 0.5
*/
if (stretch->factor<ONE)
stretch->fading = ONE - (stretch->factor*stretch->shift);
else
stretch->fading = ONE - stretch->shift;
if (stretch->fading > HALF) stretch->fading = HALF;
if (n>4 && !sscanf(argv[4], STRETCH_FLOAT_SCAN, &stretch->fading))
{
sprintf (usage, "%s\n\terror while parsing fading ratio", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
if (stretch->fading > HALF || stretch->fading < ZERO)
{
sprintf (usage, "%s\n\terror with fading ratio value", st_stretch_effect.usage);
st_fail(usage);
return ST_EOF;
}
return ST_SUCCESS;
}
/*
* Start processing
*/
int st_stretch_start(eff_t effp)
{
stretch_t stretch = (stretch_t) effp->priv;
register int i;
/* not necessary. taken care by effect processing? */
if (effp->outinfo.channels != effp->ininfo.channels)
{
st_fail("STRETCH 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("STRETCH cannot handle different rates (in=%ld, out=%ld)"
" use resample or rate", effp->ininfo.rate, effp->outinfo.rate);
return ST_EOF;
}
stretch->state = input_state;
stretch->size = (int)(effp->outinfo.rate * ONETHOUSANDS * stretch->window);
/* start in the middle of an input to avoid initial fading... */
stretch->index = stretch->size/2;
stretch->ibuf = (st_sample_t *) malloc(stretch->size *
sizeof(st_sample_t));
/* the shift ratio deal with the longest of ishift/oshift
hence ishift<=size and oshift<=size. should be asserted.
*/
if (stretch->factor < ONE)
{
stretch->ishift = (int) (stretch->shift * stretch->size);
stretch->oshift = (int) (stretch->factor * stretch->ishift);
}
else
{
stretch->oshift = (int) (stretch->shift * stretch->size);
stretch->ishift = (int) (stretch->oshift / stretch->factor);
}
stretch->oindex = stretch->index; /* start as synchronized */
stretch->obuf = (STRETCH_FLOAT *)
malloc(stretch->size * sizeof(STRETCH_FLOAT));
stretch->fsize = (int) (stretch->fading * stretch->size);
stretch->fbuf = (STRETCH_FLOAT *)
malloc(stretch->fsize * sizeof(STRETCH_FLOAT));
if (!stretch->ibuf || !stretch->obuf || !stretch->fbuf)
{
st_fail("some malloc failed");
return ST_EOF;
}
/* initialize buffers
*/
for (i=0; i<stretch->size; i++)
stretch->ibuf[i] = 0;
for (i=0; i<stretch->size; i++)
stretch->obuf[i] = ZERO;
if (stretch->fsize>1)
{
register STRETCH_FLOAT slope = ONE / (stretch->fsize - 1);
stretch->fbuf[0] = ONE;
for (i=1; i<stretch->fsize-1; i++)
stretch->fbuf[i] = slope * (stretch->fsize-i-1);
stretch->fbuf[stretch->fsize-1] = ZERO;
} else if (stretch->fsize==1)
stretch->fbuf[0] = ONE;
/* debug(stretch, "start"); */
return ST_SUCCESS;
}
/* accumulates input ibuf to output obuf with fading fbuf
*/
static void combine(stretch_t stretch)
{
register int i, size, fsize;
size = stretch->size;
fsize = stretch->fsize;
/* fade in */
for (i=0; i<fsize; i++)
stretch->obuf[i] += stretch->fbuf[fsize-i-1]*stretch->ibuf[i];
/* steady state */
for (; i<size-fsize; i++)
stretch->obuf[i] += stretch->ibuf[i];
/* fade out */
for (; i<size; i++)
stretch->obuf[i] += stretch->fbuf[i-size+fsize]*stretch->ibuf[i];
}
/*
* Processes flow.
*/
int st_stretch_flow(eff_t effp, st_sample_t *ibuf, st_sample_t *obuf,
st_size_t *isamp, st_size_t *osamp)
{
stretch_t stretch = (stretch_t) effp->priv;
st_size_t iindex, oindex;
int i;
iindex = 0;
oindex = 0;
while (iindex<*isamp && oindex<*osamp)
{
if (stretch->state == input_state)
{
st_size_t tocopy = min(*isamp-iindex,
stretch->size-stretch->index);
memcpy(stretch->ibuf+stretch->index,
ibuf+iindex, tocopy*sizeof(st_sample_t));
iindex += tocopy;
stretch->index += tocopy;
if (stretch->index == stretch->size)
{
/* compute */
combine(stretch);
/* shift input */
for (i=0; i+stretch->ishift<stretch->size; i++)
stretch->ibuf[i] = stretch->ibuf[i+stretch->ishift];
stretch->index -= stretch->ishift;
/* switch to output state */
stretch->state = output_state;
}
}
if (stretch->state == output_state)
{
while (stretch->oindex<stretch->oshift && oindex<*osamp)
{
float f;
f = stretch->obuf[stretch->oindex++];
ST_EFF_SAMPLE_CLIP_COUNT(f);
obuf[oindex++] = f;
}
if (stretch->oindex >= stretch->oshift && oindex<*osamp)
{
stretch->oindex -= stretch->oshift;
/* shift internal output buffer */
for (i=0; i+stretch->oshift<stretch->size; i++)
stretch->obuf[i] = stretch->obuf[i+stretch->oshift];
/* pad with 0 */
for (; i<stretch->size; i++)
stretch->obuf[i] = ZERO;
stretch->state = input_state;
}
}
}
*isamp = iindex;
*osamp = oindex;
return ST_SUCCESS;
}
/*
* Drain buffer at the end
* maybe not correct ? end might be artificially faded?
*/
int st_stretch_drain(eff_t effp, st_sample_t *obuf, st_size_t *osamp)
{
stretch_t stretch = (stretch_t) effp->priv;
register int i;
st_size_t oindex;
oindex = 0;
if (stretch->state == input_state)
{
for (i=stretch->index; i<stretch->size; i++)
stretch->ibuf[i] = 0;
combine(stretch);
stretch->state = output_state;
}
for (; oindex<*osamp && stretch->oindex<stretch->index;)
{
float f;
f = stretch->obuf[stretch->oindex++];
ST_EFF_SAMPLE_CLIP_COUNT(f);
obuf[oindex++] = f;
}
*osamp = oindex;
if (stretch->oindex == stretch->index)
return ST_EOF;
else
return ST_SUCCESS;
}
/*
* Do anything required when you stop reading samples.
* Don't close input file!
*/
int st_stretch_stop(eff_t effp)
{
stretch_t stretch = (stretch_t) effp->priv;
free(stretch->ibuf);
free(stretch->obuf);
free(stretch->fbuf);
return ST_SUCCESS;
}
static st_effect_t st_stretch_effect = {
"stretch",
"Usage: stretch factor [window fade shift fading]\n"
" (expansion, frame in ms, lin/..., unit<1.0, unit<0.5)\n"
" (defaults: 1.0 20 lin ...)",
0,
st_stretch_getopts,
st_stretch_start,
st_stretch_flow,
st_stretch_drain,
st_stretch_stop
};
const st_effect_t *st_stretch_effect_fn(void)
{
return &st_stretch_effect;
}