/* PlayTrackInfoThang.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 "PlayTrackInfoThang.h"
#include "Array.h"
#include "Memory.h"
#include "OscBankPlayer.h"
#include "TrackObject.h"
#include "IncrementalParameterUpdator.h"
#include "Fractions.h"
#include "FrameObject.h"
#include "DeterminedNoteStructure.h"
#include "NoteObject.h"
#include "ErrorDaemon.h"
#include "TrackEffectGenerator.h"
#include "InstrumentStructure.h"


typedef struct FrozenNoteConsCell
	{
		/* list link */
		struct FrozenNoteConsCell*	Next;

		/* good information to be used for tied notes */
		FrozenNoteRec*						FrozenNote;

		/* tie target for tied notes, NIL if there is no tie */
		struct NoteObjectRec*			TieTarget;

		/* when this note should take over (in absolute envelope ticks) */
		long											ContinuationTime;
	} FrozenNoteConsCell;


typedef struct OscBankConsCell
	{
		/* link for listing */
		struct OscBankConsCell*		Next;
		struct OscBankConsCell*		Previous;

		/* the oscillator bank */
		OscStateBankRec*					OscBank;

		/* still active flag */
		MyBoolean									StillActive;

		/* tie target for tied notes, NIL if there is no tie */
		struct NoteObjectRec*			TieTarget;

		/* this is the start time of the note, used for ordering the scanning gap list */
		long											StartTime;

		/* this is an ordered list of frozen notes ready for tie continuation */
		/* this is in sorted ascending order of TieContinuationList->ContinuationTime */
		FrozenNoteConsCell*				TieContinuationList;
	} OscBankConsCell;


struct PlayTrackInfoRec
	{
		/* frame source */
		TrackObjectRec*					TrackObject;
		/* total number of frames in the track object */
		long										TotalNumberOfFrames;
		/* index into frame array. */
		long										FrameArrayIndex;
		/* number of cycles until the next frame should be processed.  this is in */
		/* units of duration update cycles.  when this runs out, then another frame */
		/* should be processed. */
		long										NextFrameCountDown;

		/* list of notes that have been scheduled but haven't been executed yet. */
		/* it contains objects of type OscBankConsCell. */
		OscBankConsCell*				ScanningGapListHead; /* of OscBankConsCell's */
		OscBankConsCell*				ScanningGapListTail; /* of OscBankConsCell's */
		/* this is the current duration update index for the scanning gap list */
		long										ExecutionIndex;
		/* this is the index of the first element in the scanning gap list.  when */
		/* the execution index hits or exceeds this value, the first osc bank is popped */
		/* from the scanning gap list and executed.  it is only valid when */
		/* ScanningGapLength is greater than zero (i.e. ScanningGapList is non-empty) */

		/* this object keeps track of the current value of all parameters, updates them */
		/* as time passes, and evaluates commands passed in from here.  the state of */
		/* this object reflects the state at the front of scanning gap, since notes */
		/* are frozen immediately upon entering scanning gap. */
		IncrParamUpdateRec*			ParameterController;

		/* this is the template used for creating oscillator banks */
		OscBankTemplateRec*			OscillatorBankTemplate;

		/* this is a list of all currently executing oscillator banks.  there is one */
		/* entry for each note that is currently being played. */
		OscBankConsCell*				ExecutingOscillatorBanks;

		/* playback control parameters */
		float										EnvelopeUpdateRate;
		float										OverallVolumeScaling;

		/* area for generating samples into, and applying track effects */
		largefixedsigned*				LocalSampleArray;
		largefixedsigned*				LocalSampleArrayPrivateWorkspace;
		long										LocalSampleArrayLength;

		/* track effects */
		TrackEffectGenRec*			EffectGenerator;

		/* score effects.  we don't actually apply this, but we have it so that */
		/* we can send it commands.  it is a reference to a shared object. */
		TrackEffectGenRec*			ScoreEffectGenerator;

		/* flag remembering if we are using stereo */
		MyBoolean								UsingStereo;
	};


static OscBankConsCell*					OscBankConsCellFreeList = NIL;
struct FrozenNoteConsCell*			FrozenNoteConsCellFreeList = NIL;


/* dispose of cached track data structures */
void								FlushPlayTrackInfo(void)
	{
		while (OscBankConsCellFreeList != NIL)
			{
				OscBankConsCell*		Temp;

				Temp = OscBankConsCellFreeList;
				OscBankConsCellFreeList = OscBankConsCellFreeList->Next;
				ReleasePtr((char*)Temp);
			}

		while (FrozenNoteConsCellFreeList != NIL)
			{
				FrozenNoteConsCell*	Temp;

				Temp = FrozenNoteConsCellFreeList;
				FrozenNoteConsCellFreeList = FrozenNoteConsCellFreeList->Next;
				ReleasePtr((char*)Temp);
			}
	}


/* create a new track play info thing and set a bunch of parameters.  this also */
/* builds the internal representations for instruments & oscillators for this track. */
PlayTrackInfoRec*		NewPlayTrackInfo(struct TrackObjectRec* TheTrack,
											struct InstrumentRec* InstrumentSpecification,
											MyBoolean StereoFlag, LargeBCDType OverallVolumeScalingReciprocal,
											long SamplingRate, float EnvelopeRate, MyBoolean TimeInterp,
											MyBoolean WaveInterp, struct TempoControlRec* TempoControl,
											long ScanningGapWidthInEnvelopeTicks,
											struct ErrorDaemonRec* ErrorDaemon,
											struct MainWindowRec* MainWindow,
											struct TrackEffectGenRec* ScoreEffectGenerator)
	{
		PlayTrackInfoRec*	TrackInfo;

		CheckPtrExistence(TheTrack);
		CheckPtrExistence(InstrumentSpecification);
		CheckPtrExistence(TempoControl);
		CheckPtrExistence(ErrorDaemon);
		CheckPtrExistence(ScoreEffectGenerator);
		CheckPtrExistence(MainWindow);

		TrackInfo = (PlayTrackInfoRec*)AllocPtrCanFail(sizeof(PlayTrackInfoRec),
			"PlayTrackInfoRec");
		if (TrackInfo == NIL)
			{
			 FailurePoint1:
				return NIL;
			}

		/* score effect generator */
		TrackInfo->ScoreEffectGenerator = ScoreEffectGenerator;

		/* frame source */
		TrackInfo->TrackObject = TheTrack;

		/* total number of frames in the track object */
		TrackInfo->TotalNumberOfFrames = TrackObjectGetNumFrames(TheTrack);

		/* index into frame array. */
		TrackInfo->FrameArrayIndex = 0;

		/* number of cycles until the next frame should be processed.  this is in */
		/* units of duration update cycles.  when this runs out, then another frame */
		/* should be processed. */
		TrackInfo->NextFrameCountDown = 0;

		/* list of notes that have been scheduled but haven't been executed yet. */
		/* it contains objects of type OscStateBankRec. */
		TrackInfo->ScanningGapListHead = NIL;
		TrackInfo->ScanningGapListTail = NIL;

		/* this is the current envelope update index for removing things from the */
		/* scanning gap list (i.e. the back edge of the scanning gap) */
		/* by setting this negative, we cause the scanning gap to open. */
		TrackInfo->ExecutionIndex = - ScanningGapWidthInEnvelopeTicks;

		/* this object keeps track of the current value of all parameters, updates them */
		/* as time passes, and evaluates commands passed in from here. */
		TrackInfo->ParameterController = NewInitializedParamUpdator(TheTrack,TempoControl);
		if (TrackInfo->ParameterController == NIL)
			{
			 FailurePoint2:
				ReleasePtr((char*)TrackInfo);
				goto FailurePoint1;
			}

		/* this is the template used for creating oscillator banks */
		TrackInfo->OscillatorBankTemplate = NewOscBankTemplate(InstrumentSpecification,
			StereoFlag,OverallVolumeScalingReciprocal,SamplingRate,EnvelopeRate,
			TimeInterp,WaveInterp,TrackInfo->ParameterController,ErrorDaemon,MainWindow);
		if (TrackInfo->OscillatorBankTemplate == NIL)
			{
			 FailurePoint3:
				DisposeParamUpdator(TrackInfo->ParameterController);
				goto FailurePoint2;
			}

		/* allocate new sample array thing */
		TrackInfo->LocalSampleArrayLength = 1000;
		TrackInfo->LocalSampleArray = (largefixedsigned*)AllocPtrCanFail(
			TrackInfo->LocalSampleArrayLength * sizeof(largefixedsigned),"LocalSampleArray");
		if (TrackInfo->LocalSampleArray == NIL)
			{
			 FailurePoint4:
				DisposeOscBankTemplate(TrackInfo->OscillatorBankTemplate);
				goto FailurePoint3;
			}
		TrackInfo->LocalSampleArrayPrivateWorkspace = (largefixedsigned*)AllocPtrCanFail(
			TrackInfo->LocalSampleArrayLength * sizeof(largefixedsigned),
			"LocalSampleArrayPrivateWorkspace");
		if (TrackInfo->LocalSampleArrayPrivateWorkspace == NIL)
			{
			 FailurePoint5:
				ReleasePtr((char*)TrackInfo->LocalSampleArray);
				goto FailurePoint4;
			}

		/* set up track effects */
		TrackInfo->EffectGenerator = NewTrackEffectGenerator(GetInstrumentEffectSpecList(
			InstrumentSpecification),SamplingRate,StereoFlag,LargeBCD2Single(
			OverallVolumeScalingReciprocal),MainWindow,ScanningGapWidthInEnvelopeTicks);
		if (TrackInfo->EffectGenerator == NIL)
			{
			 FailurePoint6:
				ReleasePtr((char*)TrackInfo->LocalSampleArrayPrivateWorkspace);
				goto FailurePoint5;
			}

		/* remember stereo status */
		TrackInfo->UsingStereo = StereoFlag;

		/* this is a list of all currently executing oscillator banks.  there is one */
		/* entry for each note that is currently being played. */
		TrackInfo->ExecutingOscillatorBanks = NIL;

		/* playback control parameters */
		TrackInfo->EnvelopeUpdateRate = EnvelopeRate;
		TrackInfo->OverallVolumeScaling = (float)1
			/ LargeBCD2Double(OverallVolumeScalingReciprocal);

		return TrackInfo;
	}


static void					DisposeOscBankList(OscBankConsCell* List)
	{
		while (List != NIL)
			{
				OscBankConsCell*	Temp;

				/* delink */
				Temp = List;
				List = List->Next;
				/* dispose members */
				DisposeOscStateBank(Temp->OscBank);
				while (Temp->TieContinuationList != NIL)
					{
						FrozenNoteConsCell*		ColdTemp;

						ColdTemp = Temp->TieContinuationList;
						Temp->TieContinuationList = Temp->TieContinuationList->Next;
						DisposeFrozenNote(ColdTemp->FrozenNote);
						ColdTemp->Next = FrozenNoteConsCellFreeList;
						FrozenNoteConsCellFreeList = ColdTemp;
					}
				/* stick on free list */
				Temp->Next = OscBankConsCellFreeList;
				EXECUTE(Temp->Previous = (OscBankConsCell*)0x81818181;)
				OscBankConsCellFreeList = Temp;
			}
	}


/* dump a track play info thing and all the stuff in it */
void								DisposePlayTrackInfo(PlayTrackInfoRec* TrackInfo)
	{
		CheckPtrExistence(TrackInfo);
		DisposeTrackEffectGenerator(TrackInfo->EffectGenerator);
		ReleasePtr((char*)TrackInfo->LocalSampleArray);
		ReleasePtr((char*)TrackInfo->LocalSampleArrayPrivateWorkspace);
		DisposeOscBankList(TrackInfo->ScanningGapListHead);
		DisposeOscBankList(TrackInfo->ExecutingOscillatorBanks);
		DisposeOscBankTemplate(TrackInfo->OscillatorBankTemplate);
		DisposeParamUpdator(TrackInfo->ParameterController);
		ReleasePtr((char*)TrackInfo);
	}


/* cue track forward to the specified point.  returns False if it fails */
MyBoolean						CuePlayTrackInfoToPoint(PlayTrackInfoRec* TrackInfo,
											struct FractionRec* StartTime)
	{
		long							AdvancementCounter;

		CheckPtrExistence(TrackInfo);

		/* convert whole-note fraction into duration ticks */
		ERROR(StartTime->Denominator != DURATIONUPDATECLOCKRESOLUTION,PRERR(AllowResume,
			"CuePlayTrackInfoToPoint:  start time denominator has bad value"));
		AdvancementCounter = StartTime->Integer * StartTime->Denominator
			+ StartTime->Fraction;

		/* search for the proper point to start playing */
		while ((AdvancementCounter > 0)
			&& (TrackInfo->FrameArrayIndex < TrackInfo->TotalNumberOfFrames))
			{
				FrameObjectRec*		Frame; /* I've been framed! */
				FractionRec				Duration;
				long							TicksInFrame;

				/* get the frame */
				Frame = TrackObjectGetFrame(TrackInfo->TrackObject,TrackInfo->FrameArrayIndex);
				CheckPtrExistence(Frame);
				/* how long is it? */
				DurationOfFrame(Frame,&Duration);
				ERROR(Duration.Denominator != DURATIONUPDATECLOCKRESOLUTION,PRERR(AllowResume,
					"CuePlayTrackInfoToPoint:  start time denominator has odd value"));
				TicksInFrame = Duration.Integer * Duration.Denominator + Duration.Fraction;
				/* decrement our lead-in counter */
				AdvancementCounter -= TicksInFrame;
				/* advance our pointer */
				TrackInfo->FrameArrayIndex += 1;
			}

		/* AdvancementCounter is either 0 or negative.  if it's negative, then we */
		/* need to note that we must delay some before getting the next note */
		TrackInfo->NextFrameCountDown = - AdvancementCounter;

		return True;
	}


/* check to see if the track has finished and can be dropped. */
MyBoolean						PlayTrackIsItStillActive(PlayTrackInfoRec* TrackInfo)
	{
		OscBankConsCell*	OscBankScan;

		CheckPtrExistence(TrackInfo);

		/* in order to still be active, the following conditions must be satisfied: */
		/*  - there are still active oscillators. */
		/*  - there are still oscillators in the scanning gap list */
		/*  - there are still notes which haven't been scanned yet */

		if (TrackInfo->FrameArrayIndex < TrackInfo->TotalNumberOfFrames)
			{
				return True;
			}

		if (TrackInfo->ScanningGapListHead != NIL)
			{
				return True;
			}

		OscBankScan = TrackInfo->ExecutingOscillatorBanks;
		while (OscBankScan != NIL)
			{
				if (OscBankScan->StillActive)
					{
						return True;
					}
				OscBankScan = OscBankScan->Next;
			}

		return False;
	}


/* auxilliary routine which searches a list for a tie source and installs the note */
/* in the list if necessary.  True is returned if it is installed. */
static MyBoolean		SearchForTieSource(OscBankConsCell* OscBankScan,
											struct NoteObjectRec* Note, long ScanningGapFrontInEnvelopeTicks,
											PlayTrackInfoRec* TrackInfo, float EnvelopeTicksPerDurationTick)
	{
		while (OscBankScan != NIL)
			{
				FrozenNoteConsCell*		PlaceToPut;
				long									StartAdjust;

				PlaceToPut = NIL;
				if (OscBankScan->TieTarget == Note)
					{
						/* found it */
					 CreateTieContinuationPoint:
						if (FrozenNoteConsCellFreeList != NIL)
							{
								PlaceToPut = FrozenNoteConsCellFreeList;
								FrozenNoteConsCellFreeList = FrozenNoteConsCellFreeList->Next;
							}
						 else
							{
								PlaceToPut = (FrozenNoteConsCell*)AllocPtrCanFail(
									sizeof(FrozenNoteConsCell),"FrozenNoteConsCell");
								if (PlaceToPut == NIL)
									{
										return False;
									}
							}
					}
				 else
					{
						FrozenNoteConsCell*		TargScan;
						FrozenNoteConsCell*		TargLag;

						/* search tie target things */
						TargScan = OscBankScan->TieContinuationList;
						TargLag = NIL;
						while (TargScan != NIL)
							{
								if (TargScan->TieTarget == Note)
									{
										/* found one */
										goto CreateTieContinuationPoint;
									}
								TargLag = TargScan;
								TargScan = TargScan->Next;
							}
					}
				if (PlaceToPut != NIL)
					{
						FrozenNoteConsCell*		InsertScan;
						FrozenNoteConsCell*		InsertLag;

						/* fill in the fields */
						PlaceToPut->FrozenNote = FixNoteParameters(
							TrackInfo->ParameterController,Note,&StartAdjust,
							TrackInfo->OverallVolumeScaling,EnvelopeTicksPerDurationTick);
						if (PlaceToPut->FrozenNote == NIL)
							{
								PlaceToPut->Next = FrozenNoteConsCellFreeList;
								FrozenNoteConsCellFreeList = PlaceToPut;
								return False;
							}
						PlaceToPut->ContinuationTime = StartAdjust + ScanningGapFrontInEnvelopeTicks;
						PlaceToPut->TieTarget = GetNoteTieTarget(Note);
						/* insert it into the proper place */
						InsertScan = OscBankScan->TieContinuationList;
						InsertLag = NIL;
						while ((InsertScan != NIL) && (InsertScan->ContinuationTime
							<= PlaceToPut->ContinuationTime))
							{
								InsertLag = InsertScan;
								InsertScan = InsertScan->Next;
							}
						PlaceToPut->Next = InsertScan;
						if (InsertLag == NIL)
							{
								OscBankScan->TieContinuationList = PlaceToPut;
							}
						 else
							{
								InsertLag->Next = PlaceToPut;
							}
						/* we found it! */
						return True;
					}
				OscBankScan = OscBankScan->Next;
			}
		return False;
	}


/* perform one envelope clock cycle update.  if UpdateEnvelopes is true, then */
/* wave data should be generated and envelopes should be updated, otherwise only */
/* note scheduling should be performed. */
MyBoolean						PlayTrackUpdate(PlayTrackInfoRec* TrackInfo,
											MyBoolean UpdateEnvelopes, long NumDurationTicks,
											long NumFrames, largefixedsigned* OutputData,
											float EnvelopeTicksPerDurationTick,
											long ScanningGapFrontInEnvelopeTicks)
	{
		OscBankConsCell*	OscBankScan;
		OscBankConsCell*	OscBankLag;

		CheckPtrExistence(TrackInfo);

		/* schedule any notes out of the track list into the scanning gap */
		while ((TrackInfo->NextFrameCountDown <= 0)
			&& (TrackInfo->FrameArrayIndex < TrackInfo->TotalNumberOfFrames))
			{
				FrameObjectRec*		Frame;

				/* schedule a frame */
				Frame = TrackObjectGetFrame(TrackInfo->TrackObject,TrackInfo->FrameArrayIndex);
				CheckPtrExistence(Frame);
				TrackInfo->FrameArrayIndex += 1;
				if (IsThisACommandFrame(Frame))
					{
						NoteObjectRec*		Command;

						/* it's a command */
						Command = GetNoteFromFrame(Frame,0);
						/* if it's an effect command, then send it to the effect processor */
						/* otherwise we handle it */
						switch (GetCommandOpcode(Command))
							{
								default:
									ExecuteParamCommandFrame(TrackInfo->ParameterController,Command);
									break;
								case eCmdSetEffectParam1:
								case eCmdIncEffectParam1:
								case eCmdSweepEffectParamAbs1:
								case eCmdSweepEffectParamRel1:
								case eCmdSetEffectParam2:
								case eCmdIncEffectParam2:
								case eCmdSweepEffectParamAbs2:
								case eCmdSweepEffectParamRel2:
								case eCmdSetEffectParam3:
								case eCmdIncEffectParam3:
								case eCmdSweepEffectParamAbs3:
								case eCmdSweepEffectParamRel3:
								case eCmdSetEffectParam4:
								case eCmdIncEffectParam4:
								case eCmdSweepEffectParamAbs4:
								case eCmdSweepEffectParamRel4:
								case eCmdTrackEffectEnable:
									if (!TrackEffectHandleCommand(TrackInfo->EffectGenerator,
										Command,ScanningGapFrontInEnvelopeTicks))
										{
											return False;
										}
									break;
								case eCmdSetScoreEffectParam1:
								case eCmdIncScoreEffectParam1:
								case eCmdSweepScoreEffectParamAbs1:
								case eCmdSweepScoreEffectParamRel1:
								case eCmdSetScoreEffectParam2:
								case eCmdIncScoreEffectParam2:
								case eCmdSweepScoreEffectParamAbs2:
								case eCmdSweepScoreEffectParamRel2:
								case eCmdSetScoreEffectParam3:
								case eCmdIncScoreEffectParam3:
								case eCmdSweepScoreEffectParamAbs3:
								case eCmdSweepScoreEffectParamRel3:
								case eCmdSetScoreEffectParam4:
								case eCmdIncScoreEffectParam4:
								case eCmdSweepScoreEffectParamAbs4:
								case eCmdSweepScoreEffectParamRel4:
									if (!TrackEffectHandleCommand(TrackInfo->ScoreEffectGenerator,
										Command,ScanningGapFrontInEnvelopeTicks))
										{
											return False;
										}
									break;
							}
					}
				 else
					{
						long							FrameLimit;
						long							FrameScan;
						FractionRec				FrameDuration;

						/* increment the frame counter */
						DurationOfFrame(Frame,&FrameDuration);
						ERROR(DURATIONUPDATECLOCKRESOLUTION != FrameDuration.Denominator,
							PRERR(AllowResume,"PlayTrackUpdate:  strange denominator in frame duration"));
						TrackInfo->NextFrameCountDown += FrameDuration.Denominator
							* FrameDuration.Integer + FrameDuration.Fraction;

						/* it's a real note */
						FrameLimit = NumNotesInFrame(Frame);
						for (FrameScan = 0; FrameScan < FrameLimit; FrameScan += 1)
							{
								struct NoteObjectRec*	Note;
								OscBankConsCell*	NewOscBank;
								OscBankConsCell*	LinkingScan;

								Note = GetNoteFromFrame(Frame,FrameScan);
								CheckPtrExistence(Note);
								if (GetNoteIsItARest(Note))
									{
										/* just ignore rests */
										goto EndFrameScanPoint; /* this should really be a conditional */
									}

								/* first, scan the oscillator list to see if this is a note that */
								/* someone wants to tie to.  if it is, then build a frozen note */
								/* structure and add it to the object.  otherwise, build an */
								/* oscillator bank and add it to the scanning gap. */

								/* search all oscillator banks to see if it's a tie target */
								if (SearchForTieSource(TrackInfo->ExecutingOscillatorBanks,Note,
									ScanningGapFrontInEnvelopeTicks,TrackInfo,
									EnvelopeTicksPerDurationTick))
									{
										goto EndFrameScanPoint;
									}
								if (SearchForTieSource(TrackInfo->ScanningGapListHead,Note,
									ScanningGapFrontInEnvelopeTicks,TrackInfo,
									EnvelopeTicksPerDurationTick))
									{
										goto EndFrameScanPoint;
									}

								/* if we got here, then it's not a tie target */
								if (OscBankConsCellFreeList != NIL)
									{
										NewOscBank = OscBankConsCellFreeList;
										OscBankConsCellFreeList = OscBankConsCellFreeList->Next;
									}
								 else
									{
										NewOscBank = (OscBankConsCell*)AllocPtrCanFail(
											sizeof(OscBankConsCell),"OscBankConsCell");
										if (NewOscBank == NIL)
											{
												return False;
											}
									}
								NewOscBank->OscBank = NewOscBankState(TrackInfo->OscillatorBankTemplate,
									&(NewOscBank->StartTime),Note,EnvelopeTicksPerDurationTick);
								if (NewOscBank->OscBank == NIL)
									{
										NewOscBank->Next = OscBankConsCellFreeList;
										OscBankConsCellFreeList = NewOscBank;
										return False;
									}
								NewOscBank->StartTime += ScanningGapFrontInEnvelopeTicks; /* fix up start time */
								NewOscBank->StillActive = True;
								NewOscBank->TieTarget = GetOscStateTieTarget(NewOscBank->OscBank);
								NewOscBank->TieContinuationList = NIL;
								/* link it in */
								LinkingScan = TrackInfo->ScanningGapListTail;
								while ((LinkingScan != NIL)
									&& (LinkingScan->StartTime > NewOscBank->StartTime))
									{
										LinkingScan = LinkingScan->Previous;
									}
								if (LinkingScan == NIL)
									{
										NewOscBank->Previous = NIL;
										NewOscBank->Next = TrackInfo->ScanningGapListHead;
										if (TrackInfo->ScanningGapListHead != NIL)
											{
												TrackInfo->ScanningGapListHead->Previous = NewOscBank;
											}
										TrackInfo->ScanningGapListHead = NewOscBank;
										if (TrackInfo->ScanningGapListTail == NIL)
											{
												/* this happens if there were no nodes at all */
												TrackInfo->ScanningGapListTail = NewOscBank;
											}
									}
								 else
									{
										/* insert after Scan */
										NewOscBank->Previous = LinkingScan;
										NewOscBank->Next = LinkingScan->Next;
										LinkingScan->Next = NewOscBank;
										if (LinkingScan == TrackInfo->ScanningGapListTail)
											{
												/* this happens if Scan was the last element; */
												/* NewNode becomes last element */
												TrackInfo->ScanningGapListTail = NewOscBank;
											}
									}

							 EndFrameScanPoint:
								;
							}
					}
			}

		/* do timing update */
		TrackInfo->NextFrameCountDown -= NumDurationTicks;

		/* update global parameters */
		ExecuteParamUpdate(TrackInfo->ParameterController,NumDurationTicks);

		/* generate waveforms, update envelope generators & notes, etc. */
		/* there are 3 stages to stuff */
		/*  1. when UpdateEnvelopes is false, the only action is to queue up notes */
		/*     to be played.  this opens the scanning gap. */
		/*  2. when UpdateEnvelopes is true, but ExecutionIndex is still less than zero, */
		/*     notes that start before the start of the song (due to pre-origin segments */
		/*     or other things) are started and processed. */
		/*  3. Eventually, ExecutionIndex will be >= 0, and the official start of the */
		/*     song will have been passed. */
		if (UpdateEnvelopes)
			{
				/* make sure buffer is big enough, and zero it out */
				if (TrackInfo->UsingStereo)
					{
						long							Scan;

						if (NumFrames * 2 > TrackInfo->LocalSampleArrayLength)
							{
								largefixedsigned*		TempArray;

								TempArray = (largefixedsigned*)ResizePtr(
									(char*)TrackInfo->LocalSampleArray,NumFrames * 2
									* sizeof(largefixedsigned));
								if (TempArray == NIL)
									{
										return False;
									}
								TrackInfo->LocalSampleArray = TempArray;
								TempArray = (largefixedsigned*)ResizePtr(
									(char*)TrackInfo->LocalSampleArrayPrivateWorkspace,NumFrames * 2
									* sizeof(largefixedsigned));
								if (TempArray == NIL)
									{
										return False;
									}
								TrackInfo->LocalSampleArrayPrivateWorkspace = TempArray;
								TrackInfo->LocalSampleArrayLength = NumFrames * 2;
							}
						/* initialize array */
						for (Scan = 0; Scan < NumFrames * 2; Scan += 1)
							{
								PRNGCHK(TrackInfo->LocalSampleArray,&(TrackInfo->LocalSampleArray[Scan]),
									sizeof((TrackInfo->LocalSampleArray[Scan])));
								TrackInfo->LocalSampleArray[Scan] = 0;
							}
					}
				 else
					{
						long							Scan;

						if (NumFrames > TrackInfo->LocalSampleArrayLength)
							{
								largefixedsigned*		TempArray;

								TempArray = (largefixedsigned*)ResizePtr(
									(char*)TrackInfo->LocalSampleArray,NumFrames
									* sizeof(largefixedsigned));
								if (TempArray == NIL)
									{
										return False;
									}
								TrackInfo->LocalSampleArray = TempArray;
								TempArray = (largefixedsigned*)ResizePtr(
									(char*)TrackInfo->LocalSampleArrayPrivateWorkspace,NumFrames
									* sizeof(largefixedsigned));
								if (TempArray == NIL)
									{
										return False;
									}
								TrackInfo->LocalSampleArrayPrivateWorkspace = TempArray;
								TrackInfo->LocalSampleArrayLength = NumFrames;
							}
						/* initialize array */
						for (Scan = 0; Scan < NumFrames; Scan += 1)
							{
								PRNGCHK(TrackInfo->LocalSampleArray,&(TrackInfo->LocalSampleArray[Scan]),
									sizeof((TrackInfo->LocalSampleArray[Scan])));
								TrackInfo->LocalSampleArray[Scan] = 0;
							}
					}
				ERROR(PtrSize((char*)TrackInfo->LocalSampleArray)
					!= PtrSize((char*)TrackInfo->LocalSampleArrayPrivateWorkspace),
					PRERR(ForceAbort,"PlayTrackUpdate:  workspace arrays are differently sized"));

				/* see if any ties have to be tripped */
				OscBankScan = TrackInfo->ExecutingOscillatorBanks;
				while (OscBankScan != NIL)
					{
						while ((OscBankScan->TieContinuationList != NIL)
							&& (OscBankScan->TieContinuationList->ContinuationTime
							<= TrackInfo->ExecutionIndex))
							{
								FrozenNoteConsCell*		Temp;

								/* yow, let's do this one! */
								/* delink */
								Temp = OscBankScan->TieContinuationList;
								OscBankScan->TieContinuationList
									= OscBankScan->TieContinuationList->Next;
								/* execute */
								if (!ResetOscBankState(OscBankScan->OscBank,Temp->FrozenNote,
									EnvelopeTicksPerDurationTick))
									{
										return False; /* oh, no! */
									}
								OscBankScan->TieTarget = Temp->TieTarget;
								/* clean up */
								DisposeFrozenNote(Temp->FrozenNote);
								Temp->Next = FrozenNoteConsCellFreeList;
								FrozenNoteConsCellFreeList = Temp;
							}
						OscBankScan = OscBankScan->Next;
					}

				/* schedule a note from the scanning gap */
				while ((TrackInfo->ScanningGapListHead != NIL)
					&& (TrackInfo->ScanningGapListHead->StartTime <= TrackInfo->ExecutionIndex))
					{
						OscBankConsCell*	NewConsCell;

						/* yup, schedule the oscillator */
						/* [technically, the start time should never be strictly less than the */
						/* execution index (only equal), but it can occur if the user specifies */
						/* a scanning gap that's two narrow.] */
						NewConsCell = TrackInfo->ScanningGapListHead;
						TrackInfo->ScanningGapListHead = TrackInfo->ScanningGapListHead->Next;
						if (TrackInfo->ScanningGapListHead != NIL)
							{
								TrackInfo->ScanningGapListHead->Previous = NIL;
							}
						 else
							{
								TrackInfo->ScanningGapListTail = NIL;
							}
						/* link it in */
						NewConsCell->Next = TrackInfo->ExecutingOscillatorBanks;
						TrackInfo->ExecutingOscillatorBanks = NewConsCell;
						EXECUTE(NewConsCell->Previous = (OscBankConsCell*)0x81818181;)
					}

				/* let track effect generator schedule commands */
				TrackEffectProcessQueuedCommands(TrackInfo->EffectGenerator);

				/* increment our scanning gap back edge clock, after scheduling commands */
				/* (this way, commands are scheduled on the beginning of the clock they */
				/* should occur on). */
				TrackInfo->ExecutionIndex += 1;

				/* wave generator and envelope update loop */
				OscBankScan = TrackInfo->ExecutingOscillatorBanks;
				OscBankLag = NIL;
				while (OscBankScan != NIL)
					{
						OscBankScan->StillActive = !UpdateOscStateBank(OscBankScan->OscBank,
							NumFrames,TrackInfo->LocalSampleArray,TrackInfo->LocalSampleArrayPrivateWorkspace);
						if (!OscBankScan->StillActive && (OscBankScan->TieContinuationList == NIL))
							{
								OscBankConsCell*	Temp;

								/* not tied to anybody, so kill it */
								DisposeOscStateBank(OscBankScan->OscBank);
								if (OscBankLag == NIL)
									{
										TrackInfo->ExecutingOscillatorBanks = OscBankScan->Next;
									}
								 else
									{
										OscBankLag->Next = OscBankScan->Next;
									}
								Temp = OscBankScan;
								OscBankScan = OscBankScan->Next;
								Temp->Next = OscBankConsCellFreeList;
								OscBankConsCellFreeList = Temp;
							}
						 else
							{
								OscBankLag = OscBankScan;
								OscBankScan = OscBankScan->Next;
							}
					}

				/* apply effects to local array */
				ApplyTrackEffectGenerator(TrackInfo->EffectGenerator,
					TrackInfo->LocalSampleArray,NumFrames);

				/* copy data from local array to global array */
				if (TrackInfo->UsingStereo)
					{
						long							Scan;

						for (Scan = 0; Scan < NumFrames * 2; Scan += 1)
							{
								PRNGCHK(TrackInfo->LocalSampleArray,&(TrackInfo->LocalSampleArray[Scan]),
									sizeof((TrackInfo->LocalSampleArray[Scan])));
								PRNGCHK(OutputData,&(OutputData[Scan]),sizeof(OutputData[Scan]));
								OutputData[Scan] += TrackInfo->LocalSampleArray[Scan];
							}
					}
				 else
					{
						long							Scan;

						for (Scan = 0; Scan < NumFrames; Scan += 1)
							{
								PRNGCHK(TrackInfo->LocalSampleArray,&(TrackInfo->LocalSampleArray[Scan]),
									sizeof((TrackInfo->LocalSampleArray[Scan])));
								PRNGCHK(OutputData,&(OutputData[Scan]),sizeof(OutputData[Scan]));
								OutputData[Scan] += TrackInfo->LocalSampleArray[Scan];
							}
					}
			}

		/* 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(TrackInfo->EffectGenerator,NumDurationTicks);

		return True;
	}
