/*
 * downmix.c
 * Copyright (C) 2000-2002 Michel Lespinasse <walken@zoy.org>
 * Copyright (C) 1999-2000 Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *
 * This file is part of a52dec, a free ATSC A-52 stream decoder.
 * See http://liba52.sourceforge.net/ for updates.
 *
 * a52dec is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * a52dec 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <string.h>
#include <inttypes.h>

#include "a52.h"
#include "a52_internal.h"

#define CONVERT(acmod,output) (((output) << 3) + (acmod))

sample_i idivxi(sample_i a, sample_i b)
{
	int64_t c = a;
	c <<= XISHIFT;
	c /= b;

	return (sample_i) c;
}

int a52_downmix_init (int input, int flags, sample_i* level,
			  sample_i clev, sample_i slev)
{
	static const uint8_t table[11][8] = {
	{A52_CHANNEL,   A52_DOLBY,  A52_STEREO,	A52_STEREO,
	 A52_STEREO,    A52_STEREO, A52_STEREO,	A52_STEREO},
	{A52_MONO,      A52_MONO,   A52_MONO,	A52_MONO,
	 A52_MONO,      A52_MONO,   A52_MONO,	A52_MONO},
	{A52_CHANNEL,   A52_DOLBY,  A52_STEREO,	A52_STEREO,
	 A52_STEREO,    A52_STEREO, A52_STEREO,	A52_STEREO},
	{A52_CHANNEL,   A52_DOLBY,  A52_STEREO,	A52_3F,
	 A52_STEREO,    A52_3F,     A52_STEREO,	A52_3F},
	{A52_CHANNEL,   A52_DOLBY,  A52_STEREO,	A52_STEREO,
	 A52_2F1R,      A52_2F1R,	A52_2F1R,	A52_2F1R},
	{A52_CHANNEL,   A52_DOLBY,	A52_STEREO,	A52_STEREO,
	 A52_2F1R,      A52_3F1R,	A52_2F1R,	A52_3F1R},
	{A52_CHANNEL,   A52_DOLBY,	A52_STEREO,	A52_3F,
	 A52_2F2R,      A52_2F2R,	A52_2F2R,	A52_2F2R},
	{A52_CHANNEL,   A52_DOLBY,	A52_STEREO,	A52_3F,
	 A52_2F2R,      A52_3F2R,	A52_2F2R,	A52_3F2R},
	{A52_CHANNEL1,  A52_MONO,	A52_MONO,	A52_MONO,
	 A52_MONO,      A52_MONO,	A52_MONO,	A52_MONO},
	{A52_CHANNEL2,  A52_MONO,	A52_MONO,	A52_MONO,
	 A52_MONO,      A52_MONO,	A52_MONO,	A52_MONO},
	{A52_CHANNEL,   A52_DOLBY,	A52_STEREO,	A52_DOLBY,
	 A52_DOLBY,     A52_DOLBY,	A52_DOLBY,	A52_DOLBY}
	};
	int output;

	output = flags & A52_CHANNEL_MASK;
	if (output > A52_DOLBY)
		return -1;

	output = table[output][input & 7];

	if ((output == A52_STEREO) &&
	((input == A52_DOLBY) || ((input == A52_3F) && (clev == LEVEL_3DB))))
		output = A52_DOLBY;

	if (flags & A52_ADJUST_LEVEL)
		switch (CONVERT (input & 7, output))
		{
			case CONVERT (A52_3F, A52_MONO):
				*level = idivxi(imulxi(*level, LEVEL_3DB), XIUNIT + clev);
			break;
			case CONVERT (A52_STEREO, A52_MONO):
			case CONVERT (A52_2F2R, A52_2F1R):
			case CONVERT (A52_3F2R, A52_3F1R):
			level_3db:
				*level = imulxi(*level, LEVEL_3DB);
			break;
			case CONVERT (A52_3F2R, A52_2F1R):
				if (clev < LEVEL_PLUS3DB - XIUNIT)
					goto level_3db;
				/* break thru */
			case CONVERT (A52_3F, A52_STEREO):
			case CONVERT (A52_3F1R, A52_2F1R):
			case CONVERT (A52_3F1R, A52_2F2R):
			case CONVERT (A52_3F2R, A52_2F2R):
				*level = idivxi(*level, XIUNIT + clev);
			break;
			case CONVERT (A52_2F1R, A52_MONO):
				*level = idivxi(imulxi(*level, LEVEL_PLUS3DB)
				               , (XIUNIT<<1) + slev);
			break;
			case CONVERT (A52_2F1R, A52_STEREO):
			case CONVERT (A52_3F1R, A52_3F):
				*level = idivxi(*level
								, XIUNIT + imulxi(slev, LEVEL_3DB));
			break;
			case CONVERT (A52_3F1R, A52_MONO):
				*level = idivxi(imulxi(*level, LEVEL_3DB)
								, XIUNIT + clev + (slev>>1));
			break;
			case CONVERT (A52_3F1R, A52_STEREO):
				*level = idivxi(*level
								, XIUNIT + clev + imulxi(slev, LEVEL_3DB));
			break;
			case CONVERT (A52_2F2R, A52_MONO):
				*level = idivxi(imulxi(*level, LEVEL_3DB)
								, XIUNIT + slev);
			break;
			case CONVERT (A52_2F2R, A52_STEREO):
			case CONVERT (A52_3F2R, A52_3F):
				*level = idivxi(*level
								, XIUNIT + slev);
			break;
			case CONVERT (A52_3F2R, A52_MONO):
				*level = idivxi(imulxi(*level, LEVEL_3DB)
								, XIUNIT + clev + slev);
			break;
			case CONVERT (A52_3F2R, A52_STEREO):
				*level = idivxi(*level
								, XIUNIT + clev + slev);
			break;
			case CONVERT (A52_MONO, A52_DOLBY):
				*level = imulxi(*level, LEVEL_PLUS3DB);
			break;
			case CONVERT (A52_3F, A52_DOLBY):
			case CONVERT (A52_2F1R, A52_DOLBY):
				*level = idivxi(*level
								, XIUNIT + LEVEL_3DB);
			break;
			case CONVERT (A52_3F1R, A52_DOLBY):
			case CONVERT (A52_2F2R, A52_DOLBY):
				*level = idivxi(*level
								, XIUNIT + (LEVEL_3DB<<1));
			break;
			case CONVERT (A52_3F2R, A52_DOLBY):
				*level = idivxi(*level
								, XIUNIT + 3 * LEVEL_3DB);
			break;
		}

	return output;
}

int a52_downmix_coeff (sample_i* coeff, int acmod, int output, sample_i level,
			   sample_i clev, sample_i slev)
{
	switch (CONVERT (acmod, output & A52_CHANNEL_MASK))
	{
		case CONVERT (A52_CHANNEL, A52_CHANNEL):
		case CONVERT (A52_MONO, A52_MONO):
		case CONVERT (A52_STEREO, A52_STEREO):
		case CONVERT (A52_3F, A52_3F):
		case CONVERT (A52_2F1R, A52_2F1R):
		case CONVERT (A52_3F1R, A52_3F1R):
		case CONVERT (A52_2F2R, A52_2F2R):
		case CONVERT (A52_3F2R, A52_3F2R):
		case CONVERT (A52_STEREO, A52_DOLBY):
			coeff[0] = coeff[1] = coeff[2] = coeff[3] = coeff[4] = level;
		return 0;
		case CONVERT (A52_CHANNEL, A52_MONO):
			coeff[0] = coeff[1] = level >> 1;
		return 3;
		case CONVERT (A52_STEREO, A52_MONO):
			coeff[0] = coeff[1] = imulxi(level, LEVEL_3DB);
		return 3;
		case CONVERT (A52_3F, A52_MONO):
			coeff[0] = coeff[2] = imulxi(level, LEVEL_3DB);
			coeff[1] = imulxi(level, imulxi(clev, LEVEL_PLUS3DB));
		return 7;
		case CONVERT (A52_2F1R, A52_MONO):
			coeff[0] = coeff[1] = imulxi(level, LEVEL_3DB);
			coeff[2] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 7;
		case CONVERT (A52_2F2R, A52_MONO):
			coeff[0] = coeff[1] = imulxi(level, LEVEL_3DB);
			coeff[2] = coeff[3] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 15;
		case CONVERT (A52_3F1R, A52_MONO):
			coeff[0] = coeff[2] = imulxi(level, LEVEL_3DB);
			coeff[1] = imulxi(level, imulxi(clev, LEVEL_PLUS3DB));
			coeff[3] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 15;
		case CONVERT (A52_3F2R, A52_MONO):
			coeff[0] = coeff[2] = imulxi(level, LEVEL_3DB);
			coeff[1] = imulxi(level, imulxi(clev, LEVEL_PLUS3DB));
			coeff[3] = coeff[4] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 31;
		case CONVERT (A52_MONO, A52_DOLBY):
			coeff[0] = imulxi(level, LEVEL_3DB);
		return 0;
		case CONVERT (A52_3F, A52_DOLBY):
			clev = LEVEL_3DB;
		case CONVERT (A52_3F, A52_STEREO):
		case CONVERT (A52_3F1R, A52_2F1R):
		case CONVERT (A52_3F2R, A52_2F2R):
			coeff[0] = coeff[2] = coeff[3] = coeff[4] = level;
			coeff[1] = imulxi(level, clev);
		return 7;
		case CONVERT (A52_2F1R, A52_DOLBY):
			slev = XIUNIT;
		case CONVERT (A52_2F1R, A52_STEREO):
			coeff[0] = coeff[1] = level;
			coeff[2] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 7;
		case CONVERT (A52_3F1R, A52_DOLBY):
			clev = LEVEL_3DB;
			slev = XIUNIT;
		case CONVERT (A52_3F1R, A52_STEREO):
			coeff[0] = coeff[2] = level;
			coeff[1] = imulxi(level, clev);
			coeff[3] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 15;
		case CONVERT (A52_2F2R, A52_DOLBY):
			slev = LEVEL_3DB;
		case CONVERT (A52_2F2R, A52_STEREO):
			coeff[0] = coeff[1] = level;
			coeff[2] = coeff[3] = imulxi(level, slev);
		return 15;
		case CONVERT (A52_3F2R, A52_DOLBY):
			clev = LEVEL_3DB;
		case CONVERT (A52_3F2R, A52_2F1R):
			slev = LEVEL_3DB;
		case CONVERT (A52_3F2R, A52_STEREO):
			coeff[0] = coeff[2] = level;
			coeff[1] = imulxi(level, clev);
			coeff[3] = coeff[4] = imulxi(level, slev);
		return 31;
		case CONVERT (A52_3F1R, A52_3F):
			coeff[0] = coeff[1] = coeff[2] = level;
			coeff[3] = imulxi(level, imulxi(slev, LEVEL_3DB));
		return 13;
		case CONVERT (A52_3F2R, A52_3F):
			coeff[0] = coeff[1] = coeff[2] = level;
			coeff[3] = coeff[4] = imulxi(level, slev);
		return 29;
		case CONVERT (A52_2F2R, A52_2F1R):
			coeff[0] = coeff[1] = level;
			coeff[2] = coeff[3] = imulxi(level, LEVEL_3DB);
		return 12;
		case CONVERT (A52_3F2R, A52_3F1R):
			coeff[0] = coeff[1] = coeff[2] = level;
			coeff[3] = coeff[4] = imulxi(level, LEVEL_3DB);
		return 24;
		case CONVERT (A52_2F1R, A52_2F2R):
			coeff[0] = coeff[1] = level;
			coeff[2] = imulxi(level, LEVEL_3DB);
		return 0;
		case CONVERT (A52_3F1R, A52_2F2R):
			coeff[0] = coeff[2] = level;
			coeff[1] = imulxi(level, clev);
			coeff[3] = imulxi(level, LEVEL_3DB);
		return 7;
		case CONVERT (A52_3F1R, A52_3F2R):
			coeff[0] = coeff[1] = coeff[2] = level;
			coeff[3] = imulxi(level, LEVEL_3DB);
		return 0;
		case CONVERT (A52_CHANNEL, A52_CHANNEL1):
			coeff[0] = level;
			coeff[1] = 0;
		return 0;
		case CONVERT (A52_CHANNEL, A52_CHANNEL2):
			coeff[0] = 0;
			coeff[1] = level;
		return 0;
	}

	return -1;	/* NOTREACHED */
}

static void mix2to1 (sample_i * dest, sample_i * src)
{
	int i;

	for (i = 0; i < 256; i++)
		dest[i] += src[i];
}

static void mix3to1 (sample_i * samples)
{
	int i;

	for (i = 0; i < 256; i++)
		samples[i] += samples[i + 256] + samples[i + 512];
}

static void mix4to1 (sample_i * samples)
{
	int i;

	for (i = 0; i < 256; i++)
		samples[i] += (samples[i + 256] + samples[i + 512] +
			   samples[i + 768]);
}

static void mix5to1 (sample_i * samples)
{
	int i;

	for (i = 0; i < 256; i++)
		samples[i] += (samples[i + 256] + samples[i + 512] +
			   samples[i + 768] + samples[i + 1024]);
}

static void mix3to2 (sample_i * samples)
{
	int i;
	sample_i common;

	for (i = 0; i < 256; i++)
	{
		common = samples[i + 256];
		samples[i] += common;
		samples[i + 256] = samples[i + 512] + common;
	}
}

static void mix21to2 (sample_i * left, sample_i * right)
{
	int i;
	sample_i common;

	for (i = 0; i < 256; i++)
	{
		common = right[i + 256];
		left[i] += common;
		right[i] += common;
	}
}

static void mix21toS (sample_i * samples)
{
	int i;
	sample_i surround;

	for (i = 0; i < 256; i++)
	{
		surround = samples[i + 512];
		samples[i] -=  surround;
		samples[i + 256] += surround;
	}
}

static void mix31to2 (sample_i * samples)
{
	int i;
	sample_i common;

	for (i = 0; i < 256; i++)
	{
		common = samples[i + 256] + samples[i + 768];
		samples[i] += common;
		samples[i + 256] = samples[i + 512] + common;
	}
}

static void mix31toS (sample_i * samples)
{
	int i;
	sample_i common, surround;

	for (i = 0; i < 256; i++)
	{
		common = samples[i + 256];
		surround = samples[i + 768];
		samples[i] += common - surround;
		samples[i + 256] = samples[i + 512] + common + surround;
	}
}

static void mix22toS (sample_i * samples)
{
	int i;
	sample_i surround;

	for (i = 0; i < 256; i++)
	{
		surround = samples[i + 512] + samples[i + 768];
		samples[i] -= surround;
		samples[i + 256] += surround;
	}
}

static void mix32to2 (sample_i * samples)
{
	int i;
	sample_i common;

	for (i = 0; i < 256; i++)
	{
		common = samples[i + 256];
		samples[i] += common + samples[i + 768];
		samples[i + 256] = common + samples[i + 512] + samples[i + 1024];
	}
}

static void mix32toS (sample_i * samples)
{
	int i;
	sample_i common, surround;

	for (i = 0; i < 256; i++)
	{
		common = samples[i + 256];
		surround = samples[i + 768] + samples[i + 1024];
		samples[i] += common - surround;
		samples[i + 256] = samples[i + 512] + common + surround;
	}
}

static void move2to1 (sample_i * src, sample_i * dest)
{
	int i;

	for (i = 0; i < 256; i++)
		dest[i] = src[i] + src[i + 256];
}

static void zero (sample_i * samples)
{
	int i;

	for (i = 0; i < 256; i++)
		samples[i] = 0;
}

void a52_downmix (sample_i * samples, int acmod, int output, int slev)
{
	switch (CONVERT (acmod, output & A52_CHANNEL_MASK))
	{
		case CONVERT (A52_CHANNEL, A52_CHANNEL2):
			memcpy (samples, samples + 256, 256 * sizeof (sample_i));
		break;
		case CONVERT (A52_CHANNEL, A52_MONO):
		case CONVERT (A52_STEREO, A52_MONO):
		mix_2to1:
			mix2to1 (samples, samples + 256);
		break;
		case CONVERT (A52_2F1R, A52_MONO):
			if (slev == 0)
				goto mix_2to1;
		case CONVERT (A52_3F, A52_MONO):
		mix_3to1:
			mix3to1 (samples);
		break;
		case CONVERT (A52_3F1R, A52_MONO):
			if (slev == 0)
				goto mix_3to1;
		case CONVERT (A52_2F2R, A52_MONO):
			if (slev == 0)
				goto mix_2to1;
			mix4to1 (samples);
		break;
		case CONVERT (A52_3F2R, A52_MONO):
			if (slev == 0)
				goto mix_3to1;
			mix5to1 (samples);
		break;
		case CONVERT (A52_MONO, A52_DOLBY):
			memcpy (samples + 256, samples, 256 * sizeof (sample_i));
		break;
		case CONVERT (A52_3F, A52_STEREO):
		case CONVERT (A52_3F, A52_DOLBY):
		mix_3to2:
			mix3to2 (samples);
		break;
		case CONVERT (A52_2F1R, A52_STEREO):
			if (slev == 0)
				break;
			mix21to2 (samples, samples + 256);
		break;
		case CONVERT (A52_2F1R, A52_DOLBY):
			mix21toS (samples);
		break;
		case CONVERT (A52_3F1R, A52_STEREO):
			if (slev == 0)
				goto mix_3to2;
			mix31to2 (samples);
		break;
		case CONVERT (A52_3F1R, A52_DOLBY):
			mix31toS (samples);
		break;
		case CONVERT (A52_2F2R, A52_STEREO):
			if (slev == 0)
				break;
			mix2to1 (samples, samples + 512);
			mix2to1 (samples + 256, samples + 768);
		break;
		case CONVERT (A52_2F2R, A52_DOLBY):
			mix22toS (samples);
		break;
		case CONVERT (A52_3F2R, A52_STEREO):
			if (slev == 0)
				goto mix_3to2;
			mix32to2 (samples);
		break;
		case CONVERT (A52_3F2R, A52_DOLBY):
			mix32toS (samples);
		break;
		case CONVERT (A52_3F1R, A52_3F):
			if (slev == 0)
				break;
			mix21to2 (samples, samples + 512);
		break;
		case CONVERT (A52_3F2R, A52_3F):
			if (slev == 0)
				break;
			mix2to1 (samples, samples + 768);
			mix2to1 (samples + 512, samples + 1024);
		break;
		case CONVERT (A52_3F1R, A52_2F1R):
			mix3to2 (samples);
			memcpy (samples + 512, samples + 768, 256 * sizeof (sample_i));
		break;
		case CONVERT (A52_2F2R, A52_2F1R):
			mix2to1 (samples + 512, samples + 768);
		break;
		case CONVERT (A52_3F2R, A52_2F1R):
			mix3to2 (samples);
			move2to1 (samples + 768, samples + 512);
		break;
		case CONVERT (A52_3F2R, A52_3F1R):
			mix2to1 (samples + 768, samples + 1024);
		break;
		case CONVERT (A52_2F1R, A52_2F2R):
			memcpy (samples + 768, samples + 512, 256 * sizeof (sample_i));
		break;
		case CONVERT (A52_3F1R, A52_2F2R):
			mix3to2 (samples);
			memcpy (samples + 512, samples + 768, 256 * sizeof (sample_i));
		break;
		case CONVERT (A52_3F2R, A52_2F2R):
			mix3to2 (samples);
			memcpy (samples + 512, samples + 768, 256 * sizeof (sample_i));
			memcpy (samples + 768, samples + 1024, 256 * sizeof (sample_i));
		break;
		case CONVERT (A52_3F1R, A52_3F2R):
			memcpy (samples + 1027, samples + 768, 256 * sizeof (sample_i));
		break;
	}
}

void a52_upmix (sample_i * samples, int acmod, int output)
{
	switch (CONVERT (acmod, output & A52_CHANNEL_MASK))
	{
		case CONVERT (A52_CHANNEL, A52_CHANNEL2):
			memcpy (samples + 256, samples, 256 * sizeof (sample_i));
		break;

		case CONVERT (A52_3F2R, A52_MONO):
			zero (samples + 1024);
		case CONVERT (A52_3F1R, A52_MONO):
		case CONVERT (A52_2F2R, A52_MONO):
			zero (samples + 768);
		case CONVERT (A52_3F, A52_MONO):
		case CONVERT (A52_2F1R, A52_MONO):
			zero (samples + 512);
		case CONVERT (A52_CHANNEL, A52_MONO):
		case CONVERT (A52_STEREO, A52_MONO):
			zero (samples + 256);
		break;

		case CONVERT (A52_3F2R, A52_STEREO):
		case CONVERT (A52_3F2R, A52_DOLBY):
			zero (samples + 1024);
		case CONVERT (A52_3F1R, A52_STEREO):
		case CONVERT (A52_3F1R, A52_DOLBY):
			zero (samples + 768);
		case CONVERT (A52_3F, A52_STEREO):
		case CONVERT (A52_3F, A52_DOLBY):
		mix_3to2:
			memcpy (samples + 512, samples + 256, 256 * sizeof (sample_i));
			zero (samples + 256);
		break;

		case CONVERT (A52_2F2R, A52_STEREO):
		case CONVERT (A52_2F2R, A52_DOLBY):
			zero (samples + 768);
		case CONVERT (A52_2F1R, A52_STEREO):
		case CONVERT (A52_2F1R, A52_DOLBY):
			zero (samples + 512);
		break;

		case CONVERT (A52_3F2R, A52_3F):
			zero (samples + 1024);
		case CONVERT (A52_3F1R, A52_3F):
		case CONVERT (A52_2F2R, A52_2F1R):
			zero (samples + 768);
		break;

		case CONVERT (A52_3F2R, A52_3F1R):
			zero (samples + 1024);
		break;

		case CONVERT (A52_3F2R, A52_2F1R):
			zero (samples + 1024);
		case CONVERT (A52_3F1R, A52_2F1R):
		mix_31to21:
			memcpy (samples + 768, samples + 512, 256 * sizeof (sample_i));
		goto mix_3to2;

		case CONVERT (A52_3F2R, A52_2F2R):
			memcpy (samples + 1024, samples + 768, 256 * sizeof (sample_i));
		goto mix_31to21;
	}
}
