/* TrackDisplayScheduling.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 "TrackDisplayScheduling.h"
#include "TrackObject.h"
#include "FrameObject.h"
#include "Memory.h"
#include "Fractions.h"
#include "DataMunging.h"
#include "NoteObject.h"
#include "TieTrackPixel.h"
#include "TieTracking.h"
#include "StaffCalibration.h"


/* where does the very first note start (to allow insertion before it) */
#define FIRSTNOTESTART (32)

typedef struct
	{
		long									PixelStart;
		OrdType								Width : 15; /* Well... >I< know it's not going to be big... */
		unsigned int					SquashThisOne : 1; /* boolean; True == don't draw this one */
	} FrameAttrRec;

typedef struct
	{
		TrackObjectRec*				TrackObj;
		long									NumFrames;
		FrameAttrRec*					FrameAttrArray;
	} TrackAttrRec;

typedef struct
	{
		short									BarIndex : 15; /* -1 == no bar */
		unsigned int					DrawBarGreyed; /* boolean; True == draw bar greyed */
	} MeasureBarInfoRec;

struct TrackDispScheduleRec
	{
		long									NumTracks;
		/* the first track (TrackAttrArray[0]) is the "main" track */
		TrackAttrRec*					TrackAttrArray;
		MeasureBarInfoRec*		MainTrackMeasureBars;
		long									TotalWidth;
		MyBoolean							RecalculationRequired;
		TieTrackPixelRec*			TiePixelTracker; /* NIL if not up to date */
	};


/* create a new schedule state record */
TrackDispScheduleRec*	NewTrackDisplaySchedule(struct TrackObjectRec* MainTrackObj)
	{
		TrackDispScheduleRec*		Schedule;

		Schedule = (TrackDispScheduleRec*)AllocPtrCanFail(sizeof(TrackDispScheduleRec),
			"TrackDispScheduleRec");
		if (Schedule == NIL)
			{
			 FailurePoint1:
				return NIL;
			}
		Schedule->TrackAttrArray = (TrackAttrRec*)AllocPtrCanFail(0,"TrackAttrRec");
		if (Schedule->TrackAttrArray == NIL)
			{
			 FailurePoint2:
				ReleasePtr((char*)Schedule);
				goto FailurePoint1;
			}
		Schedule->MainTrackMeasureBars = (MeasureBarInfoRec*)AllocPtrCanFail(0,
			"MainTrackMeasureBars");
		if (Schedule->MainTrackMeasureBars == NIL)
			{
			 FailurePoint3:
				ReleasePtr((char*)Schedule->TrackAttrArray);
				goto FailurePoint2;
			}
		Schedule->NumTracks = 0;
		Schedule->RecalculationRequired = True;
		Schedule->TiePixelTracker = NIL;
		if (!AddTrackToDisplaySchedule(Schedule,MainTrackObj))
			{
			 FailurePoint4:
				ReleasePtr((char*)Schedule->MainTrackMeasureBars);
				goto FailurePoint3;
			}
		return Schedule;
	}


/* dispose of schedule state record */
void									DisposeTrackDisplaySchedule(TrackDispScheduleRec* Schedule)
	{
		long								Scan;

		CheckPtrExistence(Schedule);
		if (Schedule->TiePixelTracker != NIL)
			{
				DisposeTieTrackPixel(Schedule->TiePixelTracker);
			}
		for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
			{
				CheckPtrExistence(Schedule->TrackAttrArray[Scan].FrameAttrArray);
				ReleasePtr((char*)(Schedule->TrackAttrArray[Scan].FrameAttrArray));
			}
		ReleasePtr((char*)Schedule->MainTrackMeasureBars);
		ReleasePtr((char*)Schedule->TrackAttrArray);
		ReleasePtr((char*)Schedule);
	}


/* add track to schedule list */
MyBoolean							AddTrackToDisplaySchedule(TrackDispScheduleRec* Schedule,
												struct TrackObjectRec* TrackObj)
	{
		TrackAttrRec*				AttrList;

		CheckPtrExistence(Schedule);
		CheckPtrExistence(TrackObj);
#if DEBUG
		{
			long								Scan;

			for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
				{
					if (Schedule->TrackAttrArray[Scan].TrackObj == TrackObj)
						{
							PRERR(ForceAbort,"AddTrackToDisplaySchedule:  track already in list");
						}
				}
		}
#endif
		AttrList = (TrackAttrRec*)ResizePtr((char*)Schedule->TrackAttrArray,
			(Schedule->NumTracks + 1) * sizeof(TrackAttrRec));
		if (AttrList == NIL)
			{
			 FailurePoint1:
				return False;
			}
		Schedule->TrackAttrArray = AttrList;
		Schedule->TrackAttrArray[Schedule->NumTracks].TrackObj = TrackObj;
		Schedule->TrackAttrArray[Schedule->NumTracks].NumFrames = 0;
		Schedule->TrackAttrArray[Schedule->NumTracks].FrameAttrArray
			= (FrameAttrRec*)AllocPtrCanFail(0,"FrameAttrRec");
		if (Schedule->TrackAttrArray[Schedule->NumTracks].FrameAttrArray == NIL)
			{
				goto FailurePoint1;
			}
		Schedule->NumTracks += 1;
		Schedule->RecalculationRequired = True;
		return True;
	}


/* delete a track from the schedule list */
void									DeleteTrackFromDisplaySchedule(TrackDispScheduleRec* Schedule,
												struct TrackObjectRec* TrackObj)
	{
		long								Index;

		CheckPtrExistence(Schedule);
		CheckPtrExistence(TrackObj);
		Index = 0;
		/* find the entry being deleted */
		while (Schedule->TrackAttrArray[Index].TrackObj != TrackObj)
			{
				/* this bounds check is actually after the fact, for the conditional above */
				PRNGCHK(Schedule->TrackAttrArray,&(Schedule->TrackAttrArray[Index]),
					sizeof(Schedule->TrackAttrArray[Index]));
				Index += 1;
				ERROR(Index >= Schedule->NumTracks,PRERR(ForceAbort,
					"DeleteTrackFromDisplaySchedule:  track unknown"));
			}
		ERROR(Index == 0,PRERR(ForceAbort,
			"DeleteTrackFromDisplaySchedule:  deleting the main track"));
		/* delete the actual array */
		ReleasePtr((char*)Schedule->TrackAttrArray[Index].FrameAttrArray);
		/* shift the elements in the array after the hole down.  This will leave */
		/* empty slots at the end of the array but... who cares? */
		while (Index < Schedule->NumTracks - 1)
			{
				Schedule->TrackAttrArray[Index] = Schedule->TrackAttrArray[Index + 1];
				Index += 1;
			}
		/* decrement the extents counter */
		Schedule->NumTracks -= 1;
		/* mark thing ready for total recalculation */
		Schedule->RecalculationRequired = True;
	}


/* note location information must be available by the time this is called. */
static void						BuildTieRepresentation(TrackDispScheduleRec* Schedule)
	{
		long								FrameLimit;
		long								FrameScan;
		TrackObjectRec*			TrackObj;
		TieTrackRec*				TieTracker;

		CheckPtrExistence(Schedule);
		PRNGCHK(Schedule->TrackAttrArray,&(Schedule->TrackAttrArray[0]),
			sizeof(Schedule->TrackAttrArray[0]));
		TrackObj = Schedule->TrackAttrArray[0].TrackObj;
		CheckPtrExistence(TrackObj);
		ERROR(Schedule->TiePixelTracker == NIL,PRERR(ForceAbort,
			"BuildTieRepresentation:  TiePixelTracker is NIL"));
		CheckPtrExistence(Schedule->TiePixelTracker);

		TieTracker = NewTieTracker();
		if (TieTracker == NIL)
			{
				/* oh, well, all the ties just disappeared. */
				return;
			}

		FrameLimit = TrackObjectGetNumFrames(TrackObj);
		for (FrameScan = 0; FrameScan < FrameLimit; FrameScan += 1)
			{
				FrameObjectRec*			Frame;

				Frame = TrackObjectGetFrame(TrackObj,FrameScan);
				if (!IsThisACommandFrame(Frame))
					{
						long								NoteScan;
						long								NoteLimit;

						NoteLimit = NumNotesInFrame(Frame);
						for (NoteScan = 0; NoteScan < NoteLimit; NoteScan += 1)
							{
								NoteObjectRec*			Note;
								NoteObjectRec*			TieTargetNote;
								MyBoolean						ThereIsATieToThisNote;
								long								PreviousTiePixelX;
								long								PreviousTiePixelY;
								NoteObjectRec*			PreviousTieNote;

								Note = GetNoteFromFrame(Frame,NoteScan);

								/* get any note it ties to */
								TieTargetNote = GetNoteTieTarget(Note);

								/* get any note tied to it */
								ThereIsATieToThisNote = GetTieSourceFromDestination(TieTracker,
									&PreviousTiePixelX,&PreviousTiePixelY,&PreviousTieNote,Note);

								/* now, deal with all of this jucy information */
								/* first, get the location of this note, if needed */
								if ((TieTargetNote != NIL) || ThereIsATieToThisNote)
									{
										long								ThisNotePixelX;
										long								ThisNotePixelY;
										EXECUTE(MyBoolean		CallResult;)

										/* gather location information */
										EXECUTE(CallResult = )TrackDisplayIndexToPixel(Schedule,
											0/*main track*/,FrameScan,&ThisNotePixelX);
										ThisNotePixelX += NoteScan * INTERNALSEPARATION;
										ERROR(!CallResult,PRERR(ForceAbort,
											"BuildTieRepresentation:  TrackDisplayIndexToPixel failed"));
										ThisNotePixelY = ConvertPitchToPixel(GetNotePitch(Note),
											GetNoteFlatOrSharpStatus(Note));

										/* do the thing for a note tied to us */
										if (ThereIsATieToThisNote)
											{
												AddTieTrackPixelElement(Schedule->TiePixelTracker,
													PreviousTiePixelX,PreviousTiePixelY,ThisNotePixelX,
													ThisNotePixelY);
											}

										/* do the thing for us tied to another note */
										if (TieTargetNote != NIL)
											{
												AddTiePairToTieTracker(TieTracker,Note,
													ThisNotePixelX,ThisNotePixelY,TieTargetNote);
											}
									}
							}
					}
			}

		DisposeTieTracker(TieTracker);
	}


typedef struct
	{
		long									CurrentIndex;
		FractionRec						CurrentFrameStartTime;
		FractionRec						CurrentFrameDuration;
	} TrackWorkRec;


#ifdef THINK_C
	/* THINK C's optimizer has some peculiar problems with overlaying variables */
	/* and using the wrong size to access compiler temporaries when compiling */
	/* this routine.  See the bit where MaximumWidth is adjusted if it is less */
	/* than the width of the frame being considered. */
	#if __option(global_optimizer)
		#define THINK_C_GLOBAL_OPTIMIZER_ENABLED (1)
		#pragma options(!global_optimizer)
	#else
		#define THINK_C_GLOBAL_OPTIMIZER_ENABLED (0)
	#endif
#endif


/* apply schedule to tracks. */
MyBoolean							TrackDisplayScheduleUpdate(TrackDispScheduleRec* Schedule)
	{
		long								Scan;
		long								CurrentXLocation;
		FractionRec					CurrentTime;
		TrackWorkRec*				WorkArray;
		MyBoolean						DoneFlag;
		MeasureBarInfoRec*	NewMeasureBarArray;
		FractionRec					MeasureBarIntervalThing;
		FractionRec					MeasureBarWidth;
		long								MeasureBarIndex;

		CheckPtrExistence(Schedule);

		/* if everything is up to date, then don't bother */
		if (!Schedule->RecalculationRequired)
			{
				return True;
			}

		if (Schedule->TiePixelTracker != NIL)
			{
				DisposeTieTrackPixel(Schedule->TiePixelTracker);
				Schedule->TiePixelTracker = NIL;
			}
		Schedule->TiePixelTracker = NewTieTrackPixel();
		if (Schedule->TiePixelTracker == NIL)
			{
				return False;
			}

		/* resize the measure bar table */
		Scan = TrackObjectGetNumFrames(Schedule->TrackAttrArray[0].TrackObj);
		NewMeasureBarArray = (MeasureBarInfoRec*)ResizePtr(
			(char*)Schedule->MainTrackMeasureBars,sizeof(MeasureBarInfoRec) * Scan);
		if (NewMeasureBarArray == NIL)
			{
				return False;
			}
		Schedule->MainTrackMeasureBars = NewMeasureBarArray;
		while (Scan > 0)
			{
				/* erase the entries in the table */
				Scan -= 1;
				Schedule->MainTrackMeasureBars[Scan].BarIndex = -1; /* no bar */
			}

		/* resize the track location tables */
		for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
			{
				long								TheirFramesTemp;

				TheirFramesTemp = TrackObjectGetNumFrames(
					Schedule->TrackAttrArray[Scan].TrackObj);
				if (Schedule->TrackAttrArray[Scan].NumFrames != TheirFramesTemp)
					{
						FrameAttrRec*				NewArray;
						long								Index;

						/* resize the array */
						PRNGCHK(Schedule->TrackAttrArray,&(Schedule->TrackAttrArray[Scan]),
							sizeof(Schedule->TrackAttrArray[Scan]));
						CheckPtrExistence(Schedule->TrackAttrArray[Scan].FrameAttrArray);
						NewArray = (FrameAttrRec*)ResizePtr((char*)(Schedule->TrackAttrArray
							[Scan].FrameAttrArray),sizeof(FrameAttrRec) * TheirFramesTemp);
						if (NewArray == NIL)
							{
								return False;
							}
						Schedule->TrackAttrArray[Scan].FrameAttrArray = NewArray;

						/* zero the new space in the array so that we make sure it changes */
						for (Index = Schedule->TrackAttrArray[Scan].NumFrames;
							Index < TheirFramesTemp; Index += 1)
							{
								PRNGCHK(Schedule->TrackAttrArray[Scan].FrameAttrArray,
									&(Schedule->TrackAttrArray[Scan].FrameAttrArray[Index]),
									sizeof(Schedule->TrackAttrArray[Scan].FrameAttrArray[Index]));
								Schedule->TrackAttrArray[Scan].FrameAttrArray[Index].PixelStart = -1;
								Schedule->TrackAttrArray[Scan].FrameAttrArray[Index].Width = -1;
								Schedule->TrackAttrArray[Scan].FrameAttrArray[Index].SquashThisOne = False;
							}

						/* update size number */
						Schedule->TrackAttrArray[Scan].NumFrames = TheirFramesTemp;
					}
			}


		/* initialize local variable counters */
		CurrentXLocation = FIRSTNOTESTART;
		CurrentTime.Integer = 0;
		CurrentTime.Fraction = 0;
		CurrentTime.Denominator = (64*3*5*7*2);

		MeasureBarIntervalThing.Integer = 0;
		MeasureBarIntervalThing.Fraction = 0;
		MeasureBarIntervalThing.Denominator = (64*3*5*7*2);
		MeasureBarWidth.Integer = 1;
		MeasureBarWidth.Fraction = 0;
		MeasureBarWidth.Denominator = (64*3*5*7*2);
		MeasureBarIndex = 1;


		/* build workspace for each track */
		WorkArray = (TrackWorkRec*)AllocPtrCanFail(sizeof(TrackWorkRec)
			* Schedule->NumTracks,"TrackWorkRec");
		if (WorkArray == NIL)
			{
				return False;
			}
		for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
			{
				WorkArray[Scan].CurrentIndex = 0;
				WorkArray[Scan].CurrentFrameStartTime.Integer = 0;
				WorkArray[Scan].CurrentFrameStartTime.Fraction = 0;
				WorkArray[Scan].CurrentFrameStartTime.Denominator = (64*3*5*7*2);
			}


		/* perform scheduling sweep */
		/* how it works: */
		/* the start time and duration of the next frame in each channel are */
		/* calculated.  If the start time is NOT in the future, but is now, then */
		/* the note can be scheduled.  When it is scheduled, it's start time is */
		/* advanced by adding it's duration.  This way, notes will be scheduled */
		/* for display on the screen in the same order that they will be played. */
		do
			{
				long								MaximumWidth;


				/* stage 1:  calculate duration of next event for all frames */
				/* This also figures out what the smallest duration is */
				/* After stage 1, Frame will be NIL if the channel is not scheduled */
				/* this time around, or valid if it contains the one to schedule */
				MaximumWidth = 0;
				for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
					{

						if (WorkArray[Scan].CurrentIndex
							< Schedule->TrackAttrArray[Scan].NumFrames)
							{
								/* this channel still has more items to schedule. */
								/* we want to see if this frame will be scheduled this time. */
								ERROR(FracGreaterThan(&CurrentTime,&(WorkArray[Scan]
									.CurrentFrameStartTime)),PRERR(AllowResume,
									"TrackDisplayScheduleUpdate:  current time later than frame start"));

								if (FractionsEqual(&CurrentTime,&(WorkArray[Scan].CurrentFrameStartTime)))
									{
										FrameObjectRec*			Frame;

										/* the frame is scheduled this time around */

										/* obtain the frame reference */
										Frame = TrackObjectGetFrame(Schedule->
											TrackAttrArray[Scan].TrackObj,WorkArray[Scan].CurrentIndex);

										/* get duration of this frame */
										DurationOfFrame(Frame,&(WorkArray[Scan].CurrentFrameDuration));

										/* decide whether the measure bar flag should be set. */
										/* this is only done for main track, hence "Scan == 0". */
										if (Scan == 0)
											{
												/* for big notes (like quads) this may increment */
												/* MeasureBarIndex several times, but that's the desired */
												/* behavior. */
												while (FracGreaterEqual(&MeasureBarIntervalThing,
													&MeasureBarWidth))
													{
														MyBoolean					GreyedFlag;

														/* if the current measure bar index is greater than the */
														/* measure bar interval, then set the flag & decrement */
														/* the measure bar interval. */
														/*  MeasureBarWidth = num whole notes in a measure */
														/*  MeasureBarIntervalThing = current accumulated notes */
														GreyedFlag = !FractionsEqual(&MeasureBarIntervalThing,
															&MeasureBarWidth);
														SubFractions(&MeasureBarIntervalThing,&MeasureBarWidth,
															&MeasureBarIntervalThing);
														PRNGCHK(Schedule->MainTrackMeasureBars,&(Schedule
															->MainTrackMeasureBars[WorkArray[Scan].CurrentIndex]),
															sizeof(Schedule->MainTrackMeasureBars[WorkArray[Scan]
															.CurrentIndex]));
														Schedule->MainTrackMeasureBars[WorkArray[Scan]
															.CurrentIndex].BarIndex = MeasureBarIndex;
														Schedule->MainTrackMeasureBars[WorkArray[Scan]
															.CurrentIndex].DrawBarGreyed = GreyedFlag;
														MeasureBarIndex += 1;
													}

												/* now, increment the value with the current note */
												AddFractions(&MeasureBarIntervalThing,&(WorkArray[Scan]
													.CurrentFrameDuration),&MeasureBarIntervalThing);

												/* finally, if the note is a meter adjust command, use */
												/* it to recalibrate the measure barring */
												if (IsThisACommandFrame(Frame))
													{
														NoteObjectRec*			MaybeMeterCmd;

														MaybeMeterCmd = GetNoteFromFrame(Frame,0);
														if (GetCommandOpcode(MaybeMeterCmd) == eCmdSetMeter)
															{
																/* set the meter.  this is used by the editor for */
																/* placing measure bars.  measuring restarts */
																/* immediately after this command */
																/* <1i> = numerator, <2i> = denominator */
																if (GetCommandNumericArg2(MaybeMeterCmd) >= 1)
																	{
																		/* make sure denominator is within range */
																		if (GetCommandNumericArg1(MaybeMeterCmd) >= 1)
																			{
																				/* set the new measure width */
																				MeasureBarWidth.Denominator
																					= GetCommandNumericArg2(MaybeMeterCmd);
																				MeasureBarWidth.Fraction
																					= GetCommandNumericArg1(MaybeMeterCmd)
																						% MeasureBarWidth.Denominator;
																				MeasureBarWidth.Integer
																					= GetCommandNumericArg1(MaybeMeterCmd)
																						/ MeasureBarWidth.Denominator;

																				/* reset the current index */
																				MeasureBarIntervalThing.Denominator = (64*3*5*7*2);
																				MeasureBarIntervalThing.Fraction = 0;
																				MeasureBarIntervalThing.Integer = 0;
																			}
																	}
															}
														else if (GetCommandOpcode(MaybeMeterCmd)
															== eCmdSetMeasureNumber)
															{
																MeasureBarIndex = GetCommandNumericArg1(MaybeMeterCmd);
															}
													}
											}

										/* now that we have the frame's duration and have it scheduled */
										/* for display (Frame), increment the start */
										/* time to the end of the frame (which is start of next frame) */
										PRNGCHK(WorkArray,&(WorkArray[Scan]),sizeof(WorkArray[Scan]));
										AddFractions(&(WorkArray[Scan].CurrentFrameDuration),
											&(WorkArray[Scan].CurrentFrameStartTime),
											&(WorkArray[Scan].CurrentFrameStartTime));

										if ((Scan == 0) || !IsThisACommandFrame(Frame))
											{
												/* if this is NOT a command frame, then it should be scheduled */
												/* normally for display. */

												/* set up the drawing attributes */
												PRNGCHK(Schedule->TrackAttrArray[Scan].FrameAttrArray,
													&(Schedule->TrackAttrArray[Scan].FrameAttrArray[WorkArray[Scan]
													.CurrentIndex]),sizeof(Schedule->TrackAttrArray[Scan]
													.FrameAttrArray[WorkArray[Scan].CurrentIndex]));

												Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].PixelStart = CurrentXLocation;
												Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].Width = WidthOfFrameAndDraw(
													NIL,0,0,0,Frame,False/*don'tdraw*/,False);
												Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].SquashThisOne = False;

												ERROR(Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].Width == 0,PRERR(AllowResume,
													"TrackDisplayScheduleUpdate:  frame's width is 0"));

												/* we need to obtain the maximum width of these frame so we */
												/* can figure out what the next X location will be */
												if (Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].Width > MaximumWidth)
													{
														MaximumWidth = Schedule->TrackAttrArray[Scan]
															.FrameAttrArray[WorkArray[Scan].CurrentIndex].Width;
													}
											}
										 else
											{
												/* otherwise this IS a command frame in a non-main track */
												/* and therefore it should not be scheduled */

												/* adjust the displaylocation parameters */
												PRNGCHK(Schedule->TrackAttrArray[Scan].FrameAttrArray,
													&(Schedule->TrackAttrArray[Scan].FrameAttrArray[WorkArray[Scan]
													.CurrentIndex]),sizeof(Schedule->TrackAttrArray[Scan]
													.FrameAttrArray[WorkArray[Scan].CurrentIndex]));

												Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].PixelStart = CurrentXLocation;
												Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].Width = 0; /* no width! */
												Schedule->TrackAttrArray[Scan].FrameAttrArray[
													WorkArray[Scan].CurrentIndex].SquashThisOne = True; /*squish*/
											}

										/* advance frame pointer to the next frame in the channel */
										WorkArray[Scan].CurrentIndex += 1;
									}
							}
					}
				/* advance current X position using biggest width at this position */
				if (MaximumWidth != 0)
					{
						/* MaximumWidth != 0 only happens if some stuff was scheduled */
						CurrentXLocation += MaximumWidth + EXTERNALSEPARATION;
					}


				/* stage 2:  see if we are totally done with the tracks */
				DoneFlag = True;
				for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
					{
						if (WorkArray[Scan].CurrentIndex
							< Schedule->TrackAttrArray[Scan].NumFrames)
							{
								/* this channel still has more items to schedule. */
								DoneFlag = False;
							}
					}


				/* stage 3:  advance the current time counter to point to the start time */
				/* of the closest possible next frame, which corresponds to the soonest one */
				/* immediately after the smallest frame that we just scheduled. */
				/* if we aren't done, figure out when the soonest next frame starts */
				if (!DoneFlag)
					{
						FractionRec					SmallestDelta;
						MyBoolean						SmallestDeltaValid;

						/* but there's still more to go, so advance the time counter to */
						/* the closest possible next frame. */
						/* This will happen if a very small note ends a track.  The duration */
						/* of the small note will be added, but since the track is done, */
						/* there might not be any note whatsoever at that time.  In this */
						/* case, we need to find when the next note will be. */
						/* In order to do this, we search for the soonest possible frame */
						/* that we can find that is past CurrentTime. */
						SmallestDeltaValid = False;
						for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
							{
								if (WorkArray[Scan].CurrentIndex
									< Schedule->TrackAttrArray[Scan].NumFrames)
									{
										FractionRec				DeltaTemp;

										/* start time of next frame should NEVER be less than */
										/* current time. */
										ERROR(FracGreaterEqual(&CurrentTime,&(WorkArray[Scan]
											.CurrentFrameStartTime)) && !FractionsEqual(&CurrentTime,
											&(WorkArray[Scan].CurrentFrameStartTime)),PRERR(ForceAbort,
											"TrackDisplayScheduleUpdate:  next less than current time"));

										/* figure out difference between then and now */
										SubFractions(&(WorkArray[Scan].CurrentFrameStartTime),
											&CurrentTime,&DeltaTemp);
										if (!SmallestDeltaValid
											|| FracGreaterThan(&SmallestDelta,&DeltaTemp))
											{
												SmallestDelta = DeltaTemp;
												SmallestDeltaValid = True;
											}
									}
							}


						/* if SmallestDeltaValid is False at this point, then there aren't */
						/* any things that can be done, which should never happen. */
						ERROR(!SmallestDeltaValid,PRERR(AllowResume,
							"TrackDisplayScheduleUpdate:  no track, but DoneFlag is false"));
						AddFractions(&SmallestDelta,&CurrentTime,&CurrentTime);
					}

				/* make sure things don't get out of hand.  this magic number is derived */
				/* from the denominator of the smallest possible note:  64th note, with */
				/* a possible 3, 5, or 7 division, and (3/2) dot. */
				ERROR(CurrentTime.Denominator > 64*3*5*7*2,PRERR(ForceAbort,
					"TrackDisplayScheduleUpdate:  factoring malfunction"));
			} while (!DoneFlag);
		Schedule->TotalWidth = CurrentXLocation;
		Schedule->RecalculationRequired = False;
		ReleasePtr((char*)WorkArray);

		/* build tie representation */
		BuildTieRepresentation(Schedule);

		return True;
	}


#ifdef THINK_C
	#if THINK_C_GLOBAL_OPTIMIZER_ENABLED
		#pragma options(global_optimizer)
	#endif
#endif


/* find out where a pixel position is located in the score.  it returns the */
/* index of the frame the position is on and *OnFrame is True if it was on a frame. */
/* otherwise, it returns the index of the frame that the position precedes and */
/* sets *OnFrame to False.  TrackObj is the track that we want to find positions */
/* with respect to. */
MyBoolean							TrackDisplayPixelToIndex(TrackDispScheduleRec* Schedule,
												struct TrackObjectRec* TrackObj, long PixelPosition,
												MyBoolean* OnFrame, long* Index)
	{
		FrameAttrRec*				FrameAttrArray;
		long								Scan;
		long								Limit;
		long								LeftIndex;
		long								RightIndex;

		CheckPtrExistence(Schedule);
		CheckPtrExistence(TrackObj);
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return False;
			}
		for (Scan = 0; Scan < Schedule->NumTracks; Scan += 1)
			{
				if (TrackObj == Schedule->TrackAttrArray[Scan].TrackObj)
					{
						FrameAttrArray = Schedule->TrackAttrArray[Scan].FrameAttrArray;
						CheckPtrExistence(FrameAttrArray);
						Limit = Schedule->TrackAttrArray[Scan].NumFrames;
						goto FoundTrackPoint;
					}
			}
		EXECUTE(PRERR(ForceAbort,"TrackDisplayPixelToIndex:  unknown track specified"));
	 FoundTrackPoint:
		/* perform a binary search to locate the cell responsible for the track */
		/* first, check to make sure index is within range */
		if (PixelPosition < 0)
			{
				*OnFrame = False;
				*Index = 0;
				return True;
			}
		if ((Limit == 0) || (PixelPosition >= FrameAttrArray[Limit - 1].PixelStart
			+ FrameAttrArray[Limit - 1].Width))
			{
				/* since we are beyond the end, we set *OnFrame false to indicate that */
				/* the pixel position given is not a valid frame, and we return the index */
				/* of the [nonexistent] next frame */
				*OnFrame = False;
				*Index = Limit;
				return True;
			}
		/* initialize counter variables.  Invariant:  Limit > 0 */
		LeftIndex = 0;
		RightIndex = Limit - 1;
		while (LeftIndex != RightIndex)
			{
				long								Midpoint;

				/* find out pivot point for comparison */
				Midpoint = (LeftIndex + RightIndex) / 2;
				/* check to see if index falls on pivot frame */
				PRNGCHK(FrameAttrArray,&(FrameAttrArray[Midpoint]),
					sizeof(FrameAttrArray[Midpoint]));
				if ((FrameAttrArray[Midpoint].PixelStart <= PixelPosition)
					&& (FrameAttrArray[Midpoint].PixelStart
					+ FrameAttrArray[Midpoint].Width > PixelPosition))
					{
						*OnFrame = True;
						*Index = Midpoint;
						return True;
					}
				/* check to see if we can discard left half */
				if (FrameAttrArray[Midpoint].PixelStart <= PixelPosition)
					{
						LeftIndex = Midpoint + 1;
					}
				/* check to see if we can discard right half */
				if (FrameAttrArray[Midpoint].PixelStart > PixelPosition)
					{
						RightIndex = Midpoint;
					}
			}
		/* invariant: LeftIndex == RightIndex */
		/* now figure out what the results mean */
		if (FrameAttrArray[LeftIndex].PixelStart > PixelPosition)
			{
				/* it is before the one we found, in the interstice. */
				*OnFrame = False;
				*Index = LeftIndex;
				return True;
			}
		/* otherwise, it must be on the one we found */
		ERROR(FrameAttrArray[LeftIndex].PixelStart + FrameAttrArray[LeftIndex].Width
			< PixelPosition,PRERR(ForceAbort,"TrackDisplayPixelToIndex:  error"));
		*OnFrame = True;
		*Index = LeftIndex;
		return True;
	}


/* mark scheduler so that it recalculates all the stuff.  the track and frame */
/* index specify where recalculation has to start from, so that data before that */
/* doesn't need to be updated. */
void									TrackDisplayScheduleMarkChanged(TrackDispScheduleRec* Schedule,
												struct TrackObjectRec* TrackObj, long FrameIndex)
	{
		CheckPtrExistence(Schedule);
		CheckPtrExistence(TrackObj);
		/* we ignore starting position parameters for now. */
		Schedule->RecalculationRequired = True;
	}


/* calculate the pixel index for a frame based on it's array index */
MyBoolean							TrackDisplayIndexToPixel(TrackDispScheduleRec* Schedule,
												long TrackIndex, long FrameIndex, long* Pixel)
	{
		CheckPtrExistence(Schedule);
		ERROR((TrackIndex < 0) || (TrackIndex >= TrackDisplayGetNumTracks(Schedule)),
			PRERR(ForceAbort,"TrackDisplayIndexToPixel:  track index out of range"));
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return False;
			}
		PRNGCHK(Schedule->TrackAttrArray[TrackIndex].FrameAttrArray,
			&(Schedule->TrackAttrArray[TrackIndex].FrameAttrArray[FrameIndex]),
			sizeof(Schedule->TrackAttrArray[TrackIndex].FrameAttrArray[FrameIndex]));
		*Pixel = Schedule->TrackAttrArray[TrackIndex].FrameAttrArray[FrameIndex].PixelStart;
		return True;
	}


/* get the number of tracks controlled by this scheduler */
long									TrackDisplayGetNumTracks(TrackDispScheduleRec* Schedule)
	{
		CheckPtrExistence(Schedule);
		return Schedule->NumTracks;
	}


/* look up the track index of a specific track */
long									TrackDisplayGetTrackIndex(TrackDispScheduleRec* Schedule,
												struct TrackObjectRec* TrackObj)
	{
		long								Scan;

		CheckPtrExistence(Schedule);
		Scan = 0;
		while (Scan < Schedule->NumTracks)
			{
				if (Schedule->TrackAttrArray[Scan].TrackObj == TrackObj)
					{
						return Scan;
					}
				Scan += 1;
			}
		EXECUTE(PRERR(ForceAbort,"TrackDisplayGetTrackIndex:  unknown track"));
	}


/* get the track specified by the index (0..last func return value - 1) */
TrackObjectRec*				TrackDisplayGetParticularTrack(TrackDispScheduleRec* Schedule,
												long Index)
	{
		CheckPtrExistence(Schedule);
		PRNGCHK(Schedule->TrackAttrArray,&(Schedule->TrackAttrArray[Index].TrackObj),
			sizeof(TrackObjectRec*));
		return Schedule->TrackAttrArray[Index].TrackObj;
	}


/* get the total length of the track */
MyBoolean							TrackDisplayGetTotalLength(TrackDispScheduleRec* Schedule,
												long* LongestLength)
	{
		CheckPtrExistence(Schedule);
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return False;
			}
		*LongestLength = Schedule->TotalWidth;
		return True;
	}


/* find out what NOTE (not frame) is at the specified location.  NIL */
/* is returned if no note is there.  If the flag is clear, then it's a note, */
/* otherwise it's a command.  If you intend to modify it, then pass an address */
/* for FrameIndex so you know what to pass to TrackDisplayScheduleMarkChanged. */
struct NoteObjectRec*	TrackDisplayGetUnderlyingNote(TrackDispScheduleRec* Schedule,
												long TrackIndex, MyBoolean* CommandFlag, long PixelX,
												long* FrameIndex)
	{
		MyBoolean						OnFrame;
		long								Index;
		FrameObjectRec*			Frame;
		long								BeginningOfFrame;
		OrdType							FrameWidth;

		CheckPtrExistence(Schedule);
		ERROR((TrackIndex < 0) || (TrackIndex >= TrackDisplayGetNumTracks(Schedule)),
			PRERR(ForceAbort,"TrackDisplayGetUnderlyingNote:  track index out of range"));
		if (!TrackDisplayPixelToIndex(Schedule,Schedule->TrackAttrArray[TrackIndex].TrackObj,
			PixelX,&OnFrame,&Index))
			{
				*FrameIndex = 0;
				return NIL;
			}
		if (FrameIndex != NIL)
			{
				*FrameIndex = Index;
			}
		if (!OnFrame)
			{
				return NIL;
			}
		if (!TrackDisplayIndexToPixel(Schedule,TrackIndex,Index,&BeginningOfFrame))
			{
				return NIL;
			}
		Frame = TrackObjectGetFrame(Schedule->TrackAttrArray[TrackIndex].TrackObj,Index);
		FrameWidth = WidthOfFrameAndDraw(NIL,0,0,0,Frame,False/*nodraw*/,False);
		*CommandFlag = IsThisACommandFrame(Frame);
		if (*CommandFlag)
			{
				/* just a single command */
				if ((BeginningOfFrame <= PixelX) && (BeginningOfFrame + FrameWidth > PixelX))
					{
						return GetNoteFromFrame(Frame,0);
					}
				 else
					{
						return NIL;
					}
			}
		 else
			{
				/* notes are in the frame. */
				if ((BeginningOfFrame <= PixelX) && (BeginningOfFrame + FrameWidth > PixelX))
					{
						long								NoteIndex;
						long								NumFrames;

						/* calculate possible note index */
						NoteIndex = (PixelX - BeginningOfFrame) / INTERNALSEPARATION;
						ERROR(NumNotesInFrame(Frame) == 0,PRERR(ForceAbort,
							"TrackDisplayGetUnderlyingNote:  frame doesn't contain any notes"));
						NumFrames = NumNotesInFrame(Frame);
						if (NoteIndex > NumFrames - 1)
							{
								NoteIndex = NumFrames - 1;
							}
						return GetNoteFromFrame(Frame,NoteIndex);
					}
				 else
					{
						return NIL;
					}
			}
	}


/* find out if a certain frame should be drawn.  this is for squashing command */
/* frames from channels other than the front channel */
MyBoolean							TrackDisplayShouldWeDrawIt(TrackDispScheduleRec* Schedule,
												long TrackIndex, long FrameIndex)
	{
		CheckPtrExistence(Schedule);
		ERROR((TrackIndex < 0) || (TrackIndex >= TrackDisplayGetNumTracks(Schedule)),
			PRERR(ForceAbort,"TrackDisplayShouldWeDrawIt:  track index out of range"));
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return False;
			}
		PRNGCHK(Schedule->TrackAttrArray[TrackIndex].FrameAttrArray,
			&(Schedule->TrackAttrArray[TrackIndex].FrameAttrArray[FrameIndex]),
			sizeof(Schedule->TrackAttrArray[TrackIndex].FrameAttrArray[FrameIndex]));
		return !Schedule->TrackAttrArray[TrackIndex]
			.FrameAttrArray[FrameIndex].SquashThisOne;
	}


/* find out what index a measure bar should be.  if it shouldn't be a measure */
/* bar, then it returns -1. */
long									TrackDisplayMeasureBarIndex(TrackDispScheduleRec* Schedule,
												long FrameIndex)
	{
		CheckPtrExistence(Schedule);
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return -1;
			}
		PRNGCHK(Schedule->MainTrackMeasureBars,&(Schedule->MainTrackMeasureBars[FrameIndex]),
			sizeof(Schedule->MainTrackMeasureBars[FrameIndex]));
		return Schedule->MainTrackMeasureBars[FrameIndex].BarIndex;
	}


/* find out the frame index for the specified measure bar.  if it can't find the */
/* specified measure bar, it returns the location for the largest one less than */
/* the value specified.  it returns false if calculation fails. */
MyBoolean							TrackDisplayMeasureIndexToFrame(TrackDispScheduleRec* Schedule,
												long MeasureBarIndex, long* FrameIndexOut)
	{
		long								Scan;

		CheckPtrExistence(Schedule);
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return False;
			}
		for (Scan = Schedule->TrackAttrArray[0].NumFrames - 1; Scan >= 0; Scan -= 1)
			{
				PRNGCHK(Schedule->MainTrackMeasureBars,&(Schedule->MainTrackMeasureBars[Scan]),
					sizeof(Schedule->MainTrackMeasureBars[Scan]));
				if ((Schedule->MainTrackMeasureBars[Scan].BarIndex >= 0)
					&& (Schedule->MainTrackMeasureBars[Scan].BarIndex <= MeasureBarIndex))
					{
						*FrameIndexOut = Scan;
						return True;
					}
			}
		*FrameIndexOut = Schedule->TrackAttrArray[0].NumFrames - 1;
		return True;
	}


/* find out if a measure bar should be greyed out or drawn solid.  it returns */
/* true if the measure bar should be greyed. */
MyBoolean							TrackDisplayShouldMeasureBarBeGreyed(
												TrackDispScheduleRec* Schedule, long FrameIndex)
	{
		CheckPtrExistence(Schedule);
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return False;
			}
		PRNGCHK(Schedule->MainTrackMeasureBars,&(Schedule->MainTrackMeasureBars[FrameIndex]),
			sizeof(Schedule->MainTrackMeasureBars[FrameIndex]));
		ERROR(Schedule->MainTrackMeasureBars[FrameIndex].BarIndex == -1,PRERR(AllowResume,
			"TrackDisplayShouldMeasureBarBeGreyed:  no measure bar should be drawn here."));
		return Schedule->MainTrackMeasureBars[FrameIndex].DrawBarGreyed;
	}


/* get a list of coordinates that need to be tied together */
/* it might return NIL if there isn't a tie tracker. */
struct TieIntersectListRec*	TrackDisplayGetTieIntervalList(TrackDispScheduleRec* Schedule,
												long StartX, long Width)
	{
		TieIntersectListRec*	TheList;

		CheckPtrExistence(Schedule);
		if (!TrackDisplayScheduleUpdate(Schedule))
			{
				return NIL;
			}
		if (Schedule->TiePixelTracker == NIL)
			{
				return NIL;
			}
		TheList = GetTieTrackPixelIntersecting(Schedule->TiePixelTracker,StartX,Width);
		return TheList;
	}
