/*
 * $Revision: 1.1.1.1 $
 * $Date: 1998/08/15 19:16:48 $
 *
 * Copyright (c) 1995,1996 University College London
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, is permitted, for non-commercial use only, provided
 * that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Science
 *      Department at University College London
 * 4. Neither the name of the University nor of the Department may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 * Use of this software for commercial purposes is explicitly forbidden
 * unless prior written permission is obtained from the authors.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#include <sys/types.h>
#include <math.h>
#include "bat_include.h"

#define MAXWINDOW	1000	/* Max analysis window length */
#define FS		8000.0	/* Sampling rate */

#define DOWN		5	/* Decimation for pitch analyzer */
#define PITCHORDER	4	/* Model order for pitch analyzer */
#define FC		600.0	/* Pitch analyzer filter cutoff */
#define MINPIT		50.0	/* Minimum pitch */
#define MAXPIT		300.0	/* Maximum pitch */

#define MINPER		(int)(FS/(DOWN*MAXPIT)+.5)	/* Minimum period */
#define MAXPER		(int)(FS/(DOWN*MINPIT)+.5)	/* Maximum period */

#define WSCALE		1.5863	/* Energy loss due to windowing */

#define BUFLEN		((FRAMESIZE * 3) / 2)

static float s[MAXWINDOW], y[MAXWINDOW], h[MAXWINDOW];
static float fa[6], u, u1, yp1, yp2;

#define BIAS 0x84		/* define the add-in bias for 16 bit samples */
#define CLIP 32635

static void
auto_correl(float *w, int n, int p, float *r)
{
	int i, k, nk;

	for (k = 0; k <= p; k++) {
		nk = n - k;
		r[k] = 0.0;
		for (i = 0; i < nk; i++)
			r[k] += w[i] * w[i + k];
	}
}

static void
durbin(float *r, int p, float *k, float *g)
{
	int i, j;
	float a[LPC_FILTORDER + 1], at[LPC_FILTORDER + 1], e;

	for (i = 0; i <= p; i++)
		a[i] = at[i] = 0.0;

	e = r[0];
	for (i = 1; i <= p; i++) {
		k[i] = -r[i];
		for (j = 1; j < i; j++) {
			at[j] = a[j];
			k[i] -= a[j] * r[i - j];
		}
		k[i] /= e;
		a[i] = k[i];
		for (j = 1; j < i; j++)
			a[j] = at[j] + k[i] * at[i - j];
		e *= 1.0f - k[i] * k[i];
	}

	*g = (float)sqrt(e);
}

static void
inverse_filter(float *w, float *k)
{
	int i, j;
	float b[PITCHORDER + 1], bp[PITCHORDER + 1], f[PITCHORDER + 1];

	for (i = 0; i <= PITCHORDER; i++)
		b[i] = f[i] = bp[i] = 0.0;

	for (i = 0; i < BUFLEN / DOWN; i++) {
		f[0] = b[0] = w[i];
		for (j = 1; j <= PITCHORDER; j++) {
			f[j] = f[j - 1] + k[j] * bp[j - 1];
			b[j] = k[j] * f[j - 1] + bp[j - 1];
			bp[j - 1] = b[j - 1];
		}
		w[i] = f[PITCHORDER];
	}
}

static void
calc_pitch(float *w, float *per)
{
	int i, j, rpos;
	float d[MAXWINDOW / DOWN], k[PITCHORDER + 1], r[MAXPER + 1], g, rmax;
	float rval, rm, rp;
	float a, b, c, x, y;
	static int vuv = 0;

	for (i = 0, j = 0; i < BUFLEN; i += DOWN)
		d[j++] = w[i];
	auto_correl(d, BUFLEN / DOWN, PITCHORDER, r);
	durbin(r, PITCHORDER, k, &g);
	inverse_filter(d, k);
	auto_correl(d, BUFLEN / DOWN, MAXPER, r);
	rpos = 1;
	rmax = 0.0;
	for (i = MINPER; i < MAXPER; i++) {
		if (r[i] > rmax) {
			rmax = r[i];
			rpos = i;
		}
	}

	rm = r[rpos - 1];
	rp = r[rpos + 1];
	rval = rmax / r[0];

	a = 0.5f * rm - rmax + 0.5f * rp;
	b = -0.5f * rm * (2.0f * rpos + 1.0f) + 
	    2.0f * rpos * rmax + 0.5f * rp * (1.0f - 2.0f * rpos);
	c = 0.5f * rm * (rpos * rpos + rpos) +
	    rmax * (1.0f - rpos * rpos) + 0.5f * rp * (rpos * rpos - rpos);

	x = -b / (2.0f * a);
	y = a * x * x + b * x + c;
	x *= DOWN;

	rmax = y;
	rval = rmax / r[0];
	if (rval >= 0.4f || (vuv == 3 && rval >= 0.3f)) {
		*per = x;
		vuv = (vuv & 1) * 2 + 1;
	} else {
		*per = 0.0;
		vuv = (vuv & 1) * 2;
	}
}

void
lpc_init(lpcstate_t* state)
{
	int     i;
	float   r, v, w, wcT;

	for (i = 0; i < BUFLEN; i++) {
		s[i] = 0.0;
		h[i] = (float)(WSCALE * (0.54 - 0.46 *
		       cos(2 * M_PI * i / (BUFLEN - 1.0))));
	}
	wcT = (float)(2 * M_PI * FC / FS);
	r = 0.36891079f * wcT;
	v = 0.18445539f * wcT;
	w = 0.92307712f * wcT;
	fa[1] = (float)-exp(-r);
	fa[2] = 1.0f + fa[1];
	fa[3] = (float)(-2.0f * exp(-v) * cos(w));
	fa[4] = (float)exp(-2.0f * v);
	fa[5] = 1.0f + fa[3] + fa[4];

	u1 = 0.0;
	yp1 = 0.0;
	yp2 = 0.0;

	state->Oldper = 0.0;
	state->OldG = 0.0;
	for (i = 0; i <= LPC_FILTORDER; i++) {
		state->Oldk[i] = 0.0;
		state->bp[i] = 0.0;
	}
	state->pitchctr = 0;
	state->last_timestamp = 0;
}

void
lpc_analyze(const short *buf, lpcparams_t *params)
{
	int     i, j;
	float   w[MAXWINDOW], r[LPC_FILTORDER + 1];
	float   per, G, k[LPC_FILTORDER + 1];

	for (i = 0, j = BUFLEN - FRAMESIZE; i < FRAMESIZE; i++, j++) {
		s[j] = ((float)*buf) / 32768.0f;
		buf++;
		u = fa[2] * s[j] - fa[1] * u1;
		y[j] = fa[5] * u1 - fa[3] * yp1 - fa[4] * yp2;
		u1 = u;
		yp2 = yp1;
		yp1 = y[j];
	}

	calc_pitch(y, &per);

	for (i = 0; i < BUFLEN; i++)
		w[i] = s[i] * h[i];
	auto_correl(w, BUFLEN, LPC_FILTORDER, r);
	durbin(r, LPC_FILTORDER, k, &G);

	params->period = (unsigned short)(per * (1 << 8));
	params->gain   = (unsigned char) (G * (1 << 8));
	for (i = 0; i < LPC_FILTORDER; i++)
		params->k[i] = (unsigned char)(k[i + 1] * (1 << 7));
	memcpy(s, s + FRAMESIZE, (BUFLEN - FRAMESIZE) * sizeof(s[0]));
	memcpy(y, y + FRAMESIZE, (BUFLEN - FRAMESIZE) * sizeof(y[0]));
}

double drand48(void);
 
void
lpc_synthesize(short *buf, lpcparams_t *params, lpcstate_t* state)
{
	int i, j, op;
	register double u, f, per, G, NewG, Ginc, Newper, perinc;
	double k[LPC_FILTORDER + 1], Newk[LPC_FILTORDER + 1],
	       kinc[LPC_FILTORDER + 1];

	per = (double) params->period / 256.;
	G = (double) params->gain / 256.;
	k[0] = 0.0;
	for (i = 0; i < LPC_FILTORDER; i++)
		k[i + 1] = (double) (params->k[i]) / 128.;

	G /= sqrt(BUFLEN / (per == 0.0? 3.0 : per));
	Newper = state->Oldper;
	NewG = state->OldG;
	for (i = 1; i <= LPC_FILTORDER; i++)
		Newk[i] = state->Oldk[i];

	if (state->Oldper != 0 && per != 0) {
		perinc = (per - state->Oldper) / (double)FRAMESIZE;
		Ginc = (G - state->OldG) / (double)FRAMESIZE;
		for (i = 1; i <= LPC_FILTORDER; i++)
			kinc[i] = (k[i] - state->Oldk[i]) / (double)FRAMESIZE;
	} else {
		perinc = 0.0;
		Ginc = 0.0;
		for (i = 1; i <= LPC_FILTORDER; i++)
			kinc[i] = 0.0;
	}

	if (Newper == 0)
		state->pitchctr = 0;

	for (i = 0; i < FRAMESIZE; i++) {
		if (Newper == 0) {
			u = drand48() * NewG;
		} else {
			if (state->pitchctr == 0) {
				u = NewG;
				state->pitchctr = (int) Newper;
			} else {
				u = 0.0;
				state->pitchctr--;
			}
		}

		f = u;
		for (j = LPC_FILTORDER; j >= 1; j--) {
			register double b = state->bp[j - 1];
			register double kj = Newk[j];
			Newk[j] = kj + kinc[j];
			f -= b * kj;
			b += f * kj;
			state->bp[j] = b;
		}
		state->bp[0] = f;
		
		op = (int)(f * 32767.0);
		if (op > 32767) {
			op = 32767; 
		} else if (op < -32767) {
			op = -32767;
		}
		*buf++ = (short)op;

		Newper += perinc;
		NewG += Ginc;
	}
	state->Oldper = per;
	state->OldG = G;
	for (i = 1; i <= LPC_FILTORDER; i++)
		state->Oldk[i] = k[i];

}

/* this routine does non-interpolating lpc synthesis */
/* for the state passed [oth]                        */
void
lpc_extend_synthesize(short *buf, int len, lpcstate_t* s)
{
	int i, j, op;
        register double u, f;
        
	if (s->Oldper == 0) {
		s->pitchctr = 0;
        }
        
	for (i = 0; i < len; i++) {
		if (s->Oldper == 0) {
			u = drand48() * s->OldG;
		} else {
			if (s->pitchctr == 0) {
				u           = s->OldG;
				s->pitchctr = (int) s->Oldper;
			} else {
				u = 0.0;
				s->pitchctr--;
			}
		}

		f = u;
		for (j = LPC_FILTORDER; j >= 1; j--) {
			register double b  = s->bp[j - 1];
			register double kj = s->Oldk[j];
			f -= b * kj;
			b += f * kj;
			s->bp[j] = b;
		}
		s->bp[0] = f;
		
		op = (int)(f * 32767.0);
		if (op > 32767) {
			op = 32767; 
		} else if (op < -32768) {
			op = -32768;
		}
		*buf++ = (short)op;
	}
}
