/* ExecuteSynthesis.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 "ExecuteSynthesis.h"
#include "Memory.h"
#include "MainWindowStuff.h"
#include "LinearTransition.h"
#include "PlayTrackInfoThang.h"
#include "Array.h"
#include "WaveTableOscControl.h"
#include "EnvelopeState.h"
#include "LFOGenerator.h"
#include "InstrList.h"
#include "InstrObject.h"
#include "TrackObject.h"
#include "SampleOscControl.h"
#include "Fractions.h"
#include "FrameObject.h"
#include "DeterminedNoteStructure.h"
#include "TempoController.h"
#include "OscBankPlayer.h"
#include "DataMunging.h"
#include "Alert.h"
#include "CheckNameUniqueness.h"
#include "ErrorDaemon.h"
#include "TrackEffectGenerator.h"
#include "DelayLine.h"
#include "NLProc.h"
#include "FilterFirstOrderLowpass.h"
#include "FilterFirstOrderHighpass.h"
#include "FilterSecondOrderReson.h"
#include "FilterSecondOrderZero.h"
#include "FilterButterworthLowpass.h"
#include "FilterButterworthHighpass.h"
#include "FilterButterworthBandpass.h"
#include "FilterButterworthBandreject.h"
#include "FilterNull.h"
#include "FilterArray.h"
#include "Analyzer.h"
#include "EffectSpecList.h"
#include "OscEffectGenerator.h"
#include "OscFilterArray.h"
#include "OscDelayLine.h"
#include "OscNLProc.h"
#include "FilterParametricEqualizer.h"
#include "FilterResonantLowpass.h"


typedef struct PlayListNodeRec
	{
		struct PlayListNodeRec*	Next;
		PlayTrackInfoRec*				ThisTrack;
	} PlayListNodeRec;


/* build the list of objects involved in playing. */
static SynthErrorCodes	BuildPlayList(PlayListNodeRec** ListOut,
												ArrayRec* TrackObjectList, MyBoolean InStereoFlag,
												MainWindowRec* MainWindow,
												LargeBCDType OverallVolumeScalingReciprocal,
												long SamplingRate, long EnvelopeRate, MyBoolean TimeInterp,
												MyBoolean WaveInterp, TempoControlRec* TempoControl,
												long ScanningGapWidthInEnvelopeTicks,
												ErrorDaemonRec* ErrorDaemon, TrackEffectGenRec* ScoreEffectProcessor)
	{
		PlayListNodeRec*		TrackPlayList;
		long								Scan;
		long								Limit;
		InstrListRec*				InstrList;
		SynthErrorCodes			Error;

		/* build list of tracks */
		CheckPtrExistence(MainWindow);
		CheckPtrExistence(TrackObjectList);
		InstrList = MainWindowGetInstrList(MainWindow);
		TrackPlayList = NIL;
		Limit = ArrayGetLength(TrackObjectList);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				TrackObjectRec*			PossibleTrack;
				PlayListNodeRec*		NewListNode;
				PlayTrackInfoRec*		TrackPlayObject;
				InstrObjectRec*			BaseInstrument;
				char*								InstrumentNameCopy;

				/* get the track */
				PossibleTrack = (TrackObjectRec*)ArrayGetElement(TrackObjectList,Scan);
				CheckPtrExistence(PossibleTrack);
				/* get the instrument to be used */
				InstrumentNameCopy = TrackObjectGetInstrName(PossibleTrack);
				if (InstrumentNameCopy == NIL)
					{
						Error = eSynthNoMemory;
						goto TrackListBuildFailure1;
					}
				BaseInstrument = InstrListLookupNamedInstr(InstrList,InstrumentNameCopy);
				ReleasePtr(InstrumentNameCopy);
				if (BaseInstrument == NIL)
					{
						char*								TrackName;
						MyBoolean						MessageSuccess = False;

						TrackName = TrackObjectGetNameCopy(PossibleTrack);
						if (TrackName != NIL)
							{
								char*								NullTerminated;

								NullTerminated = BlockToStringCopy(TrackName);
								if (NullTerminated != NIL)
									{
										AlertHalt("The track '_' uses a nonexistent instrument.",
											NullTerminated);
										MessageSuccess = True;
										ReleasePtr(NullTerminated);
									}
								ReleasePtr(TrackName);
							}
						if (!MessageSuccess)
							{
								AlertHalt("A track uses a nonexistent instrument.",NIL);
							}
						Error = eSynthUndefinedInstrumentError;
						goto TrackListBuildFailure1;
					}
				/* build the track */
				TrackPlayObject = NewPlayTrackInfo(PossibleTrack,
					GetInstrObjectRawData(BaseInstrument),InStereoFlag,
					OverallVolumeScalingReciprocal,SamplingRate,EnvelopeRate,TimeInterp,
					WaveInterp,TempoControl,ScanningGapWidthInEnvelopeTicks,ErrorDaemon,
					MainWindow,ScoreEffectProcessor);
				if (TrackPlayObject == NIL)
					{
						Error = eSynthNoMemory;
					 TrackListBuildFailure1:
						while (TrackPlayList != NIL)
							{
								PlayListNodeRec*		Temp;

								DisposePlayTrackInfo(TrackPlayList->ThisTrack);
								Temp = TrackPlayList;
								TrackPlayList = TrackPlayList->Next;
								ReleasePtr((char*)Temp);
							}
						return Error;
					}
				/* add this one to the list */
				NewListNode = (PlayListNodeRec*)AllocPtrCanFail(sizeof(PlayListNodeRec),
					"PlayListNodeRec");
				if (NewListNode == NIL)
					{
						Error = eSynthNoMemory;
					 TrackListBuildFailure2:
						DisposePlayTrackInfo(TrackPlayObject);
						goto TrackListBuildFailure1;
					}
				NewListNode->ThisTrack = TrackPlayObject;
				NewListNode->Next = TrackPlayList;
				TrackPlayList = NewListNode;
			}
		*ListOut = TrackPlayList;
		return eSynthDone;
	}


/* this routine scans through the key playlist and determines the exact point */
/* at which playback should begin. */
static void						FindStartPoint(TrackObjectRec* KeyTrack, long FrameToStartAt,
												FractionRec* StartTimeOut)
	{
		long								Scan;
		FractionRec					Counter;

		CheckPtrExistence(KeyTrack);
		Counter.Integer = 0;
		Counter.Fraction = 0;
		Counter.Denominator = (64*3*5*7*2);
		ERROR(FrameToStartAt > TrackObjectGetNumFrames(KeyTrack),PRERR(ForceAbort,
			"FindStartPoint:  start frame is beyond end of track"));
		for (Scan = 0; Scan < FrameToStartAt; Scan += 1)
			{
				FrameObjectRec*			Frame;
				FractionRec					TempDuration;

				Frame = TrackObjectGetFrame(KeyTrack,Scan);
				CheckPtrExistence(Frame);
				DurationOfFrame(Frame,&TempDuration);
				AddFractions(&TempDuration,&Counter,&Counter);
			}
		*StartTimeOut = Counter;
	}


/* This routine does all of the work. */
/* The DataOutCallback is called every time a block of data is */
/* ready to be sent to the target device; this is provided so that data can be */
/* redirected to a file or postprocessed in some way before playback. */
/* the KeyTrack and FrameToStartAt provide a reference point indicating where */
/* playback should occur.  if KeyTrack is NIL, then playback begins at the beginning. */
/* the rate parameters are in operations per second. */
SynthErrorCodes				Synthesizer(struct MainWindowRec* MainWindow,
												MyBoolean (*DataOutCallback)(void* Refcon,
													largefixedsigned* DataBlock, long NumFrames,
													MyBoolean* AbortPlaybackFlagOut),
												void* DataOutRefcon, struct ArrayRec* ListOfTracks,
												struct TrackObjectRec* KeyTrack, long FrameToStartAt,
												long SamplingRate, long EnvelopeRate, MyBoolean UseStereo,
												LargeBCDType DefaultBeatsPerMinute,
												LargeBCDType OverallVolumeScalingReciprocal,
												MyBoolean InterpOverTime, MyBoolean InterpAcrossWaves,
												LargeBCDType ScanningGap, ErrorDaemonRec* ErrorDaemon)
	{
		SynthErrorCodes			Error;


		/* error checking */
		CheckPtrExistence(MainWindow);
		CheckPtrExistence(ListOfTracks);
		CheckPtrExistence(ErrorDaemon);
		ERROR(DataOutCallback == NIL,PRERR(ForceAbort,"Synthesizer: bad DataOutCallback"));
		if (ScanningGap < 0)
			{
				ScanningGap = 0;
			}

		/* check to see that there aren't any naming ambiguities */
		if (!CheckNameUniqueness(MainWindow))
			{
				return eSynthDuplicateNames;
			}

		/* make sure all objects are up to date */
		if (MainWindowMakeEverythingUpToDate(MainWindow))
			{
				largefixedsigned*		SampleArray;
				long								SampleArrayLength;
				EffectSpecListRec*	ScoreEffectSpec;
				TrackEffectGenRec*	ScoreEffectProcessor;

				ScoreEffectSpec = MainWindowGetScoreEffectsSpec(MainWindow);
				if (ScoreEffectSpec != NIL)
					{
						long								ScanningGapWidthInEnvelopeTicks;

						/* figure out how large the scanning gap is */
						ScanningGapWidthInEnvelopeTicks = LargeBCD2Double(ScanningGap) * EnvelopeRate;

						ScoreEffectProcessor = NewTrackEffectGenerator(ScoreEffectSpec,SamplingRate,
							UseStereo,LargeBCD2Single(OverallVolumeScalingReciprocal),MainWindow,
							ScanningGapWidthInEnvelopeTicks);
						if (ScoreEffectProcessor != NIL)
							{
								SampleArrayLength = 1000;
								SampleArray = (largefixedsigned*)AllocPtrCanFail(SampleArrayLength
									* sizeof(largefixedsigned),"SampleArray");
								if (SampleArray != NIL)
									{
										TempoControlRec*		TempoControl;

										TempoControl = NewTempoControl(DefaultBeatsPerMinute);
										if (TempoControl != NIL)
											{
												PlayListNodeRec*		PlayTrackList;

												Error = BuildPlayList(&PlayTrackList,ListOfTracks,UseStereo,
													MainWindow,OverallVolumeScalingReciprocal,SamplingRate,
													EnvelopeRate,InterpOverTime,InterpAcrossWaves,TempoControl,
													ScanningGapWidthInEnvelopeTicks,ErrorDaemon,ScoreEffectProcessor);
												if (Error == eSynthDone)
													{
														PlayListNodeRec*		Scan;
														PlayListNodeRec*		Lag;
														MyBoolean						AbortPlaybackFlag;
														FractionRec					MomentOfStarting;
														LargeBCDType				CurrentBeatsPerMinute;
														long								ScanningGapFrontInEnvelopeTicks;
														double							EnvelopeClockAccumulatorFraction;
														double							NoteDurationClockAccumulatorFraction;
														double							SamplesPerEnvelopeClock;
														double							DurationTicksPerEnvelopeClock;

														/* calculate the moment of starting for tracks */
														FindStartPoint(KeyTrack,FrameToStartAt,&MomentOfStarting);
														/* cue all tracks up to this point */
														Scan = PlayTrackList;
														while (Scan != NIL)
															{
																if (!CuePlayTrackInfoToPoint(Scan->ThisTrack,&MomentOfStarting))
																	{
																		Error = eSynthNoMemory;
																		goto PlaybackFailurePoint;
																	}
																Scan = Scan->Next;
															}

														/* this value is for determining when in REAL time (not score time) */
														/* each note begins */
														ScanningGapFrontInEnvelopeTicks = 0;

														/* initialize tempo thing */
														CurrentBeatsPerMinute = DefaultBeatsPerMinute;

														/* initialize accumulators */
														EnvelopeClockAccumulatorFraction = 0;
														NoteDurationClockAccumulatorFraction = 0;
														/* calculate increment factors */
														SamplesPerEnvelopeClock = (double)SamplingRate / EnvelopeRate;
														DurationTicksPerEnvelopeClock = ((LargeBCD2Double(DefaultBeatsPerMinute)
															/ (4/*beats per whole note*/ * 60/*seconds per minute*/))
															/ EnvelopeRate) * DURATIONUPDATECLOCKRESOLUTION;

														/* play */
														AbortPlaybackFlag = False;
														while ((PlayTrackList != NIL) && !AbortPlaybackFlag)
															{
																long								NumSampleFrames;
																long								NumNoteDurationTicks;
																LargeBCDType				OldBeatsPerMinute;
																MyBoolean						UpdateEnvelopes;

																/* figure out how many note duration ticks to generate */
																/* increment counter */
																NoteDurationClockAccumulatorFraction += DurationTicksPerEnvelopeClock;
																/* round down */
																NumNoteDurationTicks = (long)NoteDurationClockAccumulatorFraction;
																/* subtract off what we're taking out this time around, */
																/* leaving the extra little bit in there */
																NoteDurationClockAccumulatorFraction -= NumNoteDurationTicks;

																/* figure out how many samples to do before the next envelope */
																if (ScanningGapFrontInEnvelopeTicks >= ScanningGapWidthInEnvelopeTicks)
																	{
																		/* we're really sampling stuff */
																		EnvelopeClockAccumulatorFraction += SamplesPerEnvelopeClock;
																		NumSampleFrames = (long)EnvelopeClockAccumulatorFraction;
																		EnvelopeClockAccumulatorFraction -= NumSampleFrames;
																	}
																 else
																	{
																		/* scanning gap is still opening, so we're not sampling */
																		NumSampleFrames = 0;
																	}

																/* see if we need to resize the output workspace array */
																if (UseStereo)
																	{
																		long								Scan;

																		/* stereo array sizing */
																		if (NumSampleFrames * 2 > SampleArrayLength)
																			{
																				largefixedsigned*		TempArray;

																				TempArray = (largefixedsigned*)ResizePtr(
																					(char*)SampleArray,NumSampleFrames * 2
																					* sizeof(largefixedsigned));
																				if (TempArray == NIL)
																					{
																						Error = eSynthNoMemory;
																						goto PlaybackFailurePoint;
																					}
																				SampleArray = TempArray;
																				SampleArrayLength = NumSampleFrames * 2;
																			}
																		/* initialize the array */
																		for (Scan = 0; Scan < NumSampleFrames * 2; Scan += 1)
																			{
																				PRNGCHK(SampleArray,&(SampleArray[Scan]),
																					sizeof(SampleArray[Scan]));
																				SampleArray[Scan] = 0;
																			}
																	}
																 else
																	{
																		long								Scan;

																		/* mono array sizing */
																		if (NumSampleFrames > SampleArrayLength)
																			{
																				largefixedsigned*		TempArray;

																				TempArray = (largefixedsigned*)ResizePtr(
																					(char*)SampleArray,NumSampleFrames
																					* sizeof(largefixedsigned));
																				if (TempArray == NIL)
																					{
																						Error = eSynthNoMemory;
																						goto PlaybackFailurePoint;
																					}
																				SampleArray = TempArray;
																				SampleArrayLength = NumSampleFrames;
																			}
																		/* initialize the array */
																		for (Scan = 0; Scan < NumSampleFrames; Scan += 1)
																			{
																				PRNGCHK(SampleArray,&(SampleArray[Scan]),
																					sizeof(SampleArray[Scan]));
																				SampleArray[Scan] = 0;
																			}
																	}

																/* perform execution cyle */
																Scan = PlayTrackList;
																Lag = NIL;
																/* this condition is responsible for opening the scanning gap */
																UpdateEnvelopes = (ScanningGapFrontInEnvelopeTicks
																	>= ScanningGapWidthInEnvelopeTicks);
																while (Scan != NIL)
																	{
																		if (!PlayTrackUpdate(Scan->ThisTrack,
																			UpdateEnvelopes/*scanning gap control*/,
																			NumNoteDurationTicks,NumSampleFrames,SampleArray,
																			/* envelope ticks per duration tick */
																			(float)(1 / DurationTicksPerEnvelopeClock),
																			ScanningGapFrontInEnvelopeTicks))
																			{
																				Error = eSynthNoMemory;
																				goto PlaybackFailurePoint;
																			}
																		if (PlayTrackIsItStillActive(Scan->ThisTrack))
																			{
																				Lag = Scan;
																				Scan = Scan->Next;
																			}
																		 else
																			{
																				PlayListNodeRec*		Temp;

																				if (Lag == NIL)
																					{
																						PlayTrackList = Scan->Next;
																					}
																				 else
																					{
																						Lag->Next = Scan->Next;
																					}
																				Temp = Scan;
																				Scan = Scan->Next;
																				DisposePlayTrackInfo(Temp->ThisTrack);
																				ReleasePtr((char*)Temp);
																			}
																	}
																if (UpdateEnvelopes)
																	{
																		/* if we are generating samples, then we should */
																		/* apply the score effects processor */
																		TrackEffectProcessQueuedCommands(ScoreEffectProcessor);
																		ApplyTrackEffectGenerator(ScoreEffectProcessor,
																			SampleArray,NumSampleFrames);
																	}
																/* update effects, but only AFTER they have been applied, */
																/* so that parameters come from the leading edge of an */
																/* envelope period, rather than the trailing edge. */
																TrackEffectIncrementDurationTimer(ScoreEffectProcessor,
																	NumNoteDurationTicks);

																/* keep track of what time it is */
																ScanningGapFrontInEnvelopeTicks += 1;

																/* submit the data */
																AbortPlaybackFlag = False;
																if (!(*DataOutCallback)(DataOutRefcon,SampleArray,NumSampleFrames,
																	&AbortPlaybackFlag)) /* DataOutRefcon knows about stereo status */
																	{
																		Error = eSynthDataSubmitError;
																		goto PlaybackFailurePoint;
																	}

																/* and also do the tempo generator */
																OldBeatsPerMinute = CurrentBeatsPerMinute;
																CurrentBeatsPerMinute = TempoControlUpdate(TempoControl,
																	NumNoteDurationTicks);
																if (OldBeatsPerMinute != CurrentBeatsPerMinute)
																	{
																		/* tempo changed, so we have to adjust the clock */
																		DurationTicksPerEnvelopeClock = ((LargeBCD2Double(CurrentBeatsPerMinute)
																			/ (4/*beats per whole note*/ * 60/*seconds per minute*/))
																			/ EnvelopeRate) * DURATIONUPDATECLOCKRESOLUTION;
																	}
															}
														if (AbortPlaybackFlag)
															{
																Error = eSynthUserCancelled;
															}

													 PlaybackFailurePoint:
														;

														/* clean up */
														while (PlayTrackList != NIL)
															{
																PlayListNodeRec*		Temp;

																DisposePlayTrackInfo(PlayTrackList->ThisTrack);
																Temp = PlayTrackList;
																PlayTrackList = PlayTrackList->Next;
																ReleasePtr((char*)Temp);
															}
													}

												DisposeTempoControl(TempoControl);
											}

										/* clean up */
										ReleasePtr((char*)SampleArray);
									}
								 else
									{
										Error = eSynthNoMemory; /* no memory for sample array */
									}

								/* clean up */
								DisposeTrackEffectGenerator(ScoreEffectProcessor);
							}
						 else
							{
								Error = eSynthNoMemory; /* no memory for score effect processor */
							}

						/* clean up */
						DisposeEffectSpecList(ScoreEffectSpec);
					}
				 else
					{
						Error = eSynthPrereqError; /* couldn't compile score effect thing */
					}
			}
		 else
			{
				Error = eSynthPrereqError; /* couldn't compile score elements */
			}

		/* clean up */
		FlushLinearTransitionRecords();
		FlushWaveTableOscControl();
		FlushSampleOscControl();
		FlushEvalEnvelopeStateRecords();
		FlushLFOGeneratorRecords();
		FlushPlayTrackInfo();
		FlushFrozenNoteStructures();
		FlushCachedOscStateBankRecords();
		FlushTrackEffectGeneratorInfo();
		FlushCachedDelayLineStuff();
		FlushCachedNLProcStuff();
		FlushCachedFirstOrderLowpassStuff();
		FlushCachedFirstOrderHighpassStuff();
		FlushCachedSecondOrderResonStuff();
		FlushCachedSecondOrderZeroStuff();
		FlushCachedButterworthLowpassStuff();
		FlushCachedButterworthHighpassStuff();
		FlushCachedButterworthBandpassStuff();
		FlushCachedButterworthBandrejectStuff();
		FlushCachedFilterNullStuff();
		FlushCachedFilterArrayStuff();
		FlushCachedAnalyzerStuff();
		FlushOscEffectGeneratorInfo();
		FlushCachedOscFilterArrayStuff();
		FlushCachedOscDelayLineStuff();
		FlushCachedOscNLProcStuff();
		FlushCachedParametricEqualizerStuff();
		FlushCachedResonantLowpassStuff();

		return Error;
	}
