/* TrackEffectGenerator.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 ShowMe_NoteObjectRec
#include "TrackEffectGenerator.h"
#include "EffectSpecList.h"
#include "Memory.h"
#include "DelayLine.h"
#include "BinaryCodedDecimal.h"
#include "LinearTransition.h"
#include "NoteObject.h"
#include "IncrementalParameterUpdator.h"
#include "NLProc.h"
#include "FilterArray.h"
#include "Analyzer.h"


typedef struct CommandConsCell
	{
		/* link.  only forward link used since insertion sorting is not necessary */
		struct CommandConsCell*		Next;

		/* command that has been suspended */
		NoteObjectRec*						Command;

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


typedef struct OneEffectRec
	{
		struct OneEffectRec*		Next;
		EffectTypes							Type;
		union
			{
				DelayLineRec*						DelayEffect;
				NLProcRec*							NLProcEffect;
				FilterArrayRec*					FilterEffect;
				AnalyzerRec*						AnalyzerEffect;
			} u;
	} OneEffectRec;


struct TrackEffectGenRec
	{
		LargeBCDType						CurrentAccent1;
		struct LinearTransRec*	Accent1Change;
		long										Accent1ChangeCountdown;

		LargeBCDType						CurrentAccent2;
		struct LinearTransRec*	Accent2Change;
		long										Accent2ChangeCountdown;

		LargeBCDType						CurrentAccent3;
		struct LinearTransRec*	Accent3Change;
		long										Accent3ChangeCountdown;

		LargeBCDType						CurrentAccent4;
		struct LinearTransRec*	Accent4Change;
		long										Accent4ChangeCountdown;

		OneEffectRec*						List;
		MyBoolean								Stereo;

		/* list of commands that have been scheduled but haven't been executed yet. */
		CommandConsCell*				ScanningGapListHead;
		CommandConsCell*				ScanningGapListTail;
		/* this is the current duration update index for the scanning gap list */
		long										ExecutionIndex;

		/* track effect enable.  turns off processing, but command handling */
		/* remains enabled (otherwise you couldn't turn processing back on) */
		MyBoolean								Enable;
	};


static CommandConsCell*		CommandConsFreeList = NIL;


/* dispose of cached effect generator structures */
void									FlushTrackEffectGeneratorInfo(void)
	{
		while (CommandConsFreeList != NIL)
			{
				CommandConsCell*		Temp;

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


/* create a new track effect generator */
TrackEffectGenRec*		NewTrackEffectGenerator(struct EffectSpecListRec* SpecList,
												long FramesPerSecond, MyBoolean DoingStereo,
												float InverseVolume, struct MainWindowRec* MainWindow,
												long ScanningGapWidthInEnvelopeTicks)
	{
		TrackEffectGenRec*	Generator;
		OneEffectRec*				Appender;
		long								List;
		long								Scan;

		CheckPtrExistence(SpecList);
		Generator = (TrackEffectGenRec*)AllocPtrCanFail(sizeof(TrackEffectGenRec),
			"TrackEffectGenRec");
		if (Generator == NIL)
			{
			 FailurePoint1:
				return NIL;
			}
		Generator->List = NIL;
		Generator->Stereo = DoingStereo;
		Generator->ScanningGapListHead = NIL;
		Generator->ScanningGapListTail = NIL;
		Generator->Enable = True;

		/* 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. */
		Generator->ExecutionIndex = - ScanningGapWidthInEnvelopeTicks;

		/* initialize accent trackers */
		Generator->CurrentAccent1 = 0;
		Generator->Accent1ChangeCountdown = 0;
		Generator->Accent1Change = NewLinearTransition(0,0,1);
		if (Generator->Accent1Change == NIL)
			{
			 FailurePoint2:
				ReleasePtr((char*)Generator);
				goto FailurePoint1;
			}
		Generator->CurrentAccent2 = 0;
		Generator->Accent2ChangeCountdown = 0;
		Generator->Accent2Change = NewLinearTransition(0,0,1);
		if (Generator->Accent2Change == NIL)
			{
			 FailurePoint3:
				DisposeLinearTransition(Generator->Accent1Change);
				goto FailurePoint2;
			}
		Generator->CurrentAccent3 = 0;
		Generator->Accent3ChangeCountdown = 0;
		Generator->Accent3Change = NewLinearTransition(0,0,1);
		if (Generator->Accent3Change == NIL)
			{
			 FailurePoint4:
				DisposeLinearTransition(Generator->Accent2Change);
				goto FailurePoint3;
			}
		Generator->CurrentAccent4 = 0;
		Generator->Accent4ChangeCountdown = 0;
		Generator->Accent4Change = NewLinearTransition(0,0,1);
		if (Generator->Accent4Change == NIL)
			{
			 FailurePoint5:
				DisposeLinearTransition(Generator->Accent3Change);
				goto FailurePoint4;
			}

		/* build list of thingers */
		Appender = NIL;
		List = GetEffectSpecListLength(SpecList);
		for (Scan = 0; Scan < List; Scan += 1)
			{
				OneEffectRec*				Effect;

				/* allocate record */
				Effect = (OneEffectRec*)AllocPtrCanFail(sizeof(OneEffectRec),"OneEffectRec");
				if (Effect == NIL)
					{
					 FailurePoint6:
						while (Generator->List != NIL)
							{
								Effect = Generator->List;
								Generator->List = Generator->List->Next;
								switch (Effect->Type)
									{
										default:
											EXECUTE(PRERR(ForceAbort,"NewTrackEffectGenerator:  bad effect type"));
											break;
										case eDelayEffect:
											DisposeDelayLineProcessor(Effect->u.DelayEffect);
											break;
										case eNLProcEffect:
											DisposeNLProcProcessor(Effect->u.NLProcEffect);
											break;
										case eFilterEffect:
											DisposeFilterArrayProcessor(Effect->u.FilterEffect);
											break;
										case eAnalyzerEffect:
											DisposeAnalyzer(Effect->u.AnalyzerEffect);
											break;
									}
								ReleasePtr((char*)Effect);
							}
						DisposeLinearTransition(Generator->Accent4Change);
						goto FailurePoint5;
					}
				/* fill in fields */
				Effect->Type = GetEffectSpecListElementType(SpecList,Scan);
				switch (Effect->Type)
					{
						default:
							EXECUTE(PRERR(ForceAbort,"NewTrackEffectGenerator:  bad effect type"));
							break;
						case eDelayEffect:
							Effect->u.DelayEffect = NewDelayLineProcessor(
								GetDelayEffectFromEffectSpecList(SpecList,Scan),FramesPerSecond);
							if (Effect->u.DelayEffect == NIL)
								{
								 FailurePoint6b:
									ReleasePtr((char*)Effect);
									goto FailurePoint6;
								}
							break;
						case eNLProcEffect:
							Effect->u.NLProcEffect = NewNLProcProcessor(
								GetNLProcEffectFromEffectSpecList(SpecList,Scan),DoingStereo,
								InverseVolume);
							if (Effect->u.NLProcEffect == NIL)
								{
									goto FailurePoint6b;
								}
							break;
						case eFilterEffect:
							Effect->u.FilterEffect = NewFilterArrayProcessor(
								GetFilterEffectFromEffectSpecList(SpecList,Scan),FramesPerSecond,
								DoingStereo);
							if (Effect->u.FilterEffect == NIL)
								{
									goto FailurePoint6b;
								}
							break;
						case eAnalyzerEffect:
							Effect->u.AnalyzerEffect = NewAnalyzer(MainWindow,
								GetAnalyzerEffectFromEffectSpecList(SpecList,Scan),DoingStereo,
								InverseVolume);
							if (Effect->u.AnalyzerEffect == NIL)
								{
									goto FailurePoint6b;
								}
							break;
					}
				/* link */
				Effect->Next = NIL;
				if (Appender == NIL)
					{
						Generator->List = Effect;
					}
				 else
					{
						Appender->Next = Effect;
					}
				Appender = Effect;
			}
		return Generator;
	}


/* dispose of a track effect generator */
void									DisposeTrackEffectGenerator(TrackEffectGenRec* Generator)
	{
		CommandConsCell*		Scan;

		CheckPtrExistence(Generator);
		Scan = Generator->ScanningGapListHead;
		while (Scan != NIL)
			{
				CommandConsCell*		Temp;

				Temp = Scan;
				Scan = Scan->Next;
				Temp->Next = CommandConsFreeList;
				CommandConsFreeList = Temp;
			}
		DisposeLinearTransition(Generator->Accent1Change);
		DisposeLinearTransition(Generator->Accent2Change);
		DisposeLinearTransition(Generator->Accent3Change);
		DisposeLinearTransition(Generator->Accent4Change);
		while (Generator->List != NIL)
			{
				OneEffectRec*				Temp;

				Temp = Generator->List;
				Generator->List = Generator->List->Next;
				switch (Temp->Type)
					{
						default:
							EXECUTE(PRERR(ForceAbort,"DisposeTrackEffectGenerator:  bad effect type"));
							break;
						case eDelayEffect:
							DisposeDelayLineProcessor(Temp->u.DelayEffect);
							break;
						case eNLProcEffect:
							DisposeNLProcProcessor(Temp->u.NLProcEffect);
							break;
						case eFilterEffect:
							DisposeFilterArrayProcessor(Temp->u.FilterEffect);
							break;
						case eAnalyzerEffect:
							DisposeAnalyzer(Temp->u.AnalyzerEffect);
							break;
					}
				ReleasePtr((char*)Temp);
			}
		ReleasePtr((char*)Generator);
	}


/* generate effect cycle */
void									ApplyTrackEffectGenerator(TrackEffectGenRec* Generator,
												largefixedsigned* Data, long NumFrames)
	{
		OneEffectRec*				Scan;

		CheckPtrExistence(Generator);
		Scan = Generator->List;
		if ((Scan != NIL) && Generator->Enable)
			{
				float								Accent1;
				float								Accent2;
				float								Accent3;
				float								Accent4;

				Accent1 = LargeBCD2Single(Generator->CurrentAccent1);
				Accent2 = LargeBCD2Single(Generator->CurrentAccent2);
				Accent3 = LargeBCD2Single(Generator->CurrentAccent3);
				Accent4 = LargeBCD2Single(Generator->CurrentAccent4);
				while (Scan != NIL)
					{
						switch (Scan->Type)
							{
								default:
									EXECUTE(PRERR(ForceAbort,"ApplyTrackEffectGenerator:  bad effect type"));
									break;
								case eDelayEffect:
									UpdateDelayLineState(Scan->u.DelayEffect,Accent1,Accent2,Accent3,Accent4);
									if (Generator->Stereo)
										{
											ApplyDelayLineStereo(Data,NumFrames,Scan->u.DelayEffect);
										}
									 else
										{
											ApplyDelayLineMono(Data,NumFrames,Scan->u.DelayEffect);
										}
									break;
								case eNLProcEffect:
									UpdateNLProcState(Scan->u.NLProcEffect,Accent1,Accent2,Accent3,Accent4);
									ApplyNLProc(Data,NumFrames,Scan->u.NLProcEffect);
									break;
								case eFilterEffect:
									UpdateFilterArrayState(Scan->u.FilterEffect,Accent1,Accent2,Accent3,Accent4);
									ApplyFilterArray(Data,NumFrames,Scan->u.FilterEffect);
									break;
								case eAnalyzerEffect:
									ApplyAnalyzer(Data,NumFrames,Scan->u.AnalyzerEffect);
									break;
							}
						Scan = Scan->Next;
					}
			}
	}


/* hand off command to be handled by effect generator.  the command will be scheduled */
/* to occur at the time ScanningGapFrontInEnvelopeTicks (which will be */
/* ScanningGapWidthInEnvelopeTicks in the future from now). */
MyBoolean							TrackEffectHandleCommand(TrackEffectGenRec* Generator,
												struct NoteObjectRec* Command,
												long ScanningGapFrontInEnvelopeTicks)
	{
		CheckPtrExistence(Generator);
		CheckPtrExistence(Command);
		/* don't bother unless there are effects to process */
		if (Generator->List != NIL)
			{
				CommandConsCell*		Cell;

				if (CommandConsFreeList != NIL)
					{
						Cell = CommandConsFreeList;
						CommandConsFreeList = CommandConsFreeList->Next;
					}
				 else
					{
						Cell = (CommandConsCell*)AllocPtrCanFail(sizeof(CommandConsCell),
							"CommandConsCell");
						if (Cell == NIL)
							{
								return False;
							}
					}

				/* fill in data fields */
				Cell->Command = Command;
				Cell->StartTime = ScanningGapFrontInEnvelopeTicks;

				/* insert into list */
				/* there is no need to sort, since the start time of commands can't */
				/* be adjusted.  just append to the list */
				Cell->Next = NIL;
				if (Generator->ScanningGapListTail == NIL)
					{
						Generator->ScanningGapListHead = Cell;
					}
				 else
					{
						Generator->ScanningGapListTail->Next = Cell;
					}
				Generator->ScanningGapListTail = Cell;
			}

		return True;
	}


/* increment duration timer.  this is called once per envelope tick to adjust */
/* all of the parameter transition tracking devices */
void									TrackEffectIncrementDurationTimer(TrackEffectGenRec* Generator,
												long NumDurationTicks)
	{
		CheckPtrExistence(Generator);
		/* don't bother unless there are effects to process */
		if ((Generator->List != NIL) && Generator->Enable)
			{
				UpdateOne(&Generator->CurrentAccent1,Generator->Accent1Change,
					&Generator->Accent1ChangeCountdown,NumDurationTicks);
				UpdateOne(&Generator->CurrentAccent2,Generator->Accent2Change,
					&Generator->Accent2ChangeCountdown,NumDurationTicks);
				UpdateOne(&Generator->CurrentAccent3,Generator->Accent3Change,
					&Generator->Accent3ChangeCountdown,NumDurationTicks);
				UpdateOne(&Generator->CurrentAccent4,Generator->Accent4Change,
					&Generator->Accent4ChangeCountdown,NumDurationTicks);
			}
	}


/* process commands in the queue that occur now.  this should be called after */
/* queueing commands, but before incrementing the execution index, and before */
/* processing the data, so that commands are handled at the beginning of a transition. */
void									TrackEffectProcessQueuedCommands(TrackEffectGenRec* Generator)
	{
		CheckPtrExistence(Generator);
		if (Generator->List != NIL)
			{
				while ((Generator->ScanningGapListHead != NIL)
					&& (Generator->ScanningGapListHead->StartTime <= Generator->ExecutionIndex))
					{
						CommandConsCell*		Cell;

						/* since the start time of commands can't be adjusted, there should */
						/* never be a command that is strictly less than. */
						ERROR(Generator->ScanningGapListHead->StartTime < Generator->ExecutionIndex,
							PRERR(AllowResume,"TrackEffectProcessQueuedCommands:  early command"));
						/* unlink the cons cell */
						Cell = Generator->ScanningGapListHead;
						Generator->ScanningGapListHead = Generator->ScanningGapListHead->Next;
						if (Generator->ScanningGapListHead == NIL)
							{
								Generator->ScanningGapListTail = NIL;
							}
						/* see what we're supposed to do with the command */
						switch (Cell->Command->Flags & ~eCommandFlag)
							{
								default:
									EXECUTE(PRERR(ForceAbort,
										"TrackEffectProcessQueuedCommands:  bad command"));
									break;
								case eCmdSetEffectParam1: /* specify the new default effect parameter in <1l> */
								case eCmdSetScoreEffectParam1:
									Generator->CurrentAccent1 = Cell->Command->a.Command.Argument1;
									Generator->Accent1ChangeCountdown = 0;
									break;
								case eCmdIncEffectParam1: /* add <1l> to the default effect parameter */
								case eCmdIncScoreEffectParam1:
									Generator->CurrentAccent1 += Cell->Command->a.Command.Argument1;
									Generator->Accent1ChangeCountdown = 0;
									break;
								case eCmdSweepEffectParamAbs1: /* <1l> = new effect parameter, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamAbs1:
									SweepToNewValue(&(Generator->CurrentAccent1),Generator->Accent1Change,
										&(Generator->Accent1ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;
								case eCmdSweepEffectParamRel1: /* <1l> = effect parameter adjust, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamRel1:
									SweepToAdjustedValue(&(Generator->CurrentAccent1),Generator->Accent1Change,
										&(Generator->Accent1ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;

								case eCmdSetEffectParam2: /* specify the new default effect parameter in <1l> */
								case eCmdSetScoreEffectParam2:
									Generator->CurrentAccent2 = Cell->Command->a.Command.Argument1;
									Generator->Accent2ChangeCountdown = 0;
									break;
								case eCmdIncEffectParam2: /* add <1l> to the default effect parameter */
								case eCmdIncScoreEffectParam2:
									Generator->CurrentAccent2 += Cell->Command->a.Command.Argument1;
									Generator->Accent2ChangeCountdown = 0;
									break;
								case eCmdSweepEffectParamAbs2: /* <1l> = new effect parameter, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamAbs2:
									SweepToNewValue(&(Generator->CurrentAccent2),Generator->Accent2Change,
										&(Generator->Accent2ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;
								case eCmdSweepEffectParamRel2: /* <1l> = effect parameter adjust, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamRel2:
									SweepToAdjustedValue(&(Generator->CurrentAccent2),Generator->Accent2Change,
										&(Generator->Accent2ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;

								case eCmdSetEffectParam3: /* specify the new default effect parameter in <1l> */
								case eCmdSetScoreEffectParam3:
									Generator->CurrentAccent3 = Cell->Command->a.Command.Argument1;
									Generator->Accent3ChangeCountdown = 0;
									break;
								case eCmdIncEffectParam3: /* add <1l> to the default effect parameter */
								case eCmdIncScoreEffectParam3:
									Generator->CurrentAccent3 += Cell->Command->a.Command.Argument1;
									Generator->Accent3ChangeCountdown = 0;
									break;
								case eCmdSweepEffectParamAbs3: /* <1l> = new effect parameter, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamAbs3:
									SweepToNewValue(&(Generator->CurrentAccent3),Generator->Accent3Change,
										&(Generator->Accent3ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;
								case eCmdSweepEffectParamRel3: /* <1l> = effect parameter adjust, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamRel3:
									SweepToAdjustedValue(&(Generator->CurrentAccent3),Generator->Accent3Change,
										&(Generator->Accent3ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;

								case eCmdSetEffectParam4: /* specify the new default effect parameter in <1l> */
								case eCmdSetScoreEffectParam4:
									Generator->CurrentAccent4 = Cell->Command->a.Command.Argument1;
									Generator->Accent4ChangeCountdown = 0;
									break;
								case eCmdIncEffectParam4: /* add <1l> to the default effect parameter */
								case eCmdIncScoreEffectParam4:
									Generator->CurrentAccent4 += Cell->Command->a.Command.Argument1;
									Generator->Accent4ChangeCountdown = 0;
									break;
								case eCmdSweepEffectParamAbs4: /* <1l> = new effect parameter, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamAbs4:
									SweepToNewValue(&(Generator->CurrentAccent4),Generator->Accent4Change,
										&(Generator->Accent4ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;
								case eCmdSweepEffectParamRel4: /* <1l> = effect parameter adjust, <2xs> = # of beats to get there */
								case eCmdSweepScoreEffectParamRel4:
									SweepToAdjustedValue(&(Generator->CurrentAccent4),Generator->Accent4Change,
										&(Generator->Accent4ChangeCountdown),Cell->Command->a.Command.Argument1,
										Cell->Command->a.Command.Argument2);
									break;
								case eCmdTrackEffectEnable:
									Generator->Enable = (Cell->Command->a.Command.Argument1 < 0);
									break;
							}
						/* dispose */
						Cell->Next = CommandConsFreeList;
						CommandConsFreeList = Cell;
					}

				/* 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). */
				Generator->ExecutionIndex += 1;
				/* since this routine is only called when samples are being generated, */
				/* we don't have to worry about when to increment this counter */
			}
	}
