/*
 * Copyright (c) 1991-1994 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted 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 Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS 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 REGENTS 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.
 */
static const char rcsid[] =
    "@(#) $Header: lpc.c,v 1.8 96/04/23 00:23:03 van Exp $ (LBL)";
#include "config.h"
#ifdef WIN32
#include <winsock.h>
#define inline
#endif
#include <math.h>
#include "lpc.h"
#include "mulaw.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


inline 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.0f;
		for (i = 0; i < nk; i++)
			r[k] += w[i] * w[i + k];
	}
}

inline 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.0f;

	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);
}

inline 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];
	}
}

inline 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 + 1, r);
	rpos = 0;
	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.5 * rm - rmax + 0.5 * rp;
	b = -0.5 * rm * (2.0 * rpos + 1.0) + 
	    2.0 * rpos * rmax + 0.5 * rp * (1.0 - 2.0 * rpos);
	c = 0.5 * rm * (rpos * rpos + rpos) +
	    rmax * (1.0 - rpos * rpos) + 0.5 * rp * (rpos * rpos - rpos);

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

	rmax = y;
	rval = rmax / r[0];
	if (rval >= 0.4 || (vuv == 3 && rval >= 0.3)) {
		*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] = WSCALE * (0.54 - 0.46 *
		       cos(2 * M_PI * i / (BUFLEN - 1.0)));
	}
	wcT = 2 * M_PI * FC / FS;
	r = 0.36891079f * wcT;
	v = 0.18445539f * wcT;
	w = 0.92307712f * wcT;
	fa[1] = -exp(-r);
	fa[2] = 1.0 + fa[1];
	fa[3] = -2.0 * exp(-v) * cos(w);
	fa[4] = exp(-2.0 * v);
	fa[5] = 1.0 + fa[3] + fa[4];

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

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

void
lpc_analyze(const unsigned char *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)(mulawtolin[*buf++]) / 32768.;
		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 = (u_short)(per * 256.f);
	params->gain = (u_short)(G * 256.f);
	for (i = 0; i < LPC_FILTORDER; i++)
		params->k[i] = (char)(k[i + 1] * 128.f);

	memcpy(s, s + FRAMESIZE, (BUFLEN - FRAMESIZE) * sizeof(s[0]));
	memcpy(y, y + FRAMESIZE, (BUFLEN - FRAMESIZE) * sizeof(y[0]));
}

void
lpc_synthesize(unsigned char *buf, lpcparams_t *params, lpcstate_t* state)
{
	int i, j;
	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 = ((double)random() / 2147483648.0) * 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;

		*buf++ = lintomulaw[(int)(f * 32768.0) & 0xffff];

		Newper += perinc;
		NewG += Ginc;
	}

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