/* DeterminedNoteStructure.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"

#define ShowMeFrozenNoteRec
#define ShowMe_NoteObjectRec
#define ShowMeIncrParamUpdateRec
#include "DeterminedNoteStructure.h"
#include "Memory.h"
#include "BinaryCodedDecimal.h"
#include "NoteObject.h"
#include "IncrementalParameterUpdator.h"
#include "FloatingPoint.h"
#include "Frequency.h"
#include "PlayTrackInfoThang.h"


static FrozenNoteRec*				FrozenNoteFreeList = NIL;


/* flush cached frozen note blocks */
void								FlushFrozenNoteStructures(void)
	{
		while (FrozenNoteFreeList != NIL)
			{
				FrozenNoteRec*		Temp;

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


/* make sure the frozen note structure is valid */
#if DEBUG
void								ValidateFrozenNote(FrozenNoteRec* FrozenNote)
	{
		FrozenNoteRec*		Scan;

		CheckPtrExistence(FrozenNote);
		Scan = FrozenNoteFreeList;
		while (Scan != NIL)
			{
				if (Scan == FrozenNote)
					{
						PRERR(ForceAbort,"ValidateFrozenNote:  note record is on free list");
					}
				Scan = Scan->Next;
			}
	}
#endif


/* dispose of a note object */
void								DisposeFrozenNote(FrozenNoteRec* FrozenNote)
	{
		ValidateFrozenNote(FrozenNote);
		FrozenNote->Next = FrozenNoteFreeList;
		FrozenNoteFreeList = FrozenNote;
	}


/* build a new note object with all parameters determined.  *StartAdjustOut */
/* indicates how many ticks before (negative) or after (positive) now that */
/* the key-down should occur.  this is added to the scanning gap size and envelope */
/* origins to figure out how to schedule the note */
FrozenNoteRec*			FixNoteParameters(struct IncrParamUpdateRec* GlobalParamSource,
											struct NoteObjectRec* Note, long* StartAdjustOut,
											float OverallVolumeScaling, float EnvelopeTicksPerDurationTick)
	{
		FrozenNoteRec*		FrozenNote;
		float							FloatTemp;
		float							FloatOtherTemp;
		long							IntTemp;

		CheckPtrExistence(GlobalParamSource);
		CheckPtrExistence(Note);
		ERROR(IsItACommand(Note),PRERR(ForceAbort,"FixNoteParameters:  note is a command"));
		ERROR(GetNoteIsItARest(Note),PRERR(ForceAbort,"FixNoteParameters:  can't handle rests"));

		if (FrozenNoteFreeList != NIL)
			{
				FrozenNote = FrozenNoteFreeList;
				FrozenNoteFreeList = FrozenNoteFreeList->Next;
			}
		 else
			{
				FrozenNote = (FrozenNoteRec*)AllocPtrCanFail(sizeof(FrozenNoteRec),"FrozenNoteRec");
				if (FrozenNote == NIL)
					{
						return NIL;
					}
			}
		EXECUTE(FrozenNote->Next = (FrozenNoteRec*)0x81818181;)

		/* reference to the note that defines this note. */
		FrozenNote->OriginalNote = Note;

		/* frequency determined by pitch index + detuning, in Hertz */
		IntTemp = Note->a.Note.Pitch + GlobalParamSource->TransposeHalfsteps;
		if (IntTemp < 0)
			{
				IntTemp = 0;
			}
		else if (IntTemp > NUMNOTES - 1)
			{
				IntTemp = NUMNOTES - 1;
			}
		FloatTemp = (float)FEXP(((float)(IntTemp - CENTERNOTE) / 12) * (float)LOG2)
			* (float)MIDDLEC;
		switch (Note->Flags & eDetuningModeMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad detuning mode"));
					break;
				case eDetuningModeDefault:
					FloatOtherTemp = SmallBCD2Single(Note->a.Note.Detuning)
						* LargeBCD2Single(GlobalParamSource->CurrentDetune);
					if (GlobalParamSource->DetuneHertz)
						{
							goto DetuneHertzPoint;
						}
					 else
						{
							goto DetuneHalfStepsPoint;
						}
					break;
				case eDetuningModeHalfSteps:
					FloatOtherTemp = SmallBCD2Single(Note->a.Note.Detuning)
						+ LargeBCD2Single(GlobalParamSource->CurrentDetune);
				 DetuneHalfStepsPoint:
					FrozenNote->NominalFrequency = FloatTemp
						* (float)FEXP((FloatOtherTemp / 12) * (float)LOG2);
					break;
				case eDetuningModeHertz:
					FloatOtherTemp = SmallBCD2Single(Note->a.Note.Detuning)
						+ LargeBCD2Single(GlobalParamSource->CurrentDetune);
				 DetuneHertzPoint:
					FrozenNote->NominalFrequency = FloatTemp + FloatOtherTemp;
					break;
			}

		/* frequency used for doing multisampling, in Hertz */
		if (Note->a.Note.MultisamplePitchAsIf != -1)
			{
				FrozenNote->MultisampleFrequency = (float)FEXP(((float)(Note->a.Note
					.MultisamplePitchAsIf - CENTERNOTE) / 12) * (float)LOG2) * (float)MIDDLEC;
			}
		 else
			{
				FrozenNote->MultisampleFrequency = FrozenNote->NominalFrequency;
			}

		/* acceleration of envelopes */
		FrozenNote->HurryUpFactor = SmallBCD2Single(Note->a.Note.HurryUpFactor)
			* LargeBCD2Single(GlobalParamSource->CurrentHurryUp);

		/* duration, in envelope ticks */
		switch (Note->Flags & eDurationMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad duration flags"));
					break;
				case e64thNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION / 64;
					break;
				case e32ndNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION / 32;
					break;
				case e16thNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION / 16;
					break;
				case e8thNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION / 8;
					break;
				case e4thNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION / 4;
					break;
				case e2ndNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION / 2;
					break;
				case eWholeNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION;
					break;
				case eDoubleNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION * 2;
					break;
				case eQuadNote:
					IntTemp = DURATIONUPDATECLOCKRESOLUTION * 4;
					break;
			}
		switch (Note->Flags & eDivisionMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad division flags"));
					break;
				case eDiv1Modifier:
					break;
				case eDiv3Modifier:
					IntTemp = IntTemp / 3;
					break;
				case eDiv5Modifier:
					IntTemp = IntTemp / 5;
					break;
				case eDiv7Modifier:
					IntTemp = IntTemp / 7;
					break;
			}
		if ((Note->Flags & eDotModifier) != 0)
			{
				IntTemp = (IntTemp * 3) / 2;
			}
		FloatTemp = IntTemp;
		switch (Note->Flags & eDurationAdjustMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad duration adjust flags"));
					break;
				case eDurationAdjustDefault:
					if (GlobalParamSource->DurationAdjustAdditive)
						{
							goto DurationAdjustAddPoint;
						}
					 else
						{
							goto DurationAdjustMultPoint;
						}
					break;
				case eDurationAdjustAdditive:
				 DurationAdjustAddPoint:
					FloatTemp = FloatTemp + SmallBCD2Single(Note->a.Note.DurationAdjust)
						* (DURATIONUPDATECLOCKRESOLUTION / 4);
					break;
				case eDurationAdjustMultiplicative:
				 DurationAdjustMultPoint:
					FloatTemp = FloatTemp * SmallBCD2Single(Note->a.Note.DurationAdjust);
					break;
			}
		if (GlobalParamSource->DurationAdjustAdditive)
			{
				FloatTemp = FloatTemp + LargeBCD2Single(GlobalParamSource->CurrentDurationAdjust)
					* (DURATIONUPDATECLOCKRESOLUTION / 4);
			}
		 else
			{
				FloatTemp = FloatTemp * LargeBCD2Single(GlobalParamSource->CurrentDurationAdjust);
			}
		/* this line is what converts from duration update ticks to envelope ticks */
		FrozenNote->Duration = FloatTemp * EnvelopeTicksPerDurationTick;

		/* portamento duration, in envelope ticks */
		FrozenNote->PortamentoDuration = SmallBCD2Single(Note->a.Note.PortamentoDuration)
			* (DURATIONUPDATECLOCKRESOLUTION / 4) * EnvelopeTicksPerDurationTick;

		/* first release point, in envelope ticks after start of note */
		switch (Note->Flags & eRelease1OriginMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad release point 1 origin flags"));
					break;
				case eRelease1FromStart:
					FrozenNote->ReleasePoint1 = SmallBCD2Single(Note->a.Note.ReleasePoint1)
						* FrozenNote->Duration;
					FrozenNote->Release1FromStart = True;
					break;
				case eRelease1FromEnd:
					FrozenNote->ReleasePoint1 = (1 - SmallBCD2Single(Note->a.Note.ReleasePoint1))
						* FrozenNote->Duration;
					FrozenNote->Release1FromStart = False;
					break;
				case eRelease1FromDefault:
					if (GlobalParamSource->ReleasePoint1FromStart)
						{
							FrozenNote->ReleasePoint1 = (SmallBCD2Single(Note->a.Note.ReleasePoint1)
								+ LargeBCD2Single(GlobalParamSource->CurrentReleasePoint1))
								* FrozenNote->Duration;
							FrozenNote->Release1FromStart = True;
						}
					 else
						{
							FrozenNote->ReleasePoint1 = (1 - (SmallBCD2Single(
								Note->a.Note.ReleasePoint1) + LargeBCD2Single(
								GlobalParamSource->CurrentReleasePoint1))) * FrozenNote->Duration;
							FrozenNote->Release1FromStart = False;
						}
					break;
			}

		/* second release point, in envelope ticks after start of note */
		switch (Note->Flags & eRelease2OriginMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad release point 2 origin flags"));
					break;
				case eRelease2FromStart:
					FrozenNote->ReleasePoint2 = SmallBCD2Single(Note->a.Note.ReleasePoint2)
						* FrozenNote->Duration;
					FrozenNote->Release2FromStart = True;
					break;
				case eRelease2FromEnd:
					FrozenNote->ReleasePoint2 = (1 - SmallBCD2Single(Note->a.Note.ReleasePoint2))
						* FrozenNote->Duration;
					FrozenNote->Release2FromStart = False;
					break;
				case eRelease2FromDefault:
					if (GlobalParamSource->ReleasePoint2FromStart)
						{
							FrozenNote->ReleasePoint2 = (SmallBCD2Single(Note->a.Note.ReleasePoint2)
								+ LargeBCD2Single(GlobalParamSource->CurrentReleasePoint2))
								* FrozenNote->Duration;
							FrozenNote->Release2FromStart = True;
						}
					 else
						{
							FrozenNote->ReleasePoint2 = (1 - (SmallBCD2Single(
								Note->a.Note.ReleasePoint2) + LargeBCD2Single(
								GlobalParamSource->CurrentReleasePoint2))) * FrozenNote->Duration;
							FrozenNote->Release2FromStart = False;
						}
					break;
			}

		/* third release point, in envelope ticks after start of note */
		if ((Note->Flags & eRelease3FromStartNotEnd) != 0)
			{
				FrozenNote->ReleasePoint3 = 0;
				FrozenNote->Release3FromStart = True;
			}
		 else
			{
				FrozenNote->ReleasePoint3 = FrozenNote->Duration;
				FrozenNote->Release3FromStart = False;
			}

		/* overall loudness adjustment for envelopes, including global volume scaling */
		FrozenNote->LoudnessAdjust = SmallBCD2Single(Note->a.Note.OverallLoudnessAdjustment)
			* LargeBCD2Single(GlobalParamSource->CurrentVolume) * OverallVolumeScaling;

		/* stereo positioning for note */
		FloatTemp = SmallBCD2Single(Note->a.Note.StereoPositionAdjustment)
			+ LargeBCD2Single(GlobalParamSource->CurrentStereoPosition);
		if (FloatTemp < -1)
			{
				FloatTemp = -1;
			}
		else if (FloatTemp > 1)
			{
				FloatTemp = 1;
			}
		FrozenNote->StereoPosition = FloatTemp;

		/* accent values for controlling envelopes */
		FrozenNote->Accents.Accent1 = SmallBCD2Single(Note->a.Note.Accent1)
			+ LargeBCD2Single(GlobalParamSource->CurrentAccent1);
		FrozenNote->Accents.Accent2 = SmallBCD2Single(Note->a.Note.Accent2)
			+ LargeBCD2Single(GlobalParamSource->CurrentAccent2);
		FrozenNote->Accents.Accent3 = SmallBCD2Single(Note->a.Note.Accent3)
			+ LargeBCD2Single(GlobalParamSource->CurrentAccent3);
		FrozenNote->Accents.Accent4 = SmallBCD2Single(Note->a.Note.Accent4)
			+ LargeBCD2Single(GlobalParamSource->CurrentAccent4);
		FrozenNote->Accents.Accent5 = LargeBCD2Single(GlobalParamSource->CurrentAccent5);
		FrozenNote->Accents.Accent6 = LargeBCD2Single(GlobalParamSource->CurrentAccent6);
		FrozenNote->Accents.Accent7 = LargeBCD2Single(GlobalParamSource->CurrentAccent7);
		FrozenNote->Accents.Accent8 = LargeBCD2Single(GlobalParamSource->CurrentAccent8);

		/* pitch displacement maximum depth, in tonal Hertz */
		FrozenNote->PitchDisplacementDepthLimit = SmallBCD2Single(
			Note->a.Note.PitchDisplacementDepthAdjustment)
			* LargeBCD2Single(GlobalParamSource->CurrentPitchDisplacementDepthLimit);

		/* pitch displacement maximum rate, in LFO Hertz */
		FrozenNote->PitchDisplacementRateLimit = SmallBCD2Double(
			Note->a.Note.PitchDisplacementRateAdjustment)
			* LargeBCD2Double(GlobalParamSource->CurrentPitchDisplacementRateLimit);

		/* pitch displacement start point, in envelope clocks after start of note */
		switch (Note->Flags & ePitchDisplacementStartOriginMask)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"FixNoteParameters:  bad pitch disp start flags"));
					break;
				case ePitchDisplacementStartFromStart:
					FrozenNote->PitchDisplacementStartPoint = FrozenNote->Duration
						* SmallBCD2Single(Note->a.Note.PitchDisplacementStartPoint);
					break;
				case ePitchDisplacementStartFromEnd:
					FrozenNote->PitchDisplacementStartPoint = FrozenNote->Duration
						* (1 - SmallBCD2Single(Note->a.Note.PitchDisplacementStartPoint));
					break;
				case ePitchDisplacementStartFromDefault:
					if (GlobalParamSource->PitchDisplacementStartPointFromStart)
						{
							FrozenNote->PitchDisplacementStartPoint = FrozenNote->Duration
								* (SmallBCD2Single(Note->a.Note.PitchDisplacementStartPoint)
								+ LargeBCD2Single(GlobalParamSource->CurrentPitchDisplacementStartPoint));
						}
					 else
						{
							FrozenNote->PitchDisplacementStartPoint = FrozenNote->Duration
								* (1 - (SmallBCD2Single(Note->a.Note.PitchDisplacementStartPoint)
								+ LargeBCD2Single(GlobalParamSource->CurrentPitchDisplacementStartPoint)));
						}
					break;
			}

		*StartAdjustOut = (SmallBCD2Single(Note->a.Note.EarlyLateAdjust)
			+ LargeBCD2Single(GlobalParamSource->CurrentEarlyLateAdjust))
			* FrozenNote->Duration;

		return FrozenNote;
	}
