/******************************************************************************
 *
 * VOC Datatype, based on the sourcecode found in OS3.1 Native Developer Kit
 *
 * Written by Christian Buchner
 *
 ******************************************************************************
 * dispatch.c
 */

#include "classbase.h"

/*****************************************************************************/

#define DEBUG 0

#if DEBUG

#define	DB(x)	x

#include <stdarg.h>
void __stdargs Error(struct ClassBase *cb,UBYTE *Msg,...)
{
	va_list Arg;
	struct EasyStruct Req={sizeof(struct EasyStruct),0,"VOC debug message",0,"Okay"};
	va_start(Arg,Msg);
	Req.es_TextFormat=Msg;
	EasyRequestArgs(NULL,&Req,0,Arg);
	va_end(Arg);
}

#else

#define	DB(x)	

#endif



/*****************************************************************************/

Class *initClass (struct ClassBase * cb)
{
	Class *cl;
	
	if (cl = MakeClass (VOCDTCLASS, SOUNDDTCLASS, NULL, NULL, 0L))
	{
		cl->cl_Dispatcher.h_Entry = (ULONG (*)())Dispatch;
		cl->cl_UserData = (ULONG) cb;
		AddClass (cl);
	}
	
	return (cl);
}

/*****************************************************************************/

ULONG ASM Dispatch (REG (a0) Class * cl, REG (a2) Object * o, REG (a1) Msg msg)
{
	struct ClassBase *cb = (struct ClassBase *) cl->cl_UserData;
	ULONG retval = 0L;
	
	switch (msg->MethodID)
	{
	case OM_NEW:
		if (retval = DoSuperMethodA (cl, o, msg))
		{
			if (!(ConvertObjectData (cb, cl, (Object *) retval, ((struct opSet *) msg)->ops_AttrList)))
			{
				CoerceMethod (cl, (Object *) retval, OM_DISPOSE);
				retval = NULL;
			}
		}
		break;
		
	 /* Let the superclass handle everything else */
	
	default:
		retval = (ULONG) DoSuperMethodA (cl, o, msg);
		break;
	}
	
	return (retval);
}

/*****************************************************************************/

#define GETLONG(a)   ( (*(a)) + (*(a+1)<<8) + (*(a+2)<<16) + (*(a+3)<<24) )
#define GETTRIPLE(a) ( (*(a)) + (*(a+1)<<8) + (*(a+2)<<16) )
#define GETWORD(a)   ( (*(a)) + (*(a+1)<<8) )

#define	SWAPW(a)	(WORD)(((UWORD)a>>8)+((((UWORD)a&0xff)<<8)))
#define	SWAPU(a)	(UWORD)(((UWORD)a>>8)+((((UWORD)a&0xff)<<8)))
#define	SWAPL(a)	(LONG)(((ULONG)a>>24)+(((ULONG)a&0xff0000)>>8)+(((ULONG)a&0xff00)<<8)+(((ULONG)a&0xff)<<24))


/*****************************************************************************/

#define ID_Terminator		0
#define ID_Sounddata		1
#define ID_Soundcontinue	2
#define ID_Silence			3
#define ID_Marker			4
#define ID_ASCII			5
#define ID_Repeat			6
#define ID_Endrepeat		7
#define ID_Extended			8

/*****************************************************************************/

struct SoundInfo
{
	UBYTE soi_SampleRate;
	UBYTE soi_Compression;
	UBYTE soi_Data;
};

#define VOCFORMAT_8BIT	0
#define VOCFORMAT_4BIT	1
#define VOCFORMAT_26BIT	2
#define VOCFORMAT_2BIT	3
#define VOCFORMAT_MDAC	4

struct SilenceInfo
{
	UWORD sil_Length;
	UBYTE sil_SampleRate;
};

struct ExtendedInfo
{
	UWORD exi_TimeConstant;
	UBYTE exi_Compression;
	UBYTE exi_Mode;
};

#define MODE_MONO		0
#define MODE_STEREO		1


/*****************************************************************************/


/*****************************************************************************/

BOOL ConvertObjectData (struct ClassBase * cb, Class * cl, Object * o, struct TagItem * attrs)
{
	LONG ErrorCode=0;
	struct FileInfoBlock *fib;
	struct VoiceHeader *vhdr;
	STRPTR title;
	LONG size;
	ULONG len;
	BPTR fh;
	STRPTR buffer, ptr;
	
	UBYTE pass;
	UBYTE ID;
	
	ULONG Memory;
	ULONG SampleLength;
	ULONG SampleFreq;
	ULONG Frequency=10000;
	UBYTE Mode=MODE_MONO;
	UBYTE *Sample;
	UBYTE *Dest=0;
	ULONG DestLength;
	
	title = (STRPTR) GetTagData (DTA_Name, NULL, attrs);
	
	getdtattrs (cb, o,
		SDTA_VoiceHeader, &vhdr,
		DTA_Handle, &fh,
		TAG_DONE);
	
	if (fh && vhdr)
	{
		/* Allocate a temporary file info block */
		if (!(fib = (struct FileInfoBlock *) AllocMem (sizeof (struct FileInfoBlock), NULL)))
		{
			ErrorCode=ERROR_NO_FREE_STORE;
			DB(Error(cb,"not enough memory"));
		}
		else
		{
			/* Get the size of the file */
			if (ExamineFH (fh, fib))
			{
				size = fib->fib_Size;
			}
			else
			{
				Seek (fh, 0, OFFSET_END);
				size = Seek (fh, 0, OFFSET_BEGINNING);
			}
			
			/* Free the temporary file info block */
			FreeMem (fib, sizeof (struct FileInfoBlock));
			
			/* Buffered IO block */
			if (!(buffer = AllocVec (size, NULL)))
			{
				ErrorCode=ERROR_NO_FREE_STORE;
				DB(Error(cb,"not enough memory"));
			}
			else
			{
				/* Read the whole file into the buffer */
				if (Read (fh, buffer, size) != size)
				{
					DB(Error(cb,"couldn't read %ld bytes : error=%ld", size, IoErr ()));
				}
				else
				{
					if (strncmp(buffer, "Creative Voice File\32",20))
					{
						ErrorCode=ERROR_OBJECT_WRONG_TYPE;
						DB(Error(cb,"unknown format"));
					}
					else
					{
						/* 3-Pass loop over whole VOC Sample */
						/* 1. Pass: find out highest sample rate */
						/* 2. Pass: determine length of whole sample */
						/* 3. Pass: synthesize 8-Bit sample by using */
						/*          highest samplerate */
						
						SampleFreq=0;
						SampleLength=0;
						
						for (pass=1;(!ErrorCode) && pass<=3;pass++)
						{
							/* get offset of the first data block to ptr */
							
							ptr=buffer+GETWORD(buffer+20);
							
							/* loop through cunks until end of buffer reached */
							
							while((!ErrorCode) && (ID=*ptr) && (ptr<buffer+size))
							{
								len=GETTRIPLE(ptr+1);
								ptr+=4;
								
								/* Handle the different chunks */
								
								switch(ID)
								{
									case ID_Sounddata:
									{
										struct SoundInfo *soi=(struct SoundInfo*)ptr;
										/* MUST be uncompressed 8 bit PCM data */
										if (soi->soi_Compression == VOCFORMAT_8BIT)
										{
											Mode=MODE_MONO;	/* Force MONO mode */
											/* don't know whether this is correct */
											/* missing some documentation! */
											Frequency=1000000/(256-soi->soi_SampleRate);
											DestLength=ScaleLength(len-sizeof(struct SoundInfo),Frequency,SampleFreq);
											/* pass 1: Determine highest freq */
											if (pass==1) if (Frequency>SampleFreq) SampleFreq=Frequency;
											/* pass 2: calculate sample size */
											if (pass==2) SampleLength+=DestLength;
											/* pass 3: convert sample data */
											if (pass==3) Convert(ptr+sizeof(struct SoundInfo), &Dest, DestLength, Frequency, SampleFreq, Mode);
										}
										else
										{
											ErrorCode=DTERROR_UNKNOWN_COMPRESSION;
											DB(Error(cb,"unknown compression"));
										}
									}
									break;
									
									case ID_Soundcontinue:
									{
										/* Frequency and Mode may have been */
										/* initialized by ID_Extended */
										DestLength=ScaleLength(len,Frequency,SampleFreq);
										if (Mode==MODE_STEREO) DestLength/=2;
										/* pass 1: Determine highest freq */
										if (pass==1) if (Frequency>SampleFreq) SampleFreq=Frequency;
										/* pass 2: calculate sample size */
										if (pass==2) SampleLength+=DestLength;
										/* pass 3: convert sample data */
										if (pass==3) Convert(ptr, &Dest, DestLength, Frequency, SampleFreq, Mode);
									}
									break;
									
									case ID_Silence:
									{
										struct SilenceInfo *sil=(struct SilenceInfo*)ptr;
										Frequency=1000000/(256-sil->sil_SampleRate);
										DestLength=ScaleLength(((ULONG)(SWAPW(sil->sil_Length)+1)),Frequency,SampleFreq);
										/* pass 2: calculate sample size */
										if (pass==2) SampleLength+=DestLength;
										/* pass 3: just skip (leave silent) */
										if (pass==3) Dest+=DestLength;
									}
									break;
									
									case ID_Extended:
									{
										struct ExtendedInfo *exi=(struct ExtendedInfo*)ptr;
										if (exi->exi_Compression != VOCFORMAT_8BIT)
										{
											ErrorCode=DTERROR_UNKNOWN_COMPRESSION;
											DB(Error(cb,"unknown compression"));
										}
										else
										{
											if (exi->exi_Mode != MODE_MONO && exi->exi_Mode != MODE_STEREO)
											{
												ErrorCode=ERROR_OBJECT_WRONG_TYPE;
												DB(Error(cb,"unknown type"));
											}
											else
											{
												Mode=exi->exi_Mode;
												Frequency=256000000/(65536-SWAPW(exi->exi_TimeConstant));
												if (Mode==MODE_STEREO) Frequency/=2;
											}
										}
									}
									
									default:
									{
										DB(Error(cb,"unknown chunk: %ld, Len: %ld",(long)ID,len));
									}
									break;
									
								}
								ptr=ptr+len;
							}
							if (pass==2)
							{
								/* sound.datatype V40 can replay */
								/* directly from Fast RAM */
									
								Memory = (SuperClassBase->lib_Version>39) ?
											MEMF_ANY : MEMF_CHIP;
									
								if (Sample=AllocVec(SampleLength, Memory | MEMF_CLEAR))
								{
									/* Fill in the voice header */
									
									memset(vhdr,0,sizeof(struct VoiceHeader));
									vhdr->vh_OneShotHiSamples	= SampleLength;
									vhdr->vh_SamplesPerSec		= SampleFreq;
									vhdr->vh_Octaves			= 1;
									vhdr->vh_Compression		= CMP_NONE;
									vhdr->vh_Volume				= 64;
									
									/* Tell the super-class about the attributes */
									
									setdtattrs (cb, o,
										DTA_ObjName,		title,
										SDTA_Sample,		Sample,
										SDTA_SampleLength,	SampleLength,
										SDTA_Period,		(ULONG)(SysBase->ex_EClockFrequency*5)/SampleFreq,
										SDTA_Volume,		64,
										SDTA_Cycles,		1,
										TAG_DONE);
									
									Dest=Sample;
								}
								else
								{
									ErrorCode=ERROR_NO_FREE_STORE;
									DB(Error(cb,"not enough memory"));
								}
							}
						}
					}
				}
				/* Free the buffer */
				FreeVec(buffer);
			}
		}
	}
	if (ErrorCode)
	{
		SetIoErr(ErrorCode);
		return(FALSE);
	}
	return(TRUE);
}


/*****************************************************************************/

/* Convert the unsigned sample data to 8 bit signed PCM format */
/* and do a time stretching if necessary                       */
/* Destination pointer will be incremented                     */
/* This one calls assembly routines for speed reasons          */

void Convert(UBYTE *Source, UBYTE **Dest, ULONG DestLength, ULONG SourceFreq, ULONG DestFreq, UBYTE Mode)
{
	if (Mode==MODE_MONO)
	{
		ConvertMono8(Source, *Dest, DestLength, SourceFreq, DestFreq);
	}
	if (Mode==MODE_STEREO)
	{
		ConvertStereo8(Source, *Dest, DestLength, SourceFreq, DestFreq);
	}
	*Dest+=DestLength;
}

/*****************************************************************************/

ULONG setdtattrs (struct ClassBase * cb, Object * o, ULONG data,...)
{
	return (SetDTAttrsA (o, NULL, NULL, (struct TagItem *) & data));
}

/*****************************************************************************/

ULONG getdtattrs (struct ClassBase * cb, Object * o, ULONG data,...)
{
	return (GetDTAttrsA (o, (struct TagItem *) & data));
}
