/* 
 *	Copyright (c) 2000 by WTC
 *
 *  This file is part of WAVEFS44, a free sound sampling rate converter
 *	
 *  WAVEFS44 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, or (at your option)
 *  any later version.
 *   
 *  WAVEFS44 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include "global.h"
#include <math.h>

#include "wavefs44.h"
#include "conf.h"
#include "norm.h"



static char l_szBuffer[_MAX_PATH];


#define M_PI		3.1415926535897932384626433832795
#define ISRM		147
#define OSRM		160
#define WINSIZ		16384
#define MAXFIRODR	65535
#define ALPHA		10.0

typedef struct {
	short l;
	short r;
}	SmplData;

static int firodr[5] = {0, 4095, 12287, 24575, 32767};
static int lpfcof[5] = {0, 19400, 21200, 21400, 21600};
static unsigned char FQ44100[6] = {0x44, 0xac, 0x00, 0x00, 0x10, 0xb1};

static int sptr, sptrr, eptr, eptrr, ismd, ismr, winpos, iptr, firodrv;
static double hfir[MAXFIRODR+1];
static double suml, sumr, frqc, frqs, regv, wfnc, divisor, hgain;
static SmplData smpld[WINSIZ*2], smplo, *spp;

static double bessel0(double);
static void DownKernel(FILE *file);

__forceinline short SaturateRound(double flt)
{
	int tmp;

	__asm
	{
		fld		[flt]
		fistp	[tmp]
	}

	return (short)((tmp<-32768) ? -32768 : ((tmp>32767) ? 32767 : tmp));
}

void InitialSRC()
{
	int i;

	frqs = 44100*OSRM;					// virtual high sampling rate
	frqc = lpfcof[g_Ac3Context.SRC_Flag];			// cutoff freq.
	firodrv = firodr[g_Ac3Context.SRC_Flag];			// FIR order = firodrv*2+1

	divisor = bessel0(ALPHA);
	hgain = (2.0*ISRM*frqc)/frqs;
	hfir[0] = hgain;

	for (i=1; i<=firodrv; i++)
	{
		regv = (double)(i*i)/(firodrv*firodrv);
		wfnc = bessel0(sqrt(1.0-regv)*ALPHA)/divisor;
		regv = (2.0*M_PI*frqc*i)/frqs;
		hfir[i] = (hgain*sin(regv)*wfnc)/regv;
	}

	ismd=OSRM/ISRM;
	ismr=OSRM%ISRM;

	sptr = -(firodrv/ISRM);
	sptrr = -(firodrv%ISRM);
	eptr = firodrv/ISRM;
	eptrr = firodrv%ISRM;

	ZeroMemory(smpld, sizeof(smpld));

	winpos = 0;
	iptr = 0;
}

void Wavefs44(FILE *file, int size, unsigned char *buffer)
{
	if (winpos < WINSIZ)
	{
		if ((winpos + (size>>2)) < WINSIZ)
			memcpy(&smpld[winpos], buffer, size);
		else
		{
			memcpy(&smpld[winpos], buffer, (WINSIZ-winpos)<<2);
			DownKernel(file);
			memcpy(&smpld[WINSIZ], buffer+((WINSIZ-winpos)<<2), size-((WINSIZ-winpos)<<2));
		}
		winpos += size>>2;
	}
	else
	{
		if ((winpos + (size>>2)) < (WINSIZ<<1))
		{
			memcpy(&smpld[winpos], buffer, size);
			winpos += size>>2;
		}
		else
		{
			memcpy(&smpld[winpos], buffer, ((WINSIZ<<1)-winpos)<<2);
			DownKernel(file);
			memcpy(&smpld[0], buffer+(((WINSIZ<<1)-winpos)<<2), size-(((WINSIZ<<1)-winpos)<<2));
			winpos += (size>>2) - (WINSIZ<<1);
		}
	}
}

void EndSRC(FILE *file)
{
	int i;

	if (winpos < WINSIZ)
	{
		for (i=WINSIZ-1; i>=winpos; i--)
		{
			smpld[i].l = 0;
			smpld[i].r = 0;
		}

		DownKernel(file);
	}
	else
	{
		for (i=WINSIZ-1; i>=winpos; i--)
		{
			smpld[WINSIZ+i].l = 0;
			smpld[WINSIZ+i].r = 0;
		}

		DownKernel(file);
	}
}

BOOL CheckWAV(const char * p_input)
{
	HMMIO hmmio;
	MMCKINFO mmckinfoParent;
	MMCKINFO mmckinfoSubchunk;
	WAVEFORMAT pFormat;

	int FmtSize, Error_Flag = 0;

	hmmio = mmioOpen(const_cast<char*>(p_input), NULL, MMIO_READ);
	//	cant' understand why mmioOpen needs a not const char*

	mmckinfoParent.fccType = mmioFOURCC('W','A','V','E'); 
	if (mmioDescend(hmmio, (LPMMCKINFO) &mmckinfoParent, NULL, MMIO_FINDRIFF))
		Error_Flag = 1;

	mmckinfoSubchunk.ckid = mmioFOURCC('f','m','t',' '); 
	if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK)) 
		Error_Flag = 1;

	FmtSize = mmckinfoSubchunk.cksize;

	if (mmioRead(hmmio, (HPSTR) &pFormat, FmtSize) != FmtSize)
		Error_Flag = 1;
 
	if (pFormat.wFormatTag!=WAVE_FORMAT_PCM || pFormat.nChannels!=2 || pFormat.nBlockAlign!=4 ||
		(pFormat.nSamplesPerSec!=48000 && pFormat.nSamplesPerSec!=44100))
		Error_Flag = 1;

	mmioAscend(hmmio, &mmckinfoSubchunk, 0);

	mmckinfoSubchunk.ckid = mmioFOURCC('d','a','t','a'); 
	if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK)) 
		Error_Flag = 1;

	mmioClose(hmmio, 0);

	if (Error_Flag)
		return FALSE;
	else
		return TRUE;
}

void Wavefs44File(int delay, int , int , const char * p_input, char * p_output)
{
	FILE *WaveIn, *WaveOut;
	int i, DataSize, WaveInPos, rsize, wsize = 0;

	HMMIO hmmio;
	MMIOINFO mmioinfo;
	MMCKINFO mmckinfoParent;
	MMCKINFO mmckinfoSubchunk;
	WAVEFORMAT pFormat;

	g_DVDGlobals.Sound_Max = 1;
//	g_InfoFunctions.Info("");

	hmmio = mmioOpen(const_cast<char*>(p_input), NULL, MMIO_READ);
	//	cant' understand why mmioOpen needs a not const char*

	mmckinfoParent.fccType = mmioFOURCC('W','A','V','E'); 
	mmioDescend(hmmio, (LPMMCKINFO) &mmckinfoParent, NULL, MMIO_FINDRIFF);

	mmckinfoSubchunk.ckid = mmioFOURCC('f','m','t',' '); 
	mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK);

	mmioRead(hmmio, (HPSTR) &pFormat, mmckinfoSubchunk.cksize); 
	mmioAscend(hmmio, &mmckinfoSubchunk, 0);

	mmckinfoSubchunk.ckid = mmioFOURCC('d','a','t','a'); 
	mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, MMIO_FINDCHUNK);

	DataSize = mmckinfoSubchunk.cksize; 
	mmioGetInfo(hmmio, &mmioinfo, 0);
	WaveInPos =	mmioinfo.lDiskOffset;

	if (g_Ac3Context.SRC_Flag || pFormat.nSamplesPerSec==44100)
		sprintf(l_szBuffer, "%s 44.1KHz", p_output);
	else
		sprintf(l_szBuffer, "%s 48KHz", p_output);

	sprintf(pcm.filename, "%s DELAY %dms compensated.wav", l_szBuffer, delay);
	WaveOut = fopen(pcm.filename, "wb");

	fwrite(WAVHeader, sizeof(WAVHeader), 1, WaveOut);

	if (g_Ac3Context.SRC_Flag || pFormat.nSamplesPerSec==44100)
		DownWAV(WaveOut);

	if (delay > 0)
	{
		if (g_Ac3Context.SRC_Flag || pFormat.nSamplesPerSec==44100)
		{
			for (i=0; i<((int)(176.4*delay)>>2)<<2; i++)
				fputc(0, WaveOut);

			wsize += ((int)(176.4*delay)>>2)<<2;
		}
		else
		{
			for (i=0; i<delay*192; i++)
				fputc(0, WaveOut);

			wsize += delay*192;
		}
	}
	else
	{
		if (pFormat.nSamplesPerSec==44100)
		{
			if (((int)(-176.4*delay)>>2)<<2 > DataSize)
				goto THE_END;
			else
			{
				WaveInPos += ((int)(-176.4*delay)>>2)<<2;
				DataSize -= ((int)(-176.4*delay)>>2)<<2;
			}
		}
		else
		{
			if (-192*delay > DataSize)
				goto THE_END;
			else
			{
				WaveInPos += -192*delay;
				DataSize -= -192*delay;
			}
		}
	}

	WaveIn = fopen(p_input, "rb");

	if (g_Ac3Context.SRC_Flag && pFormat.nSamplesPerSec==48000)
	{
		InitialSRC();

		fseek(WaveIn, WaveInPos, SEEK_SET);
		rsize = DataSize;

		process.op = timeGetTime();

		while (rsize > 0)
		{
			if (g_Flags.Stop_Flag)
				break;

			if (rsize < (WINSIZ<<2))
			{
				for (i=WINSIZ-1; i>=(rsize>>2); i--)
				{
					smpld[winpos+i].l = 0;
					smpld[winpos+i].r = 0;
				}

				fread(&smpld[winpos], rsize, 1, WaveIn);
				rsize = 0;
			}
			else
			{
				fread(&smpld[winpos], WINSIZ<<2, 1, WaveIn);
				rsize -= (WINSIZ<<2);
			}

			DownKernel(WaveOut);

			winpos = (winpos > 0) ? 0 : WINSIZ;
/*
			process.ed = timeGetTime();
			process.elapsedMs = process.ed-process.op;
			process.elapsed = process.elapsedMs/1000;
			process.percent = (float)(100.0*(DataSize-rsize)/DataSize);
			process.remain = (int)(process.elapsedMs*(100.0-process.percent)/process.percent)/1000;

			if (g_Flags.Statistics_Flag)
			{
				sprintf(l_szBuffer, "%d:%02d:%02d", process.elapsed/3600, (process.elapsed%3600)/60, process.elapsed%60);
				g_InfoFunctions.Elapsed(l_szBuffer);

				sprintf(l_szBuffer, "%d:%02d:%02d", process.remain/3600, (process.remain%3600)/60, process.remain%60);
				g_InfoFunctions.Remain(l_szBuffer);
			}

			g_InfoFunctions.TrackProgress( (int)(process.percent*TRACK_PITCH/100));
*/
		}

		DataSize = (int)(0.91875*(DataSize-rsize));

		Normalize(NULL, 44+wsize, pcm.filename, WaveOut, 44+wsize, DataSize,g_Flags.Statistics_Flag,g_Ac3Context.Normalization_Flag);
	}
	else
		Normalize(WaveIn, WaveInPos, NULL, WaveOut, 44+wsize, DataSize,g_Flags.Statistics_Flag,g_Ac3Context.Normalization_Flag);

	wsize += DataSize;

THE_END:
	EndWAV(WaveOut, wsize);
	mmioClose(hmmio, 0);
	_fcloseall();
}

void EndWAV(FILE *file, int size)
{
	fseek(file, 40, SEEK_SET);
	fwrite(&size, sizeof(int), 1, file);
	size += 36;
	fseek(file, 4, SEEK_SET);
	fwrite(&size, sizeof(int), 1, file);
}

void DownWAV(FILE *file)
{
	fseek(file, 24, SEEK_SET);
	fwrite(FQ44100, sizeof(FQ44100), 1, file);
	fseek(file, 44, SEEK_SET);
}

static void DownKernel(FILE *file)
{
	int i, j, l;

	iptr += WINSIZ;

	while (iptr > eptr)
	{
		suml = 0;
		sumr = 0;
		j = -firodrv-sptrr;

		for (i=sptr; i<=eptr; i++, j+=ISRM)
		{
			l = j > 0 ? j : -j;

			spp = &smpld[i & ((WINSIZ<<1)-1)];
			hgain = hfir[l];
			suml += hgain * spp->l;
			sumr += hgain * spp->r;
		}

		smplo.l = SaturateRound(suml);
		smplo.r = SaturateRound(sumr);

		if (g_DVDGlobals.Sound_Max < abs(smplo.l))
			g_DVDGlobals.Sound_Max = (short)abs(smplo.l);

		if (g_DVDGlobals.Sound_Max < abs(smplo.r))
			g_DVDGlobals.Sound_Max = (short)abs(smplo.r);

		fwrite(&smplo, sizeof(SmplData), 1, file);

		sptr += ismd;
		sptrr += ismr;
		if (sptrr>0)
		{
			sptrr -= ISRM;
			sptr++;
		}

		eptr += ismd;
		eptrr += ismr;
		if (eptrr>=ISRM)
		{
			eptrr -= ISRM;
			eptr++;
		}
	}
}

static double bessel0(double x)
{
	double value = 1.0, step = 1.0, part = x/2;

	while (part > 1.0E-10)
	{
		value += part*part;
		step += 1.0;
		part = (part*x)/(2.0*step);
	}

	return value;
}
