/******************************************************************************
 *
 * WAV Datatype, based on the sourcecode found in OS3.1 Native Developer Kit
 *
 * Written by David N.Junod and Christian Buchner
 *
 ******************************************************************************
 * dispatch.c
 * TO DO: Implement different decompression techniques (ADPCM, µ-law, a-law...)
 */

#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,"WAV 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 (WAVDTCLASS, 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	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	fmt_ID	MAKE_ID('f','m','t',' ')
#define	data_ID	MAKE_ID('d','a','t','a')

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

/* RIFF header */
struct RIFFHeader
{
	ULONG	rh_RIFF;
	ULONG	rh_Size;
	ULONG	rh_Format;
};

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

/* RIFF chunk */
struct RIFFChunk
{
	ULONG	rc_ID;
	ULONG	rc_Size;
};

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

struct WaveFormat
{
	WORD	 wf_Format;
	WORD	 wf_Channels;
	ULONG	 wf_SamplesPerSec;
	ULONG	 wf_AvgBytesPerSec;
	WORD	 wf_BlockAlign;
};

/* wf_Format values */
#define	WAVE_FORMAT_PCM		1

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

struct PCMData
{
	UWORD	 pd_BitsPerSample;
};

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

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

BOOL ConvertObjectData (struct ClassBase * cb, Class * cl, Object * o, struct TagItem * attrs)
{
	LONG ErrorCode=0;
	struct FileInfoBlock *fib;
	struct VoiceHeader *vhdr;
	STRPTR Title;
	ULONG Memory;
	LONG size;
	BPTR FH;
	
	ULONG SampleLength;
	UBYTE *Sample;
	
	/* Maximum block size for file IO */
	ULONG MAXBUF=16384;
	
	/* IO related */
	UBYTE *buffer;
	ULONG ID;
	ULONG ChunkLen;
	ULONG Left;
	ULONG Got;
	
	/* RIFF related */
	BOOL GotWaveFormat=FALSE;
	UWORD Format;
	UWORD Channels;
	ULONG SamplesPerSec;
	UWORD BitsPerSample;
	UWORD BytesPerSample;
	
	Title = (UBYTE*)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))
			{
				MAXBUF = size = fib->fib_Size;
			}
			else
			{
				Seek (FH, 0, OFFSET_END);
				MAXBUF = size = Seek (FH, 0, OFFSET_BEGINNING);
			}
			
			/* Free the temporary file info block */
			FreeMem (fib, sizeof (struct FileInfoBlock));
			
			/* Limit the size of the IO block to 32K */
			if (MAXBUF>32768) MAXBUF=32768;
			
			/* Allocate Buffered IO block */
			if (!(buffer = AllocVec(MAXBUF, MEMF_ANY)))
			{
				ErrorCode=ERROR_NO_FREE_STORE;
				DB (Error (cb,"not enough memory"));
			}
			else
			{
				/* Read the RIFF header into the buffer */
				Get(cb, FH, buffer, sizeof(struct RIFFHeader) ,&ErrorCode);
				if (!ErrorCode)
				{
					/* Check the RIFF header */
					if (((struct RIFFHeader*)buffer)->rh_RIFF!=MAKE_ID('R','I','F','F') ||
							((struct RIFFHeader*)buffer)->rh_Format!=MAKE_ID('W','A','V','E'))
					{
						ErrorCode=ERROR_OBJECT_WRONG_TYPE;
						DB(Error(cb,"unknown format"));
					}
					else
					{
						/* Get size from RIFF header */
						ChunkLen = SWAPL (((struct RIFFHeader*)buffer)->rh_Size) + 8;
						DB (Error (cb,"RIFF: %ld", ChunkLen));
						
						/* Make sure we have the right size */
						/* one missing byte is tolerated! */
						if (ChunkLen != size && ChunkLen != size+1)
						{
							ErrorCode=ERROR_BAD_HUNK;
							DB (Error (cb,"mangled file"));
						}
						else
						{
							/* Loop: Read in chunk header till EOF (Got=0) */
							while((!ErrorCode) && (Got=Get(cb, FH, buffer,sizeof(struct RIFFChunk),&ErrorCode)))
							{
								if (!ErrorCode)
								{
									/* Get chunk ID and len */
									ID = ((struct RIFFChunk*)buffer)->rc_ID;
									Left = ChunkLen = SWAPL(((struct RIFFChunk*)buffer)->rc_Size);
									DB (Error (cb,"Chunk: '%lc%lc%lc%lc', %ld", (ULONG)(ID>>24), (ULONG)(ID>>16)&0xff, (ULONG)(ID>>8)&0xff, (ULONG)(ID&0xff), ChunkLen));
									
									/* Read as much of the chunk as fits into buffer */
									Left-=(Got=Get(cb, FH, buffer, Left > MAXBUF ? MAXBUF : Left, &ErrorCode));
									
									if (!ErrorCode)
									{
										/* Handle the chunk types */
										switch (ID)
										{
											/* WaveForm structure */
											case fmt_ID:
											{
												if (Left!=0)
												{
													/* format chunk CANNOT be larger than buffer!!! */
													ErrorCode=ERROR_BAD_HUNK;
													DB(Error(cb,"format chunk overflow"));
												}
												else
												{
													struct WaveFormat *wf=(struct WaveFormat *)buffer;
													
													Format = SWAPW (wf->wf_Format);
													Channels = SWAPW (wf->wf_Channels);
													SamplesPerSec = SWAPL (wf->wf_SamplesPerSec);
													
													DB (Error (cb,"Wave Format\n"
																"Format: %ld\n"
																"Channels: %ld\n"
																"SPS: %ld\n"
																"Avg.BPS: %ld\n"
																"Align: %ld",
													(LONG) Format,
													(LONG) Channels,
													(LONG) SamplesPerSec,
													(LONG) SWAPL (wf->wf_AvgBytesPerSec) ,
													(LONG) SWAPW (wf->wf_BlockAlign)));
													
													/* We can only handle PCM format */
													
													if (Format!=WAVE_FORMAT_PCM)
													{
														ErrorCode=DTERROR_UNKNOWN_COMPRESSION;
														DB (Error (cb,"unknown compression"));
													}
													else
													{
														BitsPerSample = SWAPW(((struct PCMData*)(buffer+sizeof(struct WaveFormat)))->pd_BitsPerSample);
														BytesPerSample = BitsPerSample/8;
														DB (Error (cb,"BitsPerSample: %ld",(LONG)BitsPerSample));
														
														/* Make sure we have something that we can really handle */
														if ( (BitsPerSample!=8 && BitsPerSample!=16) || (Channels!=1 && Channels!=2) )
														{
															ErrorCode=ERROR_OBJECT_WRONG_TYPE;
															DB (Error (cb,"unsupported type"));
														}
														else
														{
															GotWaveFormat=TRUE;
														}
													}
												}
											}
											break;
											
											/* Sound data */
											case data_ID:
											{
												/* Make sure we have had a WaveFormat structure */
												if (!GotWaveFormat)
												{
													ErrorCode=ERROR_REQUIRED_ARG_MISSING;
													DB (Error (cb,"no waveform structure"));
												}
												else
												{
													/* sound.datatype V40 can replay */
													/* directly from Fast RAM */
													
													Memory = (SuperClassBase->lib_Version>39) ?
																MEMF_ANY : MEMF_CHIP;
													
													SampleLength = (ChunkLen/Channels)/BytesPerSample;
													
													if (!(Sample = AllocVec (SampleLength, Memory)))
													{
														ErrorCode=ERROR_NO_FREE_STORE;
														DB (Error (cb,"not enough memory"));
													}
													else
													{
														/* Sample conversion / buffered loading loop */
														
														ULONG Offset=0;
														while (!ErrorCode)
														{
															/* Convert the data to 8 bit signed format */
															/* Efficiently mix all channels into one */
															/* and copy the Sample to chip memory */
															
															ULONG DestBlockLen=(Got/Channels)/BytesPerSample;
															UBYTE *dst = (Memory == MEMF_CHIP) ? buffer : Sample+Offset;
															
															if (BitsPerSample==8 && Channels==1)
																ConvertMono8(buffer,dst,DestBlockLen);
															
															if (BitsPerSample==8 && Channels==2)
																ConvertStereo8(buffer,dst,DestBlockLen);
															
															if (BitsPerSample==16 && Channels==1)
																	ConvertMono16(buffer,dst,DestBlockLen);
															
															if (BitsPerSample==16 && Channels==2)
																	ConvertStereo16(buffer,dst,DestBlockLen);
															
															/* For V39 soundclass only:          */
															/* Now copy sound data into chip RAM.*/
															/* This is significantly faster than */
															/* decoding directly into Chip RAM.  */
															
															if (Memory == MEMF_CHIP)
																CopyMem( buffer, Sample+Offset, DestBlockLen );
															
															/* Increase offset in destination data */
															Offset+=DestBlockLen;
															
															/* Read on if data left otherwise exit loop */
															if (Left)
																Left-=(Got=Get(cb, FH, buffer, Left > MAXBUF ? MAXBUF : Left, &ErrorCode));
															else
																break;
														}
														
														/* Fill in the VoiceHeader */
														
														memset(vhdr,0,sizeof(struct VoiceHeader));
														vhdr->vh_OneShotHiSamples	= SampleLength;
														vhdr->vh_SamplesPerSec		= SamplesPerSec;
														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)/(ULONG)vhdr->vh_SamplesPerSec,
															SDTA_Volume,				64,
															SDTA_Cycles,				1,
															TAG_DONE);
													}
												}
											}
											break;
										}
									}
								}
							}
						}
					}
				}
				/* Free the buffer */
				FreeVec (buffer);
			}
		}
	}
	if (ErrorCode)
	{
		SetIoErr(ErrorCode);
		return(FALSE);
	}
	return(TRUE);
}

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

ULONG Get(struct ClassBase *cb, BPTR FH, UBYTE *buf, ULONG size, LONG *ErrorCode)
{
		LONG got;
		
		/* read in size bytes */
		got=Read(FH, buf, size);
		
		/* Io error? */
		if (got<0)
		{
			*ErrorCode=IoErr();
			got=0;
		}
		/* don't care at all about short reads */
		return((ULONG)got);
}

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

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));
}
