/*
 * $Id: RecordAIFF.c 1.6 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;
BYTE recordingSignal = -1;

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

typedef long BOOLEAN;

struct Parameters
{
	STRPTR *	Names;
	BOOLEAN		Stereo;
	BOOLEAN		Sixteen;
	LONG *		pSeconds;
	LONG *		pRate;
	LONG *		pVolume;
	STRPTR		Source;

	LONG		Seconds;
	LONG		Rate;
	LONG		Volume;
	LONG		SourceID;
};

struct RDArgs *rdargs;
struct Parameters params;

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

	/* 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_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;

	/* "SSND" chunk header */

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

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

void
ulong2extended(unsigned long value,extended *ex)
{
	ex->exponent = 31 + 16383;

	while((value & 0x80000000) == 0)
	{
		value <<= 1;

		ex->exponent--;
	}

	ex->mantissa[0] = value;
	ex->mantissa[1] = 0;
}

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

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

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

	if(recordingSignal != -1)
	{
		FreeSignal(recordingSignal);
		recordingSignal = -1;
	}

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

	recordingSignal = AllocSignal(-1);
	if(recordingSignal == -1)
		return(FALSE);

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

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

	params.Stereo	= FALSE;
	params.Sixteen	= FALSE;
	params.Seconds	= 10;
	params.Rate		= 22050;
	params.Volume	= 15;
	params.SourceID	= RECORDSRC_Switcher;

	rdargs = ReadArgs("NAME/M,STEREO/S,16BIT/S,SECONDS/K/N,RATE/K/N,VOLUME/K/N,SOURCE/K",
	                  (LONG *)&params,NULL);

	if(rdargs == NULL)
	{
		PrintFault(IoErr(),"RecordAIFF");
		return(FALSE);
	}

	if(params.Names == NULL)
	{
		PrintFault(ERROR_REQUIRED_ARG_MISSING,"RecordAIFF");
		return(FALSE);
	}

	if(params.pSeconds != NULL)
		params.Seconds = *params.pSeconds;

	if(params.pRate != NULL)
		params.Rate = *params.pRate;

	if(params.pVolume != NULL)
		params.Volume = *params.pVolume;

	if(params.Seconds < 5)
		params.Seconds = 5;

	if(params.Volume < 1)
		params.Volume = 1;
	else if (params.Volume > 15)
		params.Volume = 15;

	if(params.Source != NULL)
	{
		STATIC STRPTR sources[] =
		{
			"Microphone",
			"Video",
			"Switcher",
			"Mixer"
		};
		const int same = 0;
		int i;

		for(i = RECORDSRC_Microphone ; i <= RECORDSRC_Mixer ; i++)
		{
			if(stricmp(params.Source,sources[i]) == same)
			{
				params.SourceID = i;
				break;
			}
		}
	}

	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_NEWFILE);
		if(handle->iff_Stream != NULL)
		{
			InitIFFasDOS(handle);

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

			Close(handle->iff_Stream);
		}

		FreeIFF(handle);
	}

	return(NULL);
}

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

#define OK (0)

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

BOOL
WriteAIFF(
	struct IFFHandle *	handle,
	short				numChannels,
	unsigned long		numSampleFrames,
	short				bitsPerSample,
	long				sampleRate,
	BYTE *				sampleLeft,
	BYTE *				sampleRight)
{
	BYTE *frameBuffer = NULL;
	ULONG framesInBuffer = 0;
	LONG status = OK;
	LONG size;
	LONG bytesPerPoint;
	BOOL result;

	bytesPerPoint = (bitsPerSample + 7) / 8;

	if(numChannels > 1)
	{
		frameBuffer = (BYTE *)AllocVec(4000,MEMF_ANY | MEMF_CLEAR);
		if(frameBuffer == NULL)
			status = ERROR_NO_FREE_STORE;

		framesInBuffer = 4000 / (numChannels * bytesPerPoint);
	}

	if(status == OK)
	{
		status = PushChunk(handle, ID_AIFF, ID_FORM, IFFSIZE_UNKNOWN);
		if(status == OK)
		{
			status = PushChunk(handle, 0, ID_COMM, sizeof(CommonChunk));
			if(status == OK)
			{
				CommonChunk common;

				common.numChannels		= numChannels;
				common.numSampleFrames	= numSampleFrames;
				common.sampleSize		= bitsPerSample;

				ulong2extended(sampleRate,&common.sampleRate);

				size = WriteChunkBytes(handle,&common,sizeof(common));
				if(size != sizeof(common))
					status = size;
			}

			if(status == OK)
				status = PopChunk(handle);
		}

		if(status == OK)
		{
			status = PushChunk(handle, 0, ID_SSND, sizeof(SampledSoundHeader) + numSampleFrames * numChannels * bytesPerPoint);
			if(status == OK)
			{
				SampledSoundHeader sampleHeader;

				sampleHeader.offset		= 0;
				sampleHeader.blockSize	= 0;

				size = WriteChunkBytes(handle,&sampleHeader,sizeof(sampleHeader));
				if(size != sizeof(sampleHeader))
					status = size;
			}

			if(status == OK)
			{
				if(numChannels > 1)
				{
					BYTE *channels[2];
					LONG frame,channel,byte,framesComplete;
					BYTE *data;

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

					framesComplete = 0;
					data = frameBuffer;

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

						framesComplete++;

						if(framesComplete == framesInBuffer)
						{
							LONG bytes = framesComplete * numChannels * bytesPerPoint;

							size = WriteChunkBytes(handle,frameBuffer,bytes);
							if(size != bytes)
							{
								status = size;
								break;
							}

							framesComplete = 0;
							data = frameBuffer;
						}
					}

					if(framesComplete > 0)
					{
						LONG bytes = framesComplete * numChannels * bytesPerPoint;

						size = WriteChunkBytes(handle,frameBuffer,bytes);
						if(size != bytes)
							status = size;
					}
				}
				else
				{
					LONG bytes = numSampleFrames * bytesPerPoint;

					size = WriteChunkBytes(handle,sampleLeft,bytes);
					if(size != bytes)
						status = size;
				}
			}

			if(status == OK)
				status = PopChunk(handle);
		}

		if(status == OK)
			status = PopChunk(handle);
	}

	FreeVec(frameBuffer);

	result = (BOOL)(status == OK);

	return(result);
}

BOOL
RecordAIFF(
	const char *	name,
	LONG			numChannels,
	LONG			bitsPerSample,
	LONG			seconds,
	LONG			rate,
	LONG			volume,
	LONG			source)
{
	APTR sampleLeft,sampleRight;
	ULONG sampleBytes;
	LONG sampleRate;
	BOOL result = FALSE;

	sampleRate = CTO_FindNearestSampleRate(rate);
	sampleBytes = seconds * sampleRate * (bitsPerSample / 8);

	sampleLeft = AllocVec(sampleBytes,MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
	sampleRight = NULL;

	if(numChannels == 2)
	{
		sampleRight = AllocVec(sampleBytes,MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
		if(sampleRight == NULL)
		{
			FreeVec(sampleLeft);
			sampleLeft = NULL;
		}
	}

	if(sampleLeft != NULL)
	{
		LONG status;

		Printf("Name \"%s\", recording rate = %lD Hz, %ld seconds\n",name,sampleRate,seconds);

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

		status = CTO_StartRecording(ac,
			DSPA_SampleDataLeft,	sampleLeft,
			DSPA_SampleDataRight,	sampleRight,
			DSPA_NumSampleBytes,	sampleBytes,
			DSPA_RecordingRate,		sampleRate,
			DSPA_RecordingSource,	source,
			DSPA_LeftInputVolume,	(CTO_VOLUME_Unity * volume) / 15,
			DSPA_RightInputVolume,	(CTO_VOLUME_Unity * volume) / 15,
			DSPA_RecordMonitorVolume,CTO_VOLUME_Unity,
			DSPA_NumChannels,		numChannels,
			DSPA_NumBitsPerSample,	bitsPerSample,
			DSPA_WakeTask,			FindTask(NULL),
			DSPA_WakeSigBit,		recordingSignal,
		TAG_DONE);

		if(status == 0)
		{
			ULONG signals;

			signals = Wait(SIGBREAKF_CTRL_C | (1UL << recordingSignal));

			CTO_Abort(ac);

			if((signals & SIGBREAKF_CTRL_C) != 0)
				PrintFault(ERROR_BREAK,"RecordAIFF");
			else
			{
				struct IFFHandle *handle;

				handle = OpenIFFFile(name);
				if(handle != NULL)
				{
					Printf("Writing... ");Flush(Output());
					result = WriteAIFF(handle,numChannels,sampleBytes / (bitsPerSample / 8),bitsPerSample,sampleRate,sampleLeft,sampleRight);
					Printf("done.\n");

					CloseIFFFile(handle);
				}
			}
		}
		else
		{
			Printf("error %ld\n",status);
		}

		FreeVec(sampleLeft);
		FreeVec(sampleRight);
	}

	return(result);
}

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

int
main(void)
{
	if(OpenAll())
	{
		int channels,bits;
		int i;
		BOOL result;

		if(params.Stereo)
			channels = 2;
		else
			channels = 1;

		if(params.Sixteen)
			bits = 16;
		else
			bits = 8;

		for(i = 0 ; params.Names[i] != NULL ; i++)
		{
			result = RecordAIFF(params.Names[i],
			                    channels,
			                    bits,
			                    params.Seconds,
			                    params.Rate,
			                    params.Volume,
			                    params.SourceID);

			if(!result)
				break;

			if(CheckSignal(SIGBREAKF_CTRL_C))
			{
				PrintFault(ERROR_BREAK,"RecordAIFF");
				break;
			}
		}
	}

	CloseAll();

	return(0);
}
