/* SampleDeviceOutput.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 "SampleDeviceOutput.h"
#include "SampleConsts.h"
#include "ExecuteSynthesis.h"
#include "Memory.h"
#include "SoundOutput.h"
#include "Alert.h"
#include "EventLoop.h"
#include "ClipWarnDialog.h"
#include "SynthProgressWindow.h"
#include "ErrorDaemon.h"


#define NUMBEROFBUFFERS (8)
#define INITIALNUMBUFFERS (2)

#define CANCELCHECKTIMER (50)


typedef struct
	{
		MyBoolean							UseStereo;
		long									SamplingRate;
		OutputNumBitsType			NumBitsOut;

		void*									CurrentBuffer;
		long									TotalFramesPerBuffer;
		long									CurrentBufferIndex;
		long									TotalSampleCount;
		long									ClippedSampleCount;
		largefixedsigned			MaxClipExtent;

		long									CancelCheck;

		SynthWinRec*					Window;
	} StateRecord;


static MyBoolean			CallbackRoutine(StateRecord* Info, largefixedsigned* DataBlock,
												long NumFrames, MyBoolean* AbortPlaybackFlagOut)
	{
		long								Limit;
		long								BufferOffset;
		void*								Buffer;
		long								Scan;
		largefixedsigned		TempValue;

		Info->CancelCheck -= 1;
		if (Info->CancelCheck < 0)
			{
				Info->CancelCheck = CANCELCHECKTIMER;
				if (RelinquishCPUJudiciouslyCheckCancel())
					{
						*AbortPlaybackFlagOut = True;
						return True;
					}
			}

		if (NumFrames + Info->CurrentBufferIndex > Info->TotalFramesPerBuffer)
			{
				/* submit the buffer & get another */
				SubmitBuffer((char*)Info->CurrentBuffer,Info->CurrentBufferIndex,NIL,NIL);
				Info->CurrentBuffer = NIL;
				do
					{
						Info->CurrentBuffer = CheckOutSoundBuffer();
						if (Info->CurrentBuffer == NIL)
							{
								/* spinwait until a buffer is available */
								if (RelinquishCPUCheckCancel())
									{
										*AbortPlaybackFlagOut = True;
										return True;
									}
							}
					} while (Info->CurrentBuffer == NIL);
				Info->CurrentBufferIndex = 0;

				if (NumFrames + Info->CurrentBufferIndex > Info->TotalFramesPerBuffer)
					{
						/* buffer overrun! */
						AlertHalt("The output buffer space is too small.  Increase the "
							"buffer seconds setting.",NIL);
						return False;
					}
			}

		Limit = NumFrames;
		BufferOffset = Info->CurrentBufferIndex;
		if (Info->UseStereo)
			{
				Limit *= 2; /* twice as many frames for stereo */
				BufferOffset *= 2;
			}
		if (Info->NumBitsOut == eOutput8Bits)
			{
				/* conversion to 8-bits */
				Buffer = (signed char*)(Info->CurrentBuffer) + BufferOffset;
				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(Info->CurrentBuffer,&(((signed char*)Buffer)[Scan]),
							sizeof(((signed char*)Buffer)[Scan]));
						((signed char*)Buffer)[Scan] = TempValue >> (largefixed_precision - 8 + 1);
					}
			}
		 else
			{
				/* conversion to 16-bits */
				Buffer = (signed short*)(Info->CurrentBuffer) + BufferOffset;
				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(Info->CurrentBuffer,&(((signed short*)Buffer)[Scan]),
							sizeof(((signed short*)Buffer)[Scan]));
						((signed short*)Buffer)[Scan] = TempValue >> (largefixed_precision - 16 + 1);
					}
			}
		Info->CurrentBufferIndex += NumFrames;

		Info->TotalSampleCount += NumFrames;

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

		return True;
	}


/* this routine opens the sound channel & performs resampling to it. */
void									SynthToSoundDevice(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,
												LargeBCDType SecondsOfBuffering, MyBoolean ClipWarn)
	{
		StateRecord					StateInfo;
		SynthErrorCodes			SynthErrorReturnCode;
		ErrorDaemonRec*			ErrorDaemon;

		CheckPtrExistence(MainWindow);
		CheckPtrExistence(ListOfTracks);
		CheckPtrExistence(KeyTrack);
		if (SecondsOfBuffering < Double2LargeBCD(1))
			{
				SecondsOfBuffering = Double2LargeBCD(1);
			}
		if (SamplingRate < MINSAMPLINGRATE)
			{
				SamplingRate = MINSAMPLINGRATE;
			}

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

		StateInfo.CurrentBuffer = NIL;
		StateInfo.TotalFramesPerBuffer = (LargeBCD2Single(SecondsOfBuffering)
			* SamplingRate) / NUMBEROFBUFFERS;
		StateInfo.CurrentBufferIndex = 0;
		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;
			}

		if (!OpenSoundChannel(SamplingRate,UseStereo ? eStereo : eMono,
			(NumBitsOut == eOutput8Bits) ? e8bit : e16bit,StateInfo.TotalFramesPerBuffer,
			NUMBEROFBUFFERS,INITIALNUMBUFFERS))
			{
				AlertHalt("Unable to open the sound output device.",NIL);
			 SetupFailurePoint2:
				DisposeSynthWindow(StateInfo.Window);
				goto SetupFailurePoint1;
			}

		StateInfo.CurrentBuffer = CheckOutSoundBuffer();
		if (StateInfo.CurrentBuffer == NIL)
			{
				/* there should initially be a buffer available */
				AlertHalt("Unable to access sound buffer.",NIL);
			 SetupFailurePoint3:
				CloseSoundChannel(NIL,NIL);
				goto SetupFailurePoint2;
			}

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

		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 ((StateInfo.CurrentBuffer != NIL) && (SynthErrorReturnCode == eSynthDone))
			{
				SubmitBuffer((char*)StateInfo.CurrentBuffer,StateInfo.CurrentBufferIndex,NIL,NIL);
				StateInfo.CurrentBuffer = 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:
					AlertHalt("An error occurred while sending data to output device.",NIL);
					break;
				case eSynthDuplicateNames:
					break;
			}

		if (SynthErrorReturnCode == eSynthUserCancelled)
			{
				KillSoundChannel(); /* don't wait for buffers to play out */
			}
		 else
			{
				CloseSoundChannel(NIL,NIL);
			}
		DisposeSynthWindow(StateInfo.Window);

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