/*	MikMod sound library
	(c) 1998, 1999, 2000, 2001 Miodrag Vallat and others - see file AUTHORS
	for complete list.

	This library is free software; you can redistribute it and/or modify
	it under the terms of the GNU Library General Public License as
	published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program 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 Library General Public License for more details.

	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  Routines for loading samples. The sample loader utilizes the routines
  provided by the "registered" sample loader.

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "mikmod_internals.h"

static	int sl_rlength;
static	SWORD sl_old;
static	SWORD *sl_buffer=NULL;
static	SAMPLOAD *musiclist=NULL,*sndfxlist=NULL;

/* size of the loader buffer in words */
#define SLBUFSIZE 2048

/* IT-Compressed status structure */
typedef struct ITPACK {
	UWORD bits;    /* current number of bits */
	UWORD bufbits; /* bits in buffer */
	SWORD last;    /* last output */
	UBYTE buf;     /* bit buffer */
} ITPACK;

int SL_Init(SAMPLOAD* s)
{
	if(!sl_buffer)
		if(!(sl_buffer=(SWORD*)MikMod_calloc(1,SLBUFSIZE*sizeof(SWORD)))) return 0;

	sl_rlength = s->length;
	if(s->infmt & SF_16BITS) sl_rlength>>=1;
	sl_old = 0;

	return 1;
}

void SL_Exit(SAMPLOAD *s)
{
	if(sl_rlength>0) _mm_fseek(s->reader,sl_rlength,SEEK_CUR);

	MikMod_free(sl_buffer);
	sl_buffer=NULL;
}

/* unpack a 8bit IT packed sample */
static int read_itcompr8(ITPACK* status,MREADER *reader,SWORD *out,UWORD count,UWORD* incnt)
{
	SWORD *dest=out,*end=out+count;
	UWORD x,y,needbits,havebits,new_count=0;
	UWORD bits = status->bits;
	UWORD bufbits = status->bufbits;
	SBYTE last = status->last;
	UBYTE buf = status->buf;

	while (dest<end) {
		needbits=new_count?3:bits;
		x=havebits=0;
		while (needbits) {
			/* feed buffer */
			if (!bufbits) {
				if((*incnt)--)
					buf=_mm_read_UBYTE(reader);
				else
					buf=0;
				bufbits=8;
			}
			/* get as many bits as necessary */
			y = needbits<bufbits?needbits:bufbits;
			x|= (buf & ((1<<y)- 1))<<havebits;
			buf>>=y;
			bufbits-=y;
			needbits-=y;
			havebits+=y;
		}
		if (new_count) {
			new_count = 0;
			if (++x >= bits)
				x++;
			bits = x;
			continue;
		}
		if (bits<7) {
			if (x==(1<<(bits-1))) {
				new_count = 1;
				continue;
			}
		}
		else if (bits<9) {
			y = (0xff >> (9-bits)) - 4;
			if ((x>y)&&(x<=y+8)) {
				if ((x-=y)>=bits)
					x++;
				bits = x;
				continue;
			}
		}
		else if (bits<10) {
			if (x>=0x100) {
				bits=x-0x100+1;
				continue;
			}
		} else {
			/* error in compressed data... */
			_mm_errno=MMERR_ITPACK_INVALID_DATA;
			return 0;
		}

		if (bits<8) /* extend sign */
			x = ((SBYTE)(x <<(8-bits))) >> (8-bits);
		*(dest++)= (last+=x) << 8; /* convert to 16 bit */
	}
	status->bits = bits;
	status->bufbits = bufbits;
	status->last = last;
	status->buf = buf;
	return (dest-out);
}

/* unpack a 16bit IT packed sample */
static int read_itcompr16(ITPACK *status,MREADER *reader,SWORD *out,UWORD count,UWORD* incnt)
{
	SWORD *dest=out,*end=out+count;
	SLONG x,y,needbits,havebits,new_count=0;
	UWORD bits = status->bits;
	UWORD bufbits = status->bufbits;
	SWORD last = status->last;
	UBYTE buf = status->buf;

	while (dest<end) {
		needbits=new_count?4:bits;
		x=havebits=0;
		while (needbits) {
			/* feed buffer */
			if (!bufbits) {
				if((*incnt)--)
					buf=_mm_read_UBYTE(reader);
				else
					buf=0;
				bufbits=8;
			}
			/* get as many bits as necessary */
			y=needbits<bufbits?needbits:bufbits;
			x|=(buf &((1<<y)-1))<<havebits;
			buf>>=y;
			bufbits-=(UWORD)y;
			needbits-=(UWORD)y;
			havebits+=(UWORD)y;
		}
		if (new_count) {
			new_count = 0;
			if (++x >= bits)
				x++;
			bits = (UWORD)x;
			continue;
		}
		if (bits<7) {
			if (x==(1<<(bits-1))) {
				new_count=1;
				continue;
			}
		}
		else if (bits<17) {
			y=(0xffff>>(17-bits))-8;
			if ((x>y)&&(x<=y+16)) {
				if ((x-=y)>=bits)
					x++;
				bits = (UWORD)x;
				continue;
			}
		}
		else if (bits<18) {
			if (x>=0x10000) {
				bits=(UWORD)(x-0x10000+1);
				continue;
			}
		} else {
			 /* error in compressed data... */
			_mm_errno=MMERR_ITPACK_INVALID_DATA;
			return 0;
		}

		if (bits<16) /* extend sign */
			x = ((SWORD)(x<<(16-bits)))>>(16-bits);
		*(dest++)=(last+=x);
	}
	status->bits = bits;
	status->bufbits = bufbits;
	status->last = last;
	status->buf = buf;
	return (dest-out);
}

static int SL_LoadInternal(void *buffer,UWORD infmt,UWORD outfmt,int scalefactor,ULONG length,MREADER *reader,int dither)
{
	SBYTE *bptr = (SBYTE*)buffer;
	SWORD *wptr = (SWORD*)buffer;
	int stodo,t,u;

	int result,c_block=0;	/* compression bytes until next block */
	ITPACK status;
	UWORD incnt = 0;

	SBYTE compressionTable[16];
	SWORD adpcmDelta = 0;
	int hasTable = 0;

	status.buf = 0;
	status.last = 0;
	status.bufbits = 0;
	status.bits = 0;

	while(length) {
		stodo=(length<SLBUFSIZE)?length:SLBUFSIZE;

		if(infmt&SF_ITPACKED) {
			sl_rlength=0;
			if (!c_block) {
				status.bits = (infmt & SF_16BITS) ? 17 : 9;
				status.last = status.bufbits = 0;
				incnt=_mm_read_I_UWORD(reader);
				c_block = (infmt & SF_16BITS) ? 0x4000 : 0x8000;
				if(infmt&SF_DELTA) sl_old=0;
			}
			if (infmt & SF_16BITS) {
				if(!(result=read_itcompr16(&status,reader,sl_buffer,stodo,&incnt)))
					return 1;
			} else {
				if(!(result=read_itcompr8(&status,reader,sl_buffer,stodo,&incnt)))
					return 1;
			}
			if(result!=stodo) {
				_mm_errno=MMERR_ITPACK_INVALID_DATA;
				return 1;
			}
			c_block -= stodo;
		} else if (infmt&SF_ADPCM4) {
			if (!hasTable) {
				/* Read compression table */
				_mm_read_SBYTES(compressionTable, 16, reader);
				hasTable = 1;
			}

			// 4-bit ADPCM data, used by MOD plugin
			for(t=0;t<stodo;t+=2) {
				UBYTE b = _mm_read_UBYTE(reader);

				adpcmDelta += compressionTable[b & 0x0f];
				sl_buffer[t] = adpcmDelta << 8;
				adpcmDelta += compressionTable[(b >> 4) & 0x0f];
				sl_buffer[t+1] = adpcmDelta << 8;
			}
		} else {
			if(infmt&SF_16BITS) {
				if(_mm_eof(reader)) {
					_mm_errno=MMERR_NOT_A_STREAM;/* better error? */
					return 1;
				}
				if(infmt&SF_BIG_ENDIAN)
					_mm_read_M_SWORDS(sl_buffer,stodo,reader);
				else
					_mm_read_I_SWORDS(sl_buffer,stodo,reader);
			} else {
				SBYTE *src;
				SWORD *dest;

				if(_mm_eof(reader)) {
					_mm_errno=MMERR_NOT_A_STREAM;/* better error? */
					return 1;
				}
				reader->Read(reader,sl_buffer,sizeof(SBYTE)*stodo);
				src = (SBYTE*)sl_buffer;
				dest  = sl_buffer;
				src += stodo;dest += stodo;

				for(t=0;t<stodo;t++) {
					src--;dest--;
					*dest = (*src)<<8;
				}
			}
			sl_rlength-=stodo;
		}

		if(infmt & SF_DELTA)
			for(t=0;t<stodo;t++) {
				sl_buffer[t] += sl_old;
				sl_old = sl_buffer[t];
			}

		if((infmt^outfmt) & SF_SIGNED)
			for(t=0;t<stodo;t++)
				sl_buffer[t]^= 0x8000;

		if(scalefactor) {
			int idx = 0;
			SLONG scaleval;

			/* Sample Scaling... average values for better results. */
			t= 0;
			while(t<stodo && length) {
				scaleval = 0;
				for(u=scalefactor;u && t<stodo;u--,t++)
					scaleval+=sl_buffer[t];
				sl_buffer[idx++]=(UWORD)(scaleval/(scalefactor-u));
				length--;
			}
			stodo = idx;
		} else
			length -= stodo;

		if (dither) {
			if((infmt & SF_STEREO) && !(outfmt & SF_STEREO)) {
				/* dither stereo to mono, average together every two samples */
				SLONG avgval;
				int idx = 0;

				t=0;
				while(t<stodo && length) {
					avgval=sl_buffer[t++];
					avgval+=sl_buffer[t++];
					sl_buffer[idx++]=(SWORD)(avgval>>1);
					length-=2;
				}
				stodo = idx;
			}
		}

		if(outfmt & SF_16BITS) {
			for(t=0;t<stodo;t++)
				*(wptr++)=sl_buffer[t];
		} else {
			for(t=0;t<stodo;t++)
				*(bptr++)=sl_buffer[t]>>8;
		}
	}
	return 0;
}

int SL_Load(void* buffer,SAMPLOAD *smp,ULONG length)
{
	return SL_LoadInternal(buffer,smp->infmt,smp->outfmt,smp->scalefactor,
				length,smp->reader,0);
}

/* Registers a sample for loading when SL_LoadSamples() is called. */
SAMPLOAD* SL_RegisterSample(SAMPLE* s,int type,MREADER* reader)
{
	SAMPLOAD *news,**samplist,*cruise;

	if(type==MD_MUSIC) {
		samplist = &musiclist;
		cruise = musiclist;
	} else
	  if (type==MD_SNDFX) {
		samplist = &sndfxlist;
		cruise = sndfxlist;
	} else
		return NULL;

	/* Allocate and add structure to the END of the list */
	if(!(news=(SAMPLOAD*)MikMod_calloc(1, sizeof(SAMPLOAD)))) return NULL;

	if(cruise) {
		while(cruise->next) cruise=cruise->next;
		cruise->next = news;
	} else
		*samplist = news;

	news->infmt     = s->flags & SF_FORMATMASK;
	news->outfmt    = news->infmt;
	news->reader    = reader;
	news->sample    = s;
	news->length    = s->length;
	news->loopstart = s->loopstart;
	news->loopend   = s->loopend;

	return news;
}

static void FreeSampleList(SAMPLOAD* s)
{
	SAMPLOAD *old;

	while(s) {
		old = s;
		s = s->next;
		MikMod_free(old);
	}
}

/* Returns the total amount of memory required by the samplelist queue. */
static ULONG SampleTotal(SAMPLOAD* samplist,int type)
{
	int total = 0;

	while(samplist) {
		samplist->sample->flags=
		  (samplist->sample->flags&~SF_FORMATMASK)|samplist->outfmt;
		total += MD_SampleLength(type,samplist->sample);
		samplist=samplist->next;
	}

	return total;
}

static ULONG RealSpeed(SAMPLOAD *s)
{
	return(s->sample->speed/(s->scalefactor?s->scalefactor:1));
}

static int DitherSamples(SAMPLOAD* samplist,int type)
{
	SAMPLOAD *c2smp=NULL;
	ULONG maxsize, speed;
	SAMPLOAD *s;

	if(!samplist) return 0;

	if((maxsize=MD_SampleSpace(type)*1024) != 0)
		while(SampleTotal(samplist,type)>maxsize) {
			/* First Pass - check for any 16 bit samples */
			s = samplist;
			while(s) {
				if(s->outfmt & SF_16BITS) {
					SL_Sample16to8(s);
					break;
				}
				s=s->next;
			}
			/* Second pass (if no 16bits found above) is to take the sample with
			   the highest speed and dither it by half. */
			if(!s) {
				s = samplist;
				speed = 0;
				while(s) {
					if((s->sample->length) && (RealSpeed(s)>speed)) {
						speed=RealSpeed(s);
						c2smp=s;
					}
					s=s->next;
				}
				if (c2smp)
					SL_HalveSample(c2smp,2);
			}
		}

	/* Samples dithered, now load them ! */
	s = samplist;
	while(s) {
		/* sample has to be loaded ? -> increase number of samples, allocate
		   memory and load sample. */
		if(s->sample->length) {
			if(s->sample->seekpos)
				_mm_fseek(s->reader, s->sample->seekpos, SEEK_SET);

			/* Call the sample load routine of the driver module. It has to
			   return a 'handle' (>=0) that identifies the sample. */
			s->sample->handle = MD_SampleLoad(s, type);
			s->sample->flags  = (s->sample->flags & ~SF_FORMATMASK) | s->outfmt;
			if(s->sample->handle<0) {
				FreeSampleList(samplist);
				if(_mm_errorhandler) _mm_errorhandler();
				return 1;
			}
		}
		s = s->next;
	}

	FreeSampleList(samplist);
	return 0;
}

int SL_LoadSamples(void)
{
	int rc;

	_mm_critical = 0;

	if((!musiclist)&&(!sndfxlist)) return 0;
	rc=DitherSamples(musiclist,MD_MUSIC)||DitherSamples(sndfxlist,MD_SNDFX);
	musiclist=sndfxlist=NULL;

	return rc;
}

void SL_Sample16to8(SAMPLOAD* s)
{
	s->outfmt &= ~SF_16BITS;
	s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt;
}

void SL_Sample8to16(SAMPLOAD* s)
{
	s->outfmt |= SF_16BITS;
	s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt;
}

void SL_SampleSigned(SAMPLOAD* s)
{
	s->outfmt |= SF_SIGNED;
	s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt;
}

void SL_SampleUnsigned(SAMPLOAD* s)
{
	s->outfmt &= ~SF_SIGNED;
	s->sample->flags = (s->sample->flags&~SF_FORMATMASK) | s->outfmt;
}

void SL_HalveSample(SAMPLOAD* s,int factor)
{
	s->scalefactor=factor>0?factor:2;

	s->sample->divfactor = s->scalefactor;
	s->sample->length    = s->length / s->scalefactor;
	s->sample->loopstart = s->loopstart / s->scalefactor;
	s->sample->loopend   = s->loopend / s->scalefactor;
}

/* ex:set ts=4: */
