/*
 * $Id: PlayAIFF.c 1.3 1997/07/12 13:03:51 olsen Exp olsen $
 *
 * :ts=4
 */

#include <exec/execbase.h>
#include <exec/memory.h>

#include <dos/dosextens.h>
#include <dos/dosasl.h>
#include <dos/rdargs.h>

#include <libraries/iffparse.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/iffparse_protos.h>

#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/iffparse_pragmas.h>

#define USE_BUILTIN_MATH
#include <string.h>
#include <stdlib.h>

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

#include "libraries/concierto.h"
#include "clib/concierto_protos.h"
#include "pragmas/concierto_pragmas.h"

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

extern struct ExecBase *SysBase;
extern struct DosLibrary *DOSBase;

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

struct Library *IFFParseBase;

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

struct Library *ConciertoBase;
struct AudioContext *ac;

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

struct RDArgs *rdargs;
STRPTR *names;

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

	/* Apple "SANE" 80 bit floating point number */

typedef struct {
	unsigned short	exponent;			/* Exponent, bit #15 is sign bit for mantissa */
	unsigned long	mantissa[2];		/* 64 bit mantissa */
} extended;

	/* Audio Interchange Format chunk data */

#define ID_AIFF MAKE_ID('A','I','F','F')
#define ID_AIFC MAKE_ID('A','I','F','C')

#define ID_FVER MAKE_ID('F','V','E','R')
#define ID_COMM MAKE_ID('C','O','M','M')
#define ID_SSND MAKE_ID('S','S','N','D')

	/* "COMM" chunk header */

typedef struct {
	short			numChannels;		/* Number of channels */
	unsigned long	numSampleFrames;	/* Number of sample frames */
	short			sampleSize; 		/* Number of bits per sample point */
	extended		sampleRate; 		/* Replay rate in samples per second */
} CommonChunk;

	/* The same for "AIFC" type files; this should be longer, but we don't */
	/* need the name of the compression format */

typedef struct {
	short			numChannels;		/* Number of channels */
	unsigned long	numSampleFrames;	/* Number of sample frames */
	short			sampleSize; 		/* Number of bits per sample point */
	extended		sampleRate; 		/* Replay rate in samples per second */
	unsigned long	compressionType;	/* Compression type */
} ExtCommonChunk;

#define NO_COMPRESSION MAKE_ID('N','O','N','E') /* No sound compression */

	/* "SSND" chunk header */

typedef struct {
	unsigned long	offset, 			/* Offset to sound data, for block alignment */
					blockSize;			/* Size of block data is aligned to */
} SampledSoundHeader;

	/* "FVER" chunk header */

typedef struct {
	long			timestamp;			/* Format version creation date */
} FormatVersionHeader;

#define AIFCVersion1 0xA2805140 		/* "AIFC" file format version #1 */

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

	/* extended2long(extended *ex):
	 *
	 *	Convert an Apple "SANE" 80 bit floating point number
	 *	into an integer value.
	 */

long
extended2long(const extended *ex)
{
	unsigned long mantissa;
	short exponent;
	long sign;

		/* We only need 32 bits precision */

	mantissa = ex->mantissa[0];

		/* Is the mantissa positive or negative? */

	exponent = ex->exponent;

	if(exponent & (1 << 15))
		sign = -1;
	else
		sign =	1;

		/* Unbias the exponent (strip the sign bit; the
		 * exponent is 15 bits wide)
		 */

	exponent = (exponent & ~(1 << 15)) - ((1 << 14) - 1);

		/* If the exponent is negative, set the mantissa to zero.
		 * We cannot represent integer values between 0 and 1.
		 */

	if(exponent < 0)
		mantissa = 0;
	else
	{
			/* We used only the upper 32 bits of the mantissa,
			 * which is what we have to make up for. Subtracting
			 * 31 from the exponent is actually dividing by
			 * 2^32.
			 */

		exponent -= 31;

			/* If the exponent is not negative, then the value
			 * the number represents will be larger than the
			 * original 64 bits of the mantissa would hold.
			 */

		if(exponent > 0)
			mantissa = (1L << 31) - 1;	/* == MAXINT */
		else
			mantissa >>= -exponent;		/* Keep the integer part of the number */
	}

		/* That's all... */

	return(sign * (long)mantissa);
}

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

VOID
CloseAll(VOID)
{
	if(rdargs != NULL)
	{
		FreeArgs(rdargs);
		rdargs = NULL;
	}

	if(IFFParseBase != NULL)
	{
		CloseLibrary(IFFParseBase);
		IFFParseBase = NULL;
	}

	if(ac != NULL)
	{
		CTO_ReleaseAudioContext(ac);
		ac = NULL;
	}

	if(ConciertoBase != NULL)
	{
		CloseLibrary(ConciertoBase);
		ConciertoBase = NULL;
	}
}

BOOL
OpenAll(VOID)
{
	IFFParseBase = OpenLibrary("iffparse.library",37);
	if(IFFParseBase == NULL)
		return(FALSE);

	ConciertoBase = OpenLibrary("concierto.library",0);
	if(ConciertoBase == NULL)
		return(FALSE);

	ac = CTO_ObtainAudioContext(0);
	if(ac == NULL)
		return(FALSE);

	rdargs = ReadArgs("NAMES/M",(LONG *)&names,NULL);
	if(rdargs == NULL)
	{
		PrintFault(IoErr(),"PlayAIFF");
		return(FALSE);
	}

	if(names == NULL)
	{
		PrintFault(ERROR_REQUIRED_ARG_MISSING,"PlayAIFF");
		return(FALSE);
	}

	return(TRUE);
}

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

VOID
CloseIFFFile(struct IFFHandle *handle)
{
	if(handle != NULL)
	{
		CloseIFF(handle);
		Close((BPTR)handle->iff_Stream);
		FreeIFF(handle);
	}
}

struct IFFHandle *
OpenIFFFile(const char *name)
{
	struct IFFHandle *handle;

	handle = AllocIFF();
	if(handle != NULL)
	{
		handle->iff_Stream = (ULONG)Open((STRPTR)name,MODE_OLDFILE);
		if(handle->iff_Stream != NULL)
		{
			InitIFFasDOS(handle);

			if(OpenIFF(handle,IFFF_READ) == 0)
				return(handle);

			Close(handle->iff_Stream);
		}

		FreeIFF(handle);
	}

	return(NULL);
}

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

#define OK	(0)
#define NOT	!

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

BOOL
LoadAIFF(
	struct IFFHandle *	handle,
	CommonChunk *		pCommonChunk,
	APTR *				pSampleLeft,
	APTR *				pSampleRight,
	ULONG *				pSampleSize)
{
	STATIC LONG Stops[5 * 2] =
	{
		ID_AIFF,ID_COMM,	/* AIFF chunks */
		ID_AIFF,ID_SSND,

		ID_AIFC,ID_FVER,	/* AIFC chunks */
		ID_AIFC,ID_COMM,
		ID_AIFC,ID_SSND
	};

	FormatVersionHeader formatHeader;
	SampledSoundHeader sampleHeader;
	ExtCommonChunk common;
	UBYTE *frameBuffer = NULL;
	ULONG framesInBuffer;
	LONG status;
	LONG size;
	UBYTE *sampleLeft,*sampleRight;
	LONG sampleSize;
	BOOL result = FALSE;

	sampleLeft	= NULL;
	sampleRight	= NULL;
	sampleSize	= 0;

	status = StopChunks(handle,Stops,5);
	if(status == OK)
	{
		struct ContextNode *chunk;
		ULONG bytesPerPoint;
		ULONG bytesToRead;
		BOOL done = FALSE;

		while(NOT done && status == OK && ParseIFF(handle,IFFPARSE_SCAN) == OK)
		{
			chunk = CurrentChunk(handle);

			switch(chunk->cn_ID)
			{
				case ID_FVER:

					size = ReadChunkBytes(handle,&formatHeader,sizeof(formatHeader));
					if(size != sizeof(formatHeader))
					{
						status = size;
					}
					else if (formatHeader.timestamp != AIFCVersion1)
					{
						status = ERROR_NOT_IMPLEMENTED;
					}

					break;

				case ID_COMM:

					if(chunk->cn_Type == ID_AIFF)
						bytesToRead = sizeof(CommonChunk);
					else
						bytesToRead = sizeof(ExtCommonChunk);

					size = ReadChunkBytes(handle,&common,bytesToRead);
					if(size != bytesToRead)
					{
						status = size;
						break;
					}

					if (chunk->cn_Type == ID_AIFC && common.compressionType != NO_COMPRESSION)
					{
						status = ERROR_NOT_IMPLEMENTED;
						break;
					}
					else if (common.numChannels > 2 || common.sampleSize > 16 || common.numSampleFrames < 1)
					{
						status = ERROR_NOT_IMPLEMENTED;
						break;
					}

					bytesPerPoint = (common.sampleSize + 7) / 8;

					sampleSize = common.numSampleFrames * bytesPerPoint;

					FreeVec(sampleLeft);
					FreeVec(sampleRight);

					sampleLeft = AllocVec(sampleSize,MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
					if(sampleLeft == NULL)
						status = ERROR_NO_FREE_STORE;

					if(common.numChannels > 1)
					{
						sampleRight = AllocVec(sampleSize,MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
						if(sampleRight == NULL)
							status = ERROR_NO_FREE_STORE;

						frameBuffer = (UBYTE *)AllocVec(4000,MEMF_ANY|MEMF_CLEAR);
						if(frameBuffer == NULL)
							status = ERROR_NO_FREE_STORE;

						framesInBuffer = 4000 / (bytesPerPoint * common.numChannels);
					}
					else
						sampleRight = NULL;

					break;

				case ID_SSND:

					size = ReadChunkBytes(handle,&sampleHeader,sizeof(sampleHeader));
					if(size != sizeof(sampleHeader))
					{
						status = size;
						break;
					}

					if(sampleHeader.offset > 0)
					{
						ULONG needed = sampleHeader.offset,skip;

						do
						{
							skip = min(needed,sampleSize);
							size = ReadChunkBytes(handle,sampleLeft,skip);

							if(size == skip)
							{
								needed -= skip;
							}
							else
							{
								status = size;
								break;
							}
						}
						while(needed > 0);
					}

					if(status == OK)
					{
						if(common.numChannels > 1)
						{
							UBYTE *channels[2];
							UBYTE *data;
							ULONG byte,channel,frame,frames,framesToGo;

							channels[0] = sampleLeft;
							channels[1] = sampleRight;

							framesToGo = common.numSampleFrames;

							do
							{
								frames = min(framesInBuffer,framesToGo);

								size = ReadChunkRecords(handle,frameBuffer,common.numChannels * bytesPerPoint,frames);
								if(size != frames)
								{
									status = size;
									break;
								}
								else
								{
									data = frameBuffer;

									for(frame = 0 ; frame < frames ; frame++)
									{
										for(channel = 0 ; channel < common.numChannels ; channel++)
										{
											for(byte = 0 ; byte < bytesPerPoint ; byte++)
												*channels[channel]++ = *data++;
										}
									}

									framesToGo -= frames;
								}
							}
							while(framesToGo > 0);
						}
						else
						{
							size = ReadChunkBytes(handle,sampleLeft,bytesPerPoint * common.numSampleFrames);
							if(size != bytesPerPoint * common.numSampleFrames)
							{
								status = size;
								break;
							}
						}

						if(status == OK)
						{
							memcpy(pCommonChunk,&common,sizeof(CommonChunk));

							result = TRUE;
							done = TRUE;
						}
					}

					break;
			}
		}
	}

	if(result)
	{
		*pSampleLeft	= sampleLeft;
		*pSampleRight	= sampleRight;
		*pSampleSize	= sampleSize;
	}
	else
	{
		FreeVec(sampleLeft);
		FreeVec(sampleRight);
	}

	FreeVec(frameBuffer);

	return(result);
}

VOID
PlayAIFF(const char *name)
{
	struct IFFHandle *handle;

	handle = OpenIFFFile(name);
	if(handle != NULL)
	{
		CommonChunk common;
		APTR sampleLeft,sampleRight;
		ULONG sampleBytes;

		if(LoadAIFF(handle,&common,&sampleLeft,&sampleRight,&sampleBytes))
		{
			LONG sampleRate;
			ULONG closestRate;
			LONG result;

			sampleRate = extended2long(&common.sampleRate);
			closestRate = CTO_FindNearestSampleRate(sampleRate);

			Printf("Playing... ");Flush(Output());

			result = CTO_StartPlayback(ac,
				DSPA_SampleDataLeft,	sampleLeft,
				DSPA_SampleDataRight,	sampleRight,
				DSPA_NumSampleBytes,	sampleBytes,
				DSPA_PlaybackRate,		closestRate,
				DSPA_NumChannels,		common.numChannels,
				DSPA_NumBitsPerSample,	(common.sampleSize + 7) & ~7,
				DSPA_WakeTask,			FindTask(NULL),
				DSPA_WakeSigBit,		SIGBREAKB_CTRL_C,
			TAG_DONE);

			if(result == 0)
			{
				Wait(SIGBREAKF_CTRL_C);

				CTO_Abort(ac);

				SetSignal(0,SIGBREAKF_CTRL_C);
			}
			else
			{
				Printf("error #%ld, ",result);
			}

			Printf("done.\n");

			FreeVec(sampleLeft);
			FreeVec(sampleRight);
		}

		CloseIFFFile(handle);
	}
}

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

int
main(void)
{
	if(OpenAll())
	{
		int i;

		for(i = 0 ; names[i] != NULL ; i++)
		{
			PlayAIFF(names[i]);

			if(CheckSignal(SIGBREAKF_CTRL_D))
			{
				PrintFault(ERROR_BREAK,"PlayAIFF");
				break;
			}
		}
	}

	CloseAll();

	return(0);
}
