/*
 * $Id: Record8SVX.c 1.5 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;
	LONG *		pSeconds;
	LONG *		pRate;
	LONG *		pVolume;
	STRPTR		Source;

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

struct RDArgs *rdargs;
struct Parameters params;

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

	/* Maximum supported replay rate, as per the
	 * "Amiga Hardware Reference Manual".
	 */

#define MAX_SAMPLE_RATE 28867

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

#define ID_8SVX MAKE_ID('8', 'S', 'V', 'X')
#define ID_VHDR MAKE_ID('V', 'H', 'D', 'R')
#define ID_BODY MAKE_ID('B', 'O', 'D', 'Y')
#define ID_CHAN MAKE_ID('C', 'H', 'A', 'N')

typedef LONG Fixed;				/* A fixed-point value, 16 bits to the left of
								 * the point and 16 to the right. A Fixed is a
								 * number of 2**16ths, i.e. 65536ths. */
#define Unity 0x10000L			/* Unity = Fixed 1.0 = maximum volume */

/* sCompression: Choice of compression algorithm applied to the samples. */
#define sCmpNone		0		/* not compressed */
#define sCmpFibDelta	1		/* Fibonacci-delta encoding (Appendix C) */
								/* Could be more kinds in the future. */
typedef struct {
	ULONG	oneShotHiSamples,	/* # samples in the high octave 1-shot part */
			repeatHiSamples,	/* # samples in the high octave repeat part */
			samplesPerHiCycle;	/* # samples/cycle in high octave, else 0 */
	UWORD	samplesPerSec;		/* data sampling rate */
	UBYTE	ctOctave,			/* # of octaves of waveforms */
			sCompression;		/* data compression technique used */
	Fixed	volume;				/* playback nominal volume from 0 to Unity
								 * (full volume). Map this value into
								 * the output hardware's dynamic range.
								 */
} Voice8Header;

#define CHANNEL_Right	4L
#define CHANNEL_Left	2L
#define CHANNEL_Stereo	6L

typedef long sampletype;

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

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.Seconds	= 10;
	params.Rate		= 22050;
	params.Volume	= 15;
	params.SourceID	= RECORDSRC_Switcher;

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

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

	if(params.Names == NULL)
	{
		PrintFault(ERROR_REQUIRED_ARG_MISSING,"Record8SVX");
		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
Write8SVX(
	struct IFFHandle *	handle,
	WORD				numChannels,
	ULONG				numSampleFrames,
	LONG				sampleRate,
	BYTE *				sampleLeft,
	BYTE *				sampleRight)
{
	LONG status = OK;
	LONG size;
	BOOL result;

	if(status == OK)
	{
		status = PushChunk(handle, ID_8SVX, ID_FORM, IFFSIZE_UNKNOWN);
		if(status == OK)
		{
			status = PushChunk(handle, 0, ID_VHDR, sizeof(Voice8Header));
			if(status == OK)
			{
				Voice8Header vh;

				memset(&vh,0,sizeof(vh));

				vh.oneShotHiSamples		= numSampleFrames;
				vh.samplesPerSec		= sampleRate;
				vh.ctOctave				= 1;
				vh.sCompression			= sCmpNone;
				vh.volume				= Unity;

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

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

		if(status == OK && numChannels == 2)
		{
			status = PushChunk(handle, 0, ID_CHAN, sizeof(sampletype));
			if(status == OK)
			{
				sampletype st = CHANNEL_Stereo;

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

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

		if(status == OK)
		{
			status = PushChunk(handle, 0, ID_BODY, numSampleFrames * numChannels);
			if(status == OK)
			{
				size = WriteChunkBytes(handle,sampleLeft,numSampleFrames);
				if(size != numSampleFrames)
					status = size;
			}

			if(status == OK && numChannels == 2)
			{
				size = WriteChunkBytes(handle,sampleRight,numSampleFrames);
				if(size != numSampleFrames)
					status = size;
			}

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

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

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

	return(result);
}

BOOL
Record8SVX(
	const char *	name,
	LONG			numChannels,
	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;

	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,		8,
			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,"Record8SVX");
			else
			{
				struct IFFHandle *handle;

				handle = OpenIFFFile(name);
				if(handle != NULL)
				{
					Printf("Writing... ");Flush(Output());
					result = Write8SVX(handle,numChannels,sampleBytes,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;
		int i;
		BOOL result;

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

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

			if(!result)
				break;

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

	CloseAll();

	return(0);
}
