/*
	author: unknown, probably bitmaster?
	slightly modified by dbalster
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "common.h"
#include "xadecode.h"

#if USE_FXD
static FXD      K0[4] = {
	0x00000000,
	0x0000F000,
	0x0001CC00,
	0x00018800
};
static FXD      K1[4] = {
	0x00000000,
	0x00000000,
	0xFFFF3000,
	0xFFFF2400
};
FXD t1, t2, at1[256], at2[256];
FXD t1_x, t2_x, at1_x[256], at2_x[256];
#else
static double   K0[4] = {
	0.0,
	0.9375,
	1.796875,
	1.53125
};
static double   K1[4] = {
	0.0,
	0.0,
	-0.8125,
	-0.859375
};
double t1, t2, at1[256], at2[256];
double t1_x, t2_x, at1_x[256], at2_x[256];
#endif

void initXaDecode(void)
{
	int i;
	
	for (i=0; i<256; ++i)
	{
		at1[i] = at2[i] = at1_x[i] = at2_x[i] = 0;
	}
}
void reinitXaDecode(int i)
{
	at1[i] = at2[i] = at1_x[i] = at2_x[i] = 0;
}
void switchXaDecode(int i)
{
	t1 = at1[i];
	t2 = at2[i];
	t1_x = at1_x[i];
	t2_x = at2_x[i];
}
void saveXaDecode(int i)
{
	at1[i]	=	t1;
	at2[i]	=	t2;
	at1_x[i]=	t1_x;
	at2_x[i]=	t2_x;
}

char xachannel(SoundSector *ss)
{
	return(ss->sectorFiller[XAChannel]);
}

unsigned char xatype(SoundSector *ss)
{
	return(unsigned char) (ss->sectorFiller[XAType]);
}

char xafileno(SoundSector *ss)
{
	return(ss->sectorFiller[XAFile]);
}

char xastereo(SoundSector *ss)
{
	return(ss->sectorFiller[XAFlags]&XAFStereo);
}

char xahalfhz(SoundSector *ss)
{
	return(ss->sectorFiller[XAFlags]&XAFHalfHz);
}

long convXaToWave(char *adp, char *wav, int cn, int fn_s, int fn_e)
{
	SoundSector ssct;
	int i;

	memcpy(ssct.sectorFiller,adp,sizeof(ssct.sectorFiller));
	for(i=0;i<18;i++)
		memcpy(ssct.SoundGroups[i],adp+sizeof(ssct.sectorFiller)+(128*i),128);
	if ((xachannel(&ssct) == cn) && (xatype(&ssct) == XAAUDIO))
	{
		if (xafileno(&ssct) >= fn_s
				&& xafileno(&ssct) <= fn_e)
		{
			if (xastereo(&ssct))
				return(decodeSoundSect1(&ssct, wav));
			else
				return(decodeSoundSect(&ssct, wav));
		}
	}
	return(0);
}

long decodeSoundSect(SoundSector *ssct, char *wav)
{
	long count, outputBytes;
	signed char snddat, filt, range;
	short decoded;
	long unit, sample;
	long sndgrp;
#if USE_FXD
	FXD tmp2, tmp3, tmp4, tmp5;
#else
	double tmp2, tmp3, tmp4, tmp5;
#endif
	
	outputBytes = 0;

	for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
	{
		count = 0;
		for (unit = 0; unit < 8; unit++)
		{
			range = getRange(ssct->SoundGroups[sndgrp], unit);
			filt = getFilter(ssct->SoundGroups[sndgrp], unit);
			for (sample = 0; sample < 28; sample++)
			{
				snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
#if USE_FXD
				tmp2 = (long)(snddat) << (12 - range);
				tmp3 = FXD_Pcm16ToFxd(tmp2);
				tmp4 = FXD_FixMul(K0[filt], t1);
				tmp5 = FXD_FixMul(K1[filt], t2);
				t2 = t1;
				t1 = tmp3 + tmp4 + tmp5;
				decoded = FXD_FxdToPcm16(t1);
#else
				tmp2 = (double)(1 << (12 - range));
				tmp3 = (double)snddat * tmp2;
				tmp4 = t1 * K0[filt];
				tmp5 = t2 * K1[filt];
				t2 = t1;
				t1 = tmp3 + tmp4 + tmp5;
				decoded = DblToPCM(t1);
#endif
				wav[outputBytes+count++] = (char)(decoded & 0x0000ffff);
				wav[outputBytes+count++] = (char)(decoded >> 8);
			}
		}
		outputBytes += count;
	}
	return outputBytes;
}

long decodeSoundSect1(SoundSector *ssct, char *wav)
{
	long count, outputBytes;
	signed char snddat, filt, range;
	signed char filt1, range1;
	short decoded;
	long unit, sample;
	long sndgrp;
#if USE_FXD
	FXD tmp2, tmp3, tmp4, tmp5;
#else
	double tmp2, tmp3, tmp4, tmp5;
#endif

	outputBytes = 0;

	for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
	{
		count = 0;
		for (unit = 0; unit < 8; unit+= 2)
		{
			range = getRange(ssct->SoundGroups[sndgrp], unit);
			filt = getFilter(ssct->SoundGroups[sndgrp], unit);
			range1 = getRange(ssct->SoundGroups[sndgrp], unit+1);
			filt1 = getFilter(ssct->SoundGroups[sndgrp], unit+1);

			for (sample = 0; sample < 28; sample++)
			{
				/* Channel 1 */
				snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
#if USE_FXD
				tmp2 = (long)(snddat) << (12 - range);
				tmp3 = FXD_Pcm16ToFxd(tmp2);
				tmp4 = FXD_FixMul(K0[filt], t1);
				tmp5 = FXD_FixMul(K1[filt], t2);
				t2 = t1;
				t1 = tmp3 + tmp4 + tmp5;
				decoded = FXD_FxdToPcm16(t1);
#else
				tmp2 = (double)(1 << (12 - range));
				tmp3 = (double)snddat * tmp2;
				tmp4 = t1 * K0[filt];
				tmp5 = t2 * K1[filt];
				t2 = t1;
				t1 = tmp3 + tmp4 + tmp5;
				decoded = DblToPCM(t1);
#endif
				wav[outputBytes + count++] = (char)(decoded & 0x0000ffff);
				wav[outputBytes + count++] = (char)(decoded >> 8);

				/* Channel 2 */
				snddat = getSoundData(ssct->SoundGroups[sndgrp], unit+1, sample);
#if USE_FXD
				tmp2 = (long)(snddat) << (12 - range1);
				tmp3 = FXD_Pcm16ToFxd(tmp2);
				tmp4 = FXD_FixMul(K0[filt1], t1_x);
				tmp5 = FXD_FixMul(K1[filt1], t2_x);
				t2_x = t1_x;
				t1_x = tmp3 + tmp4 + tmp5;
				decoded = FXD_FxdToPcm16(t1_x);
#else
				tmp2 = (double)(1 << (12 - range1));
				tmp3 = (double)snddat * tmp2;
				tmp4 = t1_x * K0[filt1];
				tmp5 = t2_x * K1[filt1];
				t2_x = t1_x;
				t1_x = tmp3 + tmp4 + tmp5;
				decoded = DblToPCM(t1_x);
#endif
				wav[outputBytes + count++] = (char)(decoded & 0x0000ffff);
				wav[outputBytes + count++] = (char)(decoded >> 8);
			}
		}
		outputBytes += count;
	}
	return outputBytes;
}

signed char getSoundData(char *buf, long unit, long sample)
{
	signed char ret;
	char *p;
	long offset, shift;

	p = buf;
	shift = (unit%2) * 4;

	offset = 16 + (unit / 2) + (sample * 4);
	p += offset;

	ret = (*p >> shift) & 0x0F;

	if (ret > 7) {
		ret -= 16;
	}
	return ret;
}

signed char getFilter(char *buf, long unit)
{
	return (*(buf + 4 + unit) >> 4) & 0x03;
}


signed char getRange(char *buf, long unit)
{
	return *(buf + 4 + unit) & 0x0F;
}

#if USE_FXD
FXD FXD_FixMul(FXD a, FXD b)
{
	long                high_a, low_a, high_b, low_b;
	long                hahb, halb, lahb;
	unsigned long       lalb;
	FXD                 ret;

	high_a = a >> 16;
	low_a = a & 0x0000FFFF;
	high_b = b >> 16;
	low_b = b & 0x0000FFFF;

	hahb = (high_a * high_b) << 16;
	halb = high_a * low_b;
	lahb = low_a * high_b;
	lalb = (unsigned long)(low_a * low_b) >> 16;

	ret = hahb + halb + lahb + lalb;

	return ret;
}
#endif