/* DiskFileOutput.c */
/*****************************************************************************/
/*                                                                           */
/*    Out Of Phase:  Digital Music Synthesis on General Purpose Computers    */
/*    Copyright (C) 1994  Thomas R. Lawrence                                 */
/*                                                                           */
/*    This program is free software; you can redistribute it and/or modify   */
/*    it under the terms of the GNU General Public License as published by   */
/*    the Free Software Foundation; either version 2 of the License, or      */
/*    (at your option) any later version.                                    */
/*                                                                           */
/*    This program is distributed in the hope that it will be useful,        */
/*    but WITHOUT ANY WARRANTY; without even the implied warranty of         */
/*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
/*    GNU General Public License for more details.                           */
/*                                                                           */
/*    You should have received a copy of the GNU General Public License      */
/*    along with this program; if not, write to the Free Software            */
/*    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                           */
/*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
/*                                                                           */
/*****************************************************************************/

#include "MiscInfo.h"
#include "Audit.h"
#include "Debug.h"
#include "Definitions.h"

#include "DiskFileOutput.h"
#include "Files.h"
#include "Memory.h"
#include "Alert.h"
#include "ExecuteSynthesis.h"
#include "ClipWarnDialog.h"
#include "SynthProgressWindow.h"
#include "BufferedFileOutput.h"
#include "ErrorDaemon.h"


#define CANCELCHECKTIMER (50)


typedef struct
	{
		MyBoolean							UseStereo;
		long									SamplingRate;
		OutputNumBitsType			NumBitsOut;
		FileType*							OutputFile;

		void*									Buffer;
		long									BufferSizeInBytes;
		char*									DataOutThing;
		long									DataOutInBytes;

		long									TotalSampleCount;
		long									ClippedSampleCount;
		largefixedsigned			MaxClipExtent;

		long									CancelCheck;

		SynthWinRec*					Window;
	} StateRecord;


/* AIFF/AIFF-C File Format: */
/*     "FORM" */
/*     4-byte big endian form chunk length descriptor (minus 8 for "FORM" & this) */
/*     4-byte type */
/*        "AIFF" = AIFF format file */
/*        "AIFC" = AIFF-C format file */
/* in any order, these chunks can occur: */
/*   Version Chunk (this only occurs in AIFF-C files) */
/*     "FVER" */
/*     4-byte big endian length, which should always be the value 4 (four) */
/*     4-byte date code.  this is probably 0xA2805140 (stored big endian), but it */
/*          probably doesn't matter. */
/*   Common Chunk for AIFF files */
/*     "COMM" */
/*     4-byte big endian length. */
/*        always 18 for AIFF files */
/*     2-byte big endian number of channels */
/*     4-byte big endian number of sample frames */
/*     2-byte big endian number of bits per sample */
/*        a value in the domain 1..32 */
/*     10-byte extended precision number of frames per second */
/*   Common Chunk for AIFF-C files */
/*     "COMM" */
/*     4-byte big endian length. */
/*        22 + compression method string length for AIFF-C files */
/*     2-byte big endian number of channels */
/*     4-byte big endian number of sample frames */
/*     2-byte big endian number of bits per sample */
/*        a value in the domain 1..32 */
/*     10-byte extended precision number of frames per second */
/*     4-byte character code ID for the compression method */
/*        "NONE" means there is no compression method used */
/*     some characters in a string identifying the compression method */
/*        this must be padded to an even number of bytes, but the pad is */
/*        NOT included in the length descriptor for the chunk. */
/*        for uncompressed data, the string should be */
/*        "\x0enot compressed\x00", including the null, for 16 bytes. */
/*        the total chunk length is thus 38 bytes. */
/*   Sound Data Chunk */
/*     "SSND" */
/*     4-byte big endian number of bytes in sample data array */
/*     4-byte big endian offset to the first byte of sample data in the array */
/*     4-byte big endian number of bytes to which the sound data is aligned. */
/*     any length vector of raw sound data. */
/*        this must be padded to an even number of bytes, but the pad is */
/*        NOT included in the length descriptor for the chunk. */
/*        Samples are stored in an integral number of bytes, the smallest that */
/*        is required for the specified number of bits.  If this is not an even */
/*        multiple of 8, then the data is shifted left and the low bits are zeroed */
/*        Multichannel sound is interleaved with the left channel first. */
static MyBoolean			SetUpAIFFHeader(FileType* OutputFile, StateRecord* Info)
	{
		BufferedOutputRec*	Output;
		unsigned long				Mantissa;
		unsigned long				Exponent;
		char								StupidExtendedThing[10];

		Output = NewBufferedOutput(OutputFile);
		if (Output == NIL)
			{
			 FailurePoint1:
				return False;
			}

		/* 0..3 */
		/*     "FORM" */
		if (!WriteBufferedOutput(Output,4,"FORM"))
			{
			 FailurePoint2:
				EndBufferedOutput(Output);
				goto FailurePoint1;
			}

		/* 4..7 */
		/*     4-byte big endian form chunk length descriptor (minus 8 for "FORM" & this) */
		if (!WriteBufferedUnsignedLongBigEndian(Output,0)) /* RESOLVED LATER */
			{
				goto FailurePoint2;
			}

		/* 8..11 */
		/*     4-byte type */
		/*        "AIFC" = AIFF-C format file */
		if (!WriteBufferedOutput(Output,4,"AIFF"))
			{
				goto FailurePoint2;
			}

		/* 12..15 */
		/*     "COMM" */
		if (!WriteBufferedOutput(Output,4,"COMM"))
			{
				goto FailurePoint2;
			}

		/* 16..19 */
		/*     4-byte big endian length. */
		/*        always 18 for AIFF files */
		if (!WriteBufferedUnsignedLongBigEndian(Output,18))
			{
				goto FailurePoint2;
			}

		/* 20..21 */
		/*     2-byte big endian number of channels */
		if (Info->UseStereo)
			{
				if (!WriteBufferedUnsignedShortBigEndian(Output,2))
					{
						goto FailurePoint2;
					}
			}
		 else
			{
				if (!WriteBufferedUnsignedShortBigEndian(Output,1))
					{
						goto FailurePoint2;
					}
			}

		/* 22..25 */
		/*     4-byte big endian number of sample frames */
		if (!WriteBufferedUnsignedLongBigEndian(Output,0)) /* RESOLVED LATER */
			{
				goto FailurePoint2;
			}

		/* 26..27 */
		/*     2-byte big endian number of bits per sample */
		/*        a value in the domain 1..32 */
		switch (Info->NumBitsOut)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"SetUpAIFFHeader:  bad number of bits"));
					break;
				case eOutput8Bits:
					if (!WriteBufferedUnsignedShortBigEndian(Output,8))
						{
							goto FailurePoint2;
						}
					break;
				case eOutput16Bits:
					if (!WriteBufferedUnsignedShortBigEndian(Output,16))
						{
							goto FailurePoint2;
						}
					break;
				case eOutput24Bits:
					if (!WriteBufferedUnsignedShortBigEndian(Output,24))
						{
							goto FailurePoint2;
						}
					break;
				case eOutput32Bits:
					if (!WriteBufferedUnsignedShortBigEndian(Output,32))
						{
							goto FailurePoint2;
						}
					break;
			}

		/* 28..37 */
		/*     10-byte extended precision number of frames per second */
		/* extended 22050 = 400D AC44000000000000 */
		/* extended 22051 = 400D AC46000000000000 */
		/* extended 44100 = 400E AC44000000000000 */
		/* extended 44101 = 400E AC45000000000000 */
		Exponent = 0x401e;
		Mantissa = Info->SamplingRate;
		while ((Mantissa & 0x80000000) == 0)
			{
				Mantissa = Mantissa << 1;
				Exponent -= 1;
			}
		StupidExtendedThing[0] = (Exponent >> 8) & 0xff;
		StupidExtendedThing[1] = Exponent & 0xff;
		StupidExtendedThing[2] = (Mantissa >> 24) & 0xff;
		StupidExtendedThing[3] = (Mantissa >> 16) & 0xff;
		StupidExtendedThing[4] = (Mantissa >> 8) & 0xff;
		StupidExtendedThing[5] = Mantissa & 0xff;
		StupidExtendedThing[6] = 0;
		StupidExtendedThing[7] = 0;
		StupidExtendedThing[8] = 0;
		StupidExtendedThing[9] = 0;
		if (!WriteBufferedOutput(Output,10,StupidExtendedThing))
			{
				goto FailurePoint2;
			}

		/* 38..41 */
		/*     "SSND" */
		if (!WriteBufferedOutput(Output,4,"SSND"))
			{
				goto FailurePoint2;
			}

		/* 42..45 */
		/*     4-byte big endian number of bytes in sample data array */
		if (!WriteBufferedUnsignedLongBigEndian(Output,0)) /* RESOLVED LATER */
			{
				goto FailurePoint2;
			}

		/* 46..49 */
		/*     4-byte big endian offset to the first byte of sample data in the array */
		if (!WriteBufferedUnsignedLongBigEndian(Output,0))
			{
				goto FailurePoint2;
			}

		/* 50..53 */
		/*     4-byte big endian number of bytes to which the sound data is aligned. */
		if (!WriteBufferedUnsignedLongBigEndian(Output,0))
			{
				goto FailurePoint2;
			}

		/*     any length vector of raw sound data. */
		/*        this must be padded to an even number of bytes, but the pad is */
		/*        NOT included in the length descriptor for the chunk. */
		/*        Samples are stored in an integral number of bytes, the smallest that */
		/*        is required for the specified number of bits.  If this is not an even */
		/*        multiple of 8, then the data is shifted left and the low bits are zeroed */
		/*        Multichannel sound is interleaved with the left channel first. */

		if (!EndBufferedOutput(Output))
			{
				return False;
			}

		return True;
	}


/* update various size fields in the file */
static MyBoolean			ResolveAIFFHeader(FileType* OutputFile, StateRecord* Info)
	{
		long								TotalBytesOfSamples;
		MyBoolean						FileSuccess;
		char								Buffer[4];

		TotalBytesOfSamples = Info->TotalSampleCount;
		if (Info->UseStereo)
			{
				TotalBytesOfSamples *= 2;
			}
		switch (Info->NumBitsOut)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"ResolveAIFFHeader:  bad number of bits"));
					break;
				case eOutput8Bits:
					break;
				case eOutput16Bits:
					TotalBytesOfSamples *= 2;
					break;
				case eOutput24Bits:
					TotalBytesOfSamples *= 3;
					break;
				case eOutput32Bits:
					TotalBytesOfSamples *= 4;
					break;
			}

		FileSuccess = True;

		/* make sure file is an even number of bytes */
		if ((GetFilePosition(OutputFile) & 1) != 0)
			{
				char						Stupid[1] = {0};

				if (0 != WriteToFile(OutputFile,Stupid,1))
					{
						FileSuccess = False;
					}
			}

		/* chop off any crud from the end */
		FileSuccess = SetFileLength(OutputFile,GetFilePosition(OutputFile)) && FileSuccess;

		/* 4..7 */
		/*     4-byte big endian form chunk length descriptor (minus 8 for "FORM" & this) */
		if (SetFilePosition(OutputFile,4))
			{
				Buffer[0] = ((TotalBytesOfSamples + 54 - 8) >> 24) & 0xff;
				Buffer[1] = ((TotalBytesOfSamples + 54 - 8) >> 16) & 0xff;
				Buffer[2] = ((TotalBytesOfSamples + 54 - 8) >> 8) & 0xff;
				Buffer[3] = (TotalBytesOfSamples + 54 - 8) & 0xff;
				if (0 != WriteToFile(OutputFile,Buffer,4))
					{
						FileSuccess = False;
					}
			}
		 else
			{
				FileSuccess = False;
			}

		/* 22..25 */
		/*     4-byte big endian number of sample frames */
		if (SetFilePosition(OutputFile,22))
			{
				Buffer[0] = (Info->TotalSampleCount >> 24) & 0xff;
				Buffer[1] = (Info->TotalSampleCount >> 16) & 0xff;
				Buffer[2] = (Info->TotalSampleCount >> 8) & 0xff;
				Buffer[3] = Info->TotalSampleCount & 0xff;
				if (0 != WriteToFile(OutputFile,Buffer,4))
					{
						FileSuccess = False;
					}
			}
		 else
			{
				FileSuccess = False;
			}

		/* 42..45 */
		/*     4-byte big endian number of bytes in sample data array */
		if (SetFilePosition(OutputFile,42))
			{
				Buffer[0] = ((TotalBytesOfSamples + 8) >> 24) & 0xff;
				Buffer[1] = ((TotalBytesOfSamples + 8) >> 16) & 0xff;
				Buffer[2] = ((TotalBytesOfSamples + 8) >> 8) & 0xff;
				Buffer[3] = (TotalBytesOfSamples + 8) & 0xff;
				if (0 != WriteToFile(OutputFile,Buffer,4))
					{
						FileSuccess = False;
					}
			}
		 else
			{
				FileSuccess = False;
			}

		return FileSuccess;
	}


static MyBoolean			CallbackRoutine(StateRecord* Info, largefixedsigned* DataBlock,
												long NumFrames, MyBoolean* AbortPlaybackFlagOut)
	{
		long								Limit;
		void*								Buffer;
		long								BufferSizeInBytes;
		char*								DataOutThing;
		long								DataOutInBytes;
		long								Scan;
		largefixedsigned		TempValue;
		long								UnwritableBytes;

		Info->CancelCheck -= 1;
		if (Info->CancelCheck < 0)
			{
				Info->CancelCheck = CANCELCHECKTIMER;
				if (RelinquishCPUJudiciouslyCheckCancel())
					{
						switch (AskYesNoCancel("Are you sure you want to abort synthesis?",NIL,
							"Abort","Resume",NIL))
							{
								default:
									EXECUTE(PRERR(ForceAbort,"DiskFileOutput:CallbackRoutine:  bad value "
										"from AskYesNoCancel"));
									break;
								case eYes:
									*AbortPlaybackFlagOut = True;
									return True;
								case eNo:
									break;
							}
						/* at first glance, we might need to somehow reset the RelinquishCPU */
						/* sticky cancel flag, but AskYesNoCancel calls the event loop, so */
						/* it gets automatically reset. */
					}
			}

		/* calculate required buffer size */
		Limit = NumFrames;
		BufferSizeInBytes = NumFrames;
		DataOutInBytes = NumFrames;
		if (Info->UseStereo)
			{
				Limit *= 2; /* twice as many frames for stereo */
				BufferSizeInBytes *= 2;
				DataOutInBytes *= 2;
			}
		switch (Info->NumBitsOut)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"CallbackRoutine:  bad number of bits"));
					break;
				case eOutput8Bits:
					break;
				case eOutput16Bits:
					BufferSizeInBytes *= sizeof(short);
					DataOutInBytes *= 2;
					break;
				case eOutput24Bits:
					BufferSizeInBytes *= sizeof(largefixedsigned);
					DataOutInBytes *= 3;
					break;
				case eOutput32Bits:
					BufferSizeInBytes *= sizeof(largefixedsigned);
					DataOutInBytes *= 4;
					break;
			}
		if (Info->BufferSizeInBytes < BufferSizeInBytes)
			{
				Buffer = ResizePtr((char*)Info->Buffer,BufferSizeInBytes);
				if (Buffer == NIL)
					{
						AlertHalt("There is not enough memory available to continue synthesis.",NIL);
						return False;
					}
				Info->Buffer = Buffer;
				Info->BufferSizeInBytes = BufferSizeInBytes;
			}
		 else
			{
				Buffer = Info->Buffer;
			}
		if (Info->DataOutInBytes < DataOutInBytes)
			{
				DataOutThing = ResizePtr(Info->DataOutThing,DataOutInBytes);
				if (DataOutThing == NIL)
					{
						AlertHalt("There is not enough memory available to continue synthesis.",NIL);
						return False;
					}
				Info->DataOutThing = DataOutThing;
				Info->DataOutInBytes = DataOutInBytes;
			}
		 else
			{
				DataOutThing = Info->DataOutThing;
			}

		/* generate the data into the buffer */
		switch (Info->NumBitsOut)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"SynthToAIFFFile:  bad number of bits"));
					break;
				case eOutput8Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							PRNGCHK(DataBlock,&(DataBlock[Scan]),sizeof(DataBlock[Scan]));
							TempValue = DataBlock[Scan] + double2largefixed((double)1 / 65536);
							/* 1 / 65536 is .5 for 16 bit signal */
							if (TempValue > int2largefixed(1) - 1)
								{
									Info->ClippedSampleCount += 1;
									if (TempValue > Info->MaxClipExtent)
										{
											Info->MaxClipExtent = TempValue;
										}
									TempValue = int2largefixed(1) - 1;
								}
							else if (TempValue < - (int2largefixed(1) - 1))
								{
									Info->ClippedSampleCount += 1;
									if (- TempValue > Info->MaxClipExtent)
										{
											Info->MaxClipExtent = - TempValue;
										}
									TempValue = - (int2largefixed(1) - 1);
								}
							PRNGCHK(Buffer,&(((signed char*)Buffer)[Scan]),
								sizeof(((signed char*)Buffer)[Scan]));
							((signed char*)Buffer)[Scan] = TempValue >> (largefixed_precision - 8 + 1);
						}
					break;
				case eOutput16Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							PRNGCHK(DataBlock,&(DataBlock[Scan]),sizeof(DataBlock[Scan]));
							TempValue = DataBlock[Scan] + double2largefixed((double)1 / 65536);
							if (TempValue > int2largefixed(1) - 1)
								{
									Info->ClippedSampleCount += 1;
									if (TempValue > Info->MaxClipExtent)
										{
											Info->MaxClipExtent = TempValue;
										}
									TempValue = int2largefixed(1) - 1;
								}
							else if (TempValue < - (int2largefixed(1) - 1))
								{
									Info->ClippedSampleCount += 1;
									if (- TempValue > Info->MaxClipExtent)
										{
											Info->MaxClipExtent = - TempValue;
										}
									TempValue = - (int2largefixed(1) - 1);
								}
							PRNGCHK(Buffer,&(((signed short*)Buffer)[Scan]),
								sizeof(((signed short*)Buffer)[Scan]));
							((signed short*)Buffer)[Scan] = TempValue >> (largefixed_precision - 16 + 1);
						}
					break;
				case eOutput24Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							PRNGCHK(DataBlock,&(DataBlock[Scan]),sizeof(DataBlock[Scan]));
							TempValue = DataBlock[Scan];
							if (TempValue > int2largefixed(1) - 1)
								{
									Info->ClippedSampleCount += 1;
									if (TempValue > Info->MaxClipExtent)
										{
											Info->MaxClipExtent = TempValue;
										}
									TempValue = int2largefixed(1) - 1;
								}
							else if (TempValue < - (int2largefixed(1) - 1))
								{
									Info->ClippedSampleCount += 1;
									if (- TempValue > Info->MaxClipExtent)
										{
											Info->MaxClipExtent = - TempValue;
										}
									TempValue = - (int2largefixed(1) - 1);
								}
							PRNGCHK(Buffer,&(((signed short*)Buffer)[Scan]),
								sizeof(((signed short*)Buffer)[Scan]));
							((largefixedsigned*)Buffer)[Scan] = TempValue >> (largefixed_precision - 24 + 1);
						}
					break;
				case eOutput32Bits:
					/* 32-bits doesn't clip. */
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							PRNGCHK(DataBlock,&(DataBlock[Scan]),sizeof(DataBlock[Scan]));
							TempValue = DataBlock[Scan];
							PRNGCHK(Buffer,&(((signed short*)Buffer)[Scan]),
								sizeof(((signed short*)Buffer)[Scan]));
							((largefixedsigned*)Buffer)[Scan] = TempValue;
						}
					break;
			}

		/* reorder the data to be big-endian */
		switch (Info->NumBitsOut)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"SynthToAIFFFile:  bad number of bits"));
					break;
				case eOutput8Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							signed short				TempVal;

							PRNGCHK(Buffer,&(((signed char*)Buffer)[Scan]),
								sizeof(((signed char*)Buffer)[Scan]));
							TempVal = ((signed char*)Buffer)[Scan];
							PRNGCHK(DataOutThing,&(DataOutThing[Scan]),sizeof(DataOutThing[Scan]));
							DataOutThing[Scan] = TempVal;
						}
					break;
				case eOutput16Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							signed short				TempVal;

							PRNGCHK(Buffer,&(((signed short*)Buffer)[Scan]),
								sizeof(((signed short*)Buffer)[Scan]));
							TempVal = ((signed short*)Buffer)[Scan];
							PRNGCHK(DataOutThing,&(DataOutThing[2 * Scan + 0]),
								sizeof(DataOutThing[2 * Scan + 0]) * 2);
							DataOutThing[2 * Scan + 1] = TempVal & 0xff;
							DataOutThing[2 * Scan + 0] = (TempVal >> 8) & 0xff;
						}
					break;
				case eOutput24Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							largefixedsigned		TempVal;

							PRNGCHK(Buffer,&(((largefixedsigned*)Buffer)[Scan]),
								sizeof(((largefixedsigned*)Buffer)[Scan]));
							TempVal = ((largefixedsigned*)Buffer)[Scan];
							PRNGCHK(DataOutThing,&(DataOutThing[3 * Scan + 0]),
								sizeof(DataOutThing[3 * Scan + 0]) * 3);
							DataOutThing[3 * Scan + 2] = TempVal & 0xff;
							DataOutThing[3 * Scan + 1] = (TempVal >> 8) & 0xff;
							DataOutThing[3 * Scan + 0] = (TempVal >> 16) & 0xff;
						}
					break;
				case eOutput32Bits:
					for (Scan = 0; Scan < Limit; Scan += 1)
						{
							largefixedsigned		TempVal;

							PRNGCHK(Buffer,&(((largefixedsigned*)Buffer)[Scan]),
								sizeof(((largefixedsigned*)Buffer)[Scan]));
							TempVal = ((largefixedsigned*)Buffer)[Scan];
							PRNGCHK(DataOutThing,&(DataOutThing[4 * Scan + 0]),
								sizeof(DataOutThing[4 * Scan + 0]) * 4);
							DataOutThing[4 * Scan + 3] = TempVal & 0xff;
							DataOutThing[4 * Scan + 2] = (TempVal >> 8) & 0xff;
							DataOutThing[4 * Scan + 1] = (TempVal >> 16) & 0xff;
							DataOutThing[4 * Scan + 0] = (TempVal >> 24) & 0xff;
						}
					break;
			}

		/* write data to the file */
	 RetryPoint:
		UnwritableBytes = WriteToFile(Info->OutputFile,DataOutThing,DataOutInBytes);
		if (UnwritableBytes != 0)
			{
				switch (AskYesNoCancel("An error occurred writing to the file.  The disk "
					"may be full.  Do you want to try writing again?",NIL,"Retry","Abort",NIL))
					{
						default:
							EXECUTE(PRERR(ForceAbort,"SynthToAIFFFile:  bad value from AskYesNoCancel"));
							break;
						case eYes:
							/* back up */
							if (!SetFilePosition(Info->OutputFile,GetFilePosition(Info->OutputFile)
								- (UnwritableBytes - DataOutInBytes)))
								{
									AlertHalt("File position could not be reset.  Aborting synthesis.",NIL);
									return False;
								}
							goto RetryPoint;
						case eNo:
							/* clean up */
							SetFilePosition(Info->OutputFile,GetFilePosition(Info->OutputFile)
								- (UnwritableBytes - DataOutInBytes));
							return False;
					}
			}

		Info->TotalSampleCount += NumFrames;

		UpdateSynthWindow(Info->Window,Info->SamplingRate,Info->TotalSampleCount,
			Info->ClippedSampleCount,False);

		return True;
	}


/* this routine opens a file and dumps the data to it. */
void									SynthToAIFFFile(struct MainWindowRec* MainWindow,
												struct ArrayRec* ListOfTracks, struct TrackObjectRec* KeyTrack,
												long FrameToStartAt, long SamplingRate, long EnvelopeRate,
												MyBoolean UseStereo, LargeBCDType DefaultBeatsPerMinute,
												LargeBCDType OverallVolumeScalingReciprocal,
												MyBoolean InterpOverTime, MyBoolean InterpAcrossWaves,
												LargeBCDType ScanningGap, OutputNumBitsType NumBitsOut,
												MyBoolean ClipWarn)
	{
		StateRecord					StateInfo;
		SynthErrorCodes			SynthErrorReturnCode;
		FileSpec*						WhereToSaveFile;
		ErrorDaemonRec*			ErrorDaemon;

		CheckPtrExistence(MainWindow);
		CheckPtrExistence(ListOfTracks);
		CheckPtrExistence(KeyTrack);

		StateInfo.UseStereo = UseStereo;
		StateInfo.SamplingRate = SamplingRate;
		StateInfo.NumBitsOut = NumBitsOut;

		StateInfo.TotalSampleCount = 0;
		StateInfo.ClippedSampleCount = 0;
		StateInfo.MaxClipExtent = 0;

		StateInfo.CancelCheck = 0;

		StateInfo.Window = NewSynthWindow(EnvelopeRate / 2,True/*show clipping*/);
		if (StateInfo.Window == NIL)
			{
				AlertHalt("There is not enough memory available to perform synthesis.",NIL);
			 SetupFailurePoint1:
				return;
			}

		WhereToSaveFile = PutFile("AIFF Output File");
		if (WhereToSaveFile == NIL)
			{
			 SetupFailurePoint2:
				DisposeSynthWindow(StateInfo.Window);
				goto SetupFailurePoint1;
			}

		if (!CreateFile(WhereToSaveFile,ApplicationCreator,CODE4BYTES('A','I','F','F')))
			{
				AlertHalt("Unable to create the file.",NIL);
			 SetupFailurePoint3:
				DisposeFileSpec(WhereToSaveFile);
				goto SetupFailurePoint2;
			}

		if (!OpenFile(WhereToSaveFile,&(StateInfo.OutputFile),eReadAndWrite))
			{
				AlertHalt("Unable to open the file for writing.",NIL);
			 SetupFailurePoint4:
				goto SetupFailurePoint3;
			}

		if (!SetUpAIFFHeader(StateInfo.OutputFile,&StateInfo))
			{
				AlertHalt("Unable to write data to the file.",NIL);
			 SetupFailurePoint5:
				CloseFile(StateInfo.OutputFile);
				goto SetupFailurePoint4;
			}

		ErrorDaemon = NewErrorDaemon();
		if (ErrorDaemon == NIL)
			{
				AlertHalt("There is not enough memory available to perform synthesis.",NIL);
			 SetupFailurePoint6:
				goto SetupFailurePoint5;
			}

		StateInfo.Buffer = AllocPtrCanFail(0,"StateInfo.Buffer");
		if (StateInfo.Buffer == NIL)
			{
				AlertHalt("There is not enough memory available to perform synthesis.",NIL);
			 SetupFailurePoint7:
				DisposeErrorDaemon(ErrorDaemon);
				goto SetupFailurePoint6;
			}
		StateInfo.BufferSizeInBytes = 0;

		StateInfo.DataOutThing = AllocPtrCanFail(0,"StateInfo.DataOutThing");
		if (StateInfo.DataOutThing == NIL)
			{
				AlertHalt("There is not enough memory available to perform synthesis.",NIL);
			 SetupFailurePoint8:
				ReleasePtr(StateInfo.Buffer);
				goto SetupFailurePoint7;
			}
		StateInfo.DataOutInBytes = 0;

		SynthErrorReturnCode = Synthesizer(MainWindow,
			(MyBoolean (*)(void*,largefixedsigned*,long,MyBoolean*))&CallbackRoutine,
			&StateInfo,ListOfTracks,KeyTrack,FrameToStartAt,SamplingRate,EnvelopeRate,
			UseStereo,DefaultBeatsPerMinute,OverallVolumeScalingReciprocal,InterpOverTime,
			InterpAcrossWaves,ScanningGap,ErrorDaemon);

		UpdateSynthWindow(StateInfo.Window,StateInfo.SamplingRate,StateInfo.TotalSampleCount,
			StateInfo.ClippedSampleCount,True);

		if (!ResolveAIFFHeader(StateInfo.OutputFile,&StateInfo))
			{
				AlertHalt("Unable to properly finalize the data in the file.  It will "
					"probably be unusable without manual repair.",NIL);
			}

		switch (SynthErrorReturnCode)
			{
				default:
					EXECUTE(PRERR(AllowResume,
						"SynthToSoundDevice:  bad return code from Synthesizer()"));
					break;
				case eSynthDone:
					break;
				case eSynthNoMemory:
					AlertHalt("There is not enough memory available to continue synthesis.",NIL);
					break;
				case eSynthUserCancelled:
					break;
				/* case eSynthProgramError: */
					break;
				case eSynthPrereqError:
					break;
				case eSynthUndefinedInstrumentError:
					break;
				case eSynthDataSubmitError:
					break;
				case eSynthDuplicateNames:
					break;
			}

		CloseFile(StateInfo.OutputFile);
		DisposeFileSpec(WhereToSaveFile);
		DisposeSynthWindow(StateInfo.Window);
		ReleasePtr(StateInfo.Buffer);
		ReleasePtr(StateInfo.DataOutThing);

		if (ErrorDaemonDidClampingOccur(ErrorDaemon))
			{
				ClampWarnDialog(ErrorDaemonGetMaxClamping(ErrorDaemon),
					ErrorDaemonGetMaxClamping(ErrorDaemon)
					* LargeBCD2Double(OverallVolumeScalingReciprocal));
			}
		 else
			{
				if (ClipWarn && (StateInfo.ClippedSampleCount != 0))
					{
						ClipWarnDialog(StateInfo.ClippedSampleCount,StateInfo.TotalSampleCount,
							largefixed2double(StateInfo.MaxClipExtent),
							largefixed2double(StateInfo.MaxClipExtent)
							* LargeBCD2Double(OverallVolumeScalingReciprocal),False);
					}
			}

		DisposeErrorDaemon(ErrorDaemon);
	}
