/* OscDelayLine.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 "OscDelayLine.h"
#include "Memory.h"
#include "DelayEffectSpec.h"
#include "EnvelopeState.h"
#include "LFOGenerator.h"
#include "FilterFirstOrderLowpass.h"


/* tap algorithm select */
typedef enum
	{
		eTapLeftToLeft EXECUTE(= -3241),
		eTapLeftToMono,
		eTapLeftToRight,
		eTapMonoToLeft,
		eTapMonoToMono,
		eTapMonoToRight,
		eTapRightToLeft,
		eTapRightToMono,
		eTapRightToRight,
		eTapLeftToLeftLPF,
		eTapLeftToMonoLPF,
		eTapLeftToRightLPF,
		eTapMonoToLeftLPF,
		eTapMonoToMonoLPF,
		eTapMonoToRightLPF,
		eTapRightToLeftLPF,
		eTapRightToMonoLPF,
		eTapRightToRightLPF
	} TapAlgorithm;


typedef struct OscDelayLineTapRec
	{
		/* link to next tap record */
		struct OscDelayLineTapRec*		Next;

		/* what are we tapping */
		DelayChannelType	SourceTap;
		EvalEnvelopeRec*	SourceEnvelope;
		LFOGenRec*				SourceLFO;
		DelayChannelType	TargetTap;
		EvalEnvelopeRec*	TargetEnvelope;
		LFOGenRec*				TargetLFO;
		EvalEnvelopeRec*	ScaleEnvelope;
		LFOGenRec*				ScaleLFO;
		EvalEnvelopeRec*	CutoffEnvelope;
		LFOGenRec*				CutoffLFO;

		/* control information */
		TapAlgorithm			TapProcessAlgorithm;

		/* state information */
		FirstOrderLowpassRec*	LowPassFilter;
		long							SourceOffset;
		long							TargetOffset;
		float							Scaling;
	} OscDelayLineTapRec;


struct OscDelayLineRec
	{
		/* maximum delay */
		float							MaxDelayTime;

		/* state information */
		long							DelayLineLength; /* must be power of 2 */
		long							DelayLineIndex;
		largefixedsigned*	LeftDelayLineArray; /* for mono, only this one is used */
		largefixedsigned*	RightDelayLineArray;
		long							FramesPerSecond;
		float							EnvelopeTicksPerSecond;

		/* tap list */
		OscDelayLineTapRec*	Taps;

		/* free list link */
		OscDelayLineRec*	Next;
	};


static OscDelayLineRec*			OscDelayLineFreeList = NIL;
static OscDelayLineTapRec*	OscDelayLineTapFreeList = NIL;


/* flush cached oscillator delay line records */
void							FlushCachedOscDelayLineStuff(void)
	{
		while (OscDelayLineFreeList != NIL)
			{
				OscDelayLineRec*		Temp;

				Temp = OscDelayLineFreeList;
				OscDelayLineFreeList = OscDelayLineFreeList->Next;
				ReleasePtr((char*)Temp);
			}
		while (OscDelayLineTapFreeList != NIL)
			{
				OscDelayLineTapRec*	Temp;

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


/* create a new oscillator delay line processor */
OscDelayLineRec*	NewOscDelayLineProcessor(struct DelayEffectRec* Template,
										float EnvelopeTicksPerSecond, long SamplingRate,
										struct MainWindowRec* MainWindow, AccentParam* Accents,
										float HurryUp, float InitialFrequency, float FreqForMultisampling,
										long* PreOriginTimeOut)
	{
		OscDelayLineRec*		Delay;
		long								Temp;
		long								Scan;
		long								Limit;
		OscDelayLineTapRec*	Appender;
		long								MaxPreOrigin;
		long								OnePreOrigin;

		CheckPtrExistence(Template);
		if (OscDelayLineFreeList != NIL)
			{
				Delay = OscDelayLineFreeList;
				OscDelayLineFreeList = OscDelayLineFreeList->Next;
			}
		 else
			{
				Delay = (OscDelayLineRec*)AllocPtrCanFail(sizeof(OscDelayLineRec),
					"OscDelayLineRec");
				if (Delay == NIL)
					{
					 FailurePoint1:
						return NIL;
					}
			}

		MaxPreOrigin = 0;

		Delay->MaxDelayTime = GetDelayMaxTime(Template);
		Delay->Taps = NIL;
		Delay->EnvelopeTicksPerSecond = EnvelopeTicksPerSecond;

		/* set line length */
		Temp = SamplingRate * Delay->MaxDelayTime + 1;
		Temp = Temp & 0x3fffffff;
		Delay->DelayLineLength = 1UL;
		while (Temp != 0)
			{
				Temp = Temp >> 1;
				Delay->DelayLineLength = Delay->DelayLineLength << 1;
			}
		Delay->FramesPerSecond = SamplingRate;
		Delay->DelayLineIndex = 0;

		/* build lines */
		Delay->LeftDelayLineArray = (largefixedsigned*)AllocPtrCanFail(
			sizeof(largefixedsigned) * Delay->DelayLineLength,"DelayLineArray");
		if (Delay->LeftDelayLineArray == NIL)
			{
			 FailurePoint2:
				ReleasePtr((char*)Delay);
				goto FailurePoint1;
			}
		Delay->RightDelayLineArray = (largefixedsigned*)AllocPtrCanFail(
			sizeof(largefixedsigned) * Delay->DelayLineLength,"DelayLineArray");
		if (Delay->RightDelayLineArray == NIL)
			{
			 FailurePoint3:
				ReleasePtr((char*)Delay->LeftDelayLineArray);
				goto FailurePoint2;
			}
		for (Temp = 0; Temp < Delay->DelayLineLength; Temp += 1)
			{
				Delay->LeftDelayLineArray[Temp] = 0;
				Delay->RightDelayLineArray[Temp] = 0;
			}

		/* build tap list */
		Appender = NIL;
		Limit = GetDelayEffectSpecNumTaps(Template);
		for (Scan = 0; Scan < Limit; Scan += 1)
			{
				OscDelayLineTapRec*		Tap;

				/* allocate delay line */
				if (OscDelayLineTapFreeList != NIL)
					{
						Tap = OscDelayLineTapFreeList;
						OscDelayLineTapFreeList = OscDelayLineTapFreeList->Next;
					}
				 else
					{
						Tap = (OscDelayLineTapRec*)AllocPtrCanFail(sizeof(OscDelayLineTapRec),
							"OscDelayLineTapRec");
						if (Tap == NIL)
							{
							 FailurePoint4:
								while (Delay->Taps != NIL)
									{
										Tap = Delay->Taps;
										Delay->Taps = Delay->Taps->Next;
										DisposeEnvelopeStateRecord(Tap->SourceEnvelope);
										DisposeLFOGenerator(Tap->SourceLFO);
										DisposeEnvelopeStateRecord(Tap->TargetEnvelope);
										DisposeLFOGenerator(Tap->TargetLFO);
										DisposeEnvelopeStateRecord(Tap->ScaleEnvelope);
										DisposeLFOGenerator(Tap->ScaleLFO);
										if (Tap->CutoffEnvelope != NIL)
											{
												DisposeEnvelopeStateRecord(Tap->CutoffEnvelope);
											}
										if (Tap->CutoffLFO != NIL)
											{
												DisposeLFOGenerator(Tap->CutoffLFO);
											}
										if (Tap->LowPassFilter != NIL)
											{
												DisposeFirstOrderLowpass(Tap->LowPassFilter);
											}
										ReleasePtr((char*)Tap);
									}
								ReleasePtr((char*)Delay->RightDelayLineArray);
								goto FailurePoint3;
							}
					}
				/* fill in template fields */
				Tap->SourceTap = GetDelayTapSource(Template,Scan);
				Tap->TargetTap = GetDelayTapTarget(Template,Scan);
				Tap->SourceEnvelope = NewEnvelopeStateRecord(
					GetDelayTapSourceEnvelope(Template,Scan),Accents,
					InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
				if (Tap->SourceEnvelope == NIL)
					{
					 FailurePoint4a:
						Tap->Next = OscDelayLineTapFreeList;
						OscDelayLineTapFreeList = Tap;
						goto FailurePoint4;
					}
				if (OnePreOrigin > MaxPreOrigin)
					{
						MaxPreOrigin = OnePreOrigin;
					}
				Tap->SourceLFO = NewLFOGenerator(GetDelayTapSourceLFO(Template,Scan),
					&OnePreOrigin,Accents,InitialFrequency,HurryUp,
					EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
				if (Tap->SourceLFO == NIL)
					{
					 FailurePoint4b:
						DisposeEnvelopeStateRecord(Tap->SourceEnvelope);
						goto FailurePoint4a;
					}
				if (OnePreOrigin > MaxPreOrigin)
					{
						MaxPreOrigin = OnePreOrigin;
					}
				Tap->TargetEnvelope = NewEnvelopeStateRecord(
					GetDelayTapTargetEnvelope(Template,Scan),Accents,
					InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
				if (Tap->TargetEnvelope == NIL)
					{
					 FailurePoint4c:
						DisposeLFOGenerator(Tap->SourceLFO);
						goto FailurePoint4b;
					}
				if (OnePreOrigin > MaxPreOrigin)
					{
						MaxPreOrigin = OnePreOrigin;
					}
				Tap->TargetLFO = NewLFOGenerator(GetDelayTapTargetLFO(Template,Scan),
					&OnePreOrigin,Accents,InitialFrequency,HurryUp,
					EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
				if (Tap->TargetLFO == NIL)
					{
					 FailurePoint4d:
						DisposeEnvelopeStateRecord(Tap->TargetEnvelope);
						goto FailurePoint4c;
					}
				if (OnePreOrigin > MaxPreOrigin)
					{
						MaxPreOrigin = OnePreOrigin;
					}
				Tap->ScaleEnvelope = NewEnvelopeStateRecord(
					GetDelayTapScaleEnvelope(Template,Scan),Accents,
					InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
				if (Tap->ScaleEnvelope == NIL)
					{
					 FailurePoint4e:
						DisposeLFOGenerator(Tap->TargetLFO);
						goto FailurePoint4d;
					}
				if (OnePreOrigin > MaxPreOrigin)
					{
						MaxPreOrigin = OnePreOrigin;
					}
				Tap->ScaleLFO = NewLFOGenerator(GetDelayTapScaleLFO(Template,Scan),
					&OnePreOrigin,Accents,InitialFrequency,HurryUp,
					EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
				if (Tap->ScaleLFO == NIL)
					{
					 FailurePoint4f:
						DisposeEnvelopeStateRecord(Tap->ScaleEnvelope);
						goto FailurePoint4e;
					}
				if (OnePreOrigin > MaxPreOrigin)
					{
						MaxPreOrigin = OnePreOrigin;
					}
				if (GetDelayTapFilterEnable(Template,Scan))
					{
						Tap->CutoffEnvelope = NewEnvelopeStateRecord(
							GetDelayTapCutoffEnvelope(Template,Scan),Accents,
							InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
						if (Tap->CutoffEnvelope == NIL)
							{
							 FailurePoint4g:
								DisposeLFOGenerator(Tap->ScaleLFO);
								goto FailurePoint4f;
							}
						if (OnePreOrigin > MaxPreOrigin)
							{
								MaxPreOrigin = OnePreOrigin;
							}
					}
				 else
					{
						Tap->CutoffEnvelope = NIL;
					}
				if (GetDelayTapFilterEnable(Template,Scan))
					{
						Tap->CutoffLFO = NewLFOGenerator(GetDelayTapCutoffLFO(Template,Scan),
							&OnePreOrigin,Accents,InitialFrequency,HurryUp,
							EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
						if (Tap->CutoffLFO == NIL)
							{
							 FailurePoint4h:
								if (Tap->CutoffEnvelope != NIL)
									{
										DisposeEnvelopeStateRecord(Tap->CutoffEnvelope);
									}
								goto FailurePoint4g;
							}
						if (OnePreOrigin > MaxPreOrigin)
							{
								MaxPreOrigin = OnePreOrigin;
							}
					}
				 else
					{
						Tap->CutoffLFO = NIL;
					}
				if (GetDelayTapFilterEnable(Template,Scan))
					{
						Tap->LowPassFilter = NewFirstOrderLowpass();
						if (Tap->LowPassFilter == NIL)
							{
							 FailurePoint4i:
								if (Tap->CutoffLFO != NIL)
									{
										DisposeLFOGenerator(Tap->CutoffLFO);
									}
								goto FailurePoint4h;
							}
					}
				 else
					{
						Tap->LowPassFilter = NIL;
					}
				/* determine which algorithm to use */
				switch (Tap->SourceTap)
					{
						default:
							EXECUTE(PRERR(ForceAbort,"NewOscDelayLineProcessor:  bad tap source"));
							break;
						case eTapLeftChannel:
							switch (Tap->TargetTap)
								{
									default:
										EXECUTE(PRERR(ForceAbort,"NewOscDelayLineProcessor:  bad tap target"));
										break;
									case eTapLeftChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapLeftToLeft;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapLeftToLeftLPF;
											}
										break;
									case eTapRightChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapLeftToRight;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapLeftToRightLPF;
											}
										break;
									case eTapMonoChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapLeftToMono;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapLeftToMonoLPF;
											}
										break;
								}
							break;
						case eTapRightChannel:
							switch (Tap->TargetTap)
								{
									default:
										EXECUTE(PRERR(ForceAbort,"NewOscDelayLineProcessor:  bad tap target"));
										break;
									case eTapLeftChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapRightToLeft;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapRightToLeftLPF;
											}
										break;
									case eTapRightChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapRightToRight;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapRightToRightLPF;
											}
										break;
									case eTapMonoChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapRightToMono;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapRightToMonoLPF;
											}
										break;
								}
							break;
						case eTapMonoChannel:
							switch (Tap->TargetTap)
								{
									default:
										EXECUTE(PRERR(ForceAbort,"NewOscDelayLineProcessor:  bad tap target"));
										break;
									case eTapLeftChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapMonoToLeft;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapMonoToLeftLPF;
											}
										break;
									case eTapRightChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapMonoToRight;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapMonoToRightLPF;
											}
										break;
									case eTapMonoChannel:
										if (!GetDelayTapFilterEnable(Template,Scan))
											{
												Tap->TapProcessAlgorithm = eTapMonoToMono;
											}
										 else
											{
												Tap->TapProcessAlgorithm = eTapMonoToMonoLPF;
											}
										break;
								}
							break;
					}
				/* append to list */
				Tap->Next = NIL;
				if (Appender == NIL)
					{
						Delay->Taps = Tap;
					}
				 else
					{
						Appender->Next = Tap;
					}
				Appender = Tap;
			}

		*PreOriginTimeOut = MaxPreOrigin;

		return Delay;
	}


/* fix up the origin time so that envelopes start at the proper times */
void							OscDelayLineProcessorFixEnvelopeOrigins(OscDelayLineRec* Delay,
										long ActualPreOriginTime)
	{
		OscDelayLineTapRec*		Scan;

		CheckPtrExistence(Delay);
		Scan = Delay->Taps;
		while (Scan != NIL)
			{
				CheckPtrExistence(Scan);
				EnvelopeStateFixUpInitialDelay(Scan->SourceEnvelope,ActualPreOriginTime);
				EnvelopeStateFixUpInitialDelay(Scan->TargetEnvelope,ActualPreOriginTime);
				EnvelopeStateFixUpInitialDelay(Scan->ScaleEnvelope,ActualPreOriginTime);
				if (Scan->CutoffEnvelope != NIL)
					{
						EnvelopeStateFixUpInitialDelay(Scan->CutoffEnvelope,ActualPreOriginTime);
					}
				LFOGeneratorFixEnvelopeOrigins(Scan->SourceLFO,ActualPreOriginTime);
				LFOGeneratorFixEnvelopeOrigins(Scan->TargetLFO,ActualPreOriginTime);
				LFOGeneratorFixEnvelopeOrigins(Scan->ScaleLFO,ActualPreOriginTime);
				if (Scan->CutoffLFO != NIL)
					{
						LFOGeneratorFixEnvelopeOrigins(Scan->CutoffLFO,ActualPreOriginTime);
					}
				Scan = Scan->Next;
			}
	}


/* dispose of the oscillator delay line processor */
void							DisposeOscDelayLineProcessor(OscDelayLineRec* Delay)
	{
		CheckPtrExistence(Delay);
		ReleasePtr((char*)Delay->LeftDelayLineArray);
		ReleasePtr((char*)Delay->RightDelayLineArray);
		while (Delay->Taps != NIL)
			{
				OscDelayLineTapRec*		Temp;

				Temp = Delay->Taps;
				DisposeEnvelopeStateRecord(Temp->SourceEnvelope);
				DisposeEnvelopeStateRecord(Temp->TargetEnvelope);
				DisposeEnvelopeStateRecord(Temp->ScaleEnvelope);
				if (Temp->CutoffEnvelope != NIL)
					{
						DisposeEnvelopeStateRecord(Temp->CutoffEnvelope);
					}
				DisposeLFOGenerator(Temp->SourceLFO);
				DisposeLFOGenerator(Temp->TargetLFO);
				DisposeLFOGenerator(Temp->ScaleLFO);
				if (Temp->CutoffLFO != NIL)
					{
						DisposeLFOGenerator(Temp->CutoffLFO);
					}
				if (Temp->LowPassFilter != NIL)
					{
						DisposeFirstOrderLowpass(Temp->LowPassFilter);
					}
				Delay->Taps = Delay->Taps->Next;
				Temp->Next = OscDelayLineTapFreeList;
				OscDelayLineTapFreeList = Temp;
			}
		Delay->Next = OscDelayLineFreeList;
		OscDelayLineFreeList = Delay;
	}


/* generate delays from envelopes */
void							OscDelayLineProcessorUpdateEnvelopes(OscDelayLineRec* Delay,
										float OscillatorFrequency)
	{
		OscDelayLineTapRec*	Scan;

		CheckPtrExistence(Delay);
		/* update tap states */
		Scan = Delay->Taps;
		while (Scan != NIL)
			{
				float							Time;

				CheckPtrExistence(Scan);
				/* determine source offset */
				Time = FastFixed2Float(LFOGenUpdateCycle(Scan->SourceLFO,
					EnvelopeUpdate(Scan->SourceEnvelope,OscillatorFrequency),OscillatorFrequency));
				if (Time < 0)
					{
						Time = 0;
					}
				if (Time > Delay->MaxDelayTime)
					{
						Time = Delay->MaxDelayTime;
					}
				Scan->SourceOffset = Time * Delay->FramesPerSecond;
				/* determine target offset */
				Time = FastFixed2Float(LFOGenUpdateCycle(Scan->TargetLFO,
					EnvelopeUpdate(Scan->TargetEnvelope,OscillatorFrequency),OscillatorFrequency));
				if (Time < 0)
					{
						Time = 0;
					}
				if (Time > Delay->MaxDelayTime)
					{
						Time = Delay->MaxDelayTime;
					}
				Scan->TargetOffset = Time * Delay->FramesPerSecond;
				/* update lowpass filter */
				if (Scan->LowPassFilter != NIL)
					{
						SetFirstOrderLowpassCoefficients(Scan->LowPassFilter,FastFixed2Float(
							LFOGenUpdateCycle(Scan->CutoffLFO,EnvelopeUpdate(Scan->CutoffEnvelope,
							OscillatorFrequency),OscillatorFrequency)),Delay->FramesPerSecond);
					}
				/* determine scale factor */
				Scan->Scaling = FastFixed2Float(LFOGenUpdateCycle(Scan->ScaleLFO,
					EnvelopeUpdate(Scan->ScaleEnvelope,OscillatorFrequency),OscillatorFrequency));
				/* do the next one */
				Scan = Scan->Next;
			}
	}


/* apply oscillator delay processing to some stuff to stereo data */
void							ApplyOscDelayLineStereo(largefixedsigned* Data, long NumFrames,
										OscDelayLineRec* Delay)
	{
		long						Scan;
		long						VectorMask;
		long						VectorIndex;
		largefixedsigned*	LeftDelayLineArray;
		largefixedsigned*	RightDelayLineArray;

		CheckPtrExistence(Data);
		CheckPtrExistence(Delay);
		VectorMask = Delay->DelayLineLength - 1;
		VectorIndex = Delay->DelayLineIndex;
		LeftDelayLineArray = Delay->LeftDelayLineArray;
		RightDelayLineArray = Delay->RightDelayLineArray;
		for (Scan = 0; Scan < NumFrames; Scan += 1)
			{
				OscDelayLineTapRec*		Tap;

				/* initialize current point with dry data */
				LeftDelayLineArray[VectorIndex] = Data[Scan * 2 + 0];
				RightDelayLineArray[VectorIndex] = Data[Scan * 2 + 1];
				/* iterate over taps */
				Tap = Delay->Taps;
				while (Tap != NIL)
					{
						long							SourceIndex;
						long							TargetIndex;
						largefixedsigned	Temp;

						/* precompute some stuff */
						SourceIndex = VectorMask & (VectorIndex +  Tap->SourceOffset);
						TargetIndex = VectorMask & (VectorIndex +  Tap->TargetOffset);
						PRNGCHK(LeftDelayLineArray,&(LeftDelayLineArray[SourceIndex]),
							sizeof(LeftDelayLineArray[SourceIndex]));
						PRNGCHK(LeftDelayLineArray,&(LeftDelayLineArray[TargetIndex]),
							sizeof(LeftDelayLineArray[TargetIndex]));
						PRNGCHK(RightDelayLineArray,&(RightDelayLineArray[SourceIndex]),
							sizeof(RightDelayLineArray[SourceIndex]));
						PRNGCHK(RightDelayLineArray,&(RightDelayLineArray[TargetIndex]),
							sizeof(RightDelayLineArray[TargetIndex]));
						/* apply transformation */
						switch (Tap->TapProcessAlgorithm)
							{
								default:
									EXECUTE(PRERR(ForceAbort,
										"ApplyOscDelayLineStereo:  unknown tap processing algorithm"));
									break;

								case eTapLeftToLeft:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)(
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
								case eTapLeftToMono:
									Temp = LeftDelayLineArray[SourceIndex] * Tap->Scaling;
									LeftDelayLineArray[TargetIndex] += Temp;
									RightDelayLineArray[TargetIndex] += Temp;
									break;
								case eTapLeftToRight:
									RightDelayLineArray[TargetIndex] += (largefixedsigned)(
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
								case eTapMonoToLeft:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)(
										((LeftDelayLineArray[SourceIndex]
										+ RightDelayLineArray[SourceIndex]) >> 1) * Tap->Scaling);
									break;
								case eTapMonoToMono:
									Temp = ((LeftDelayLineArray[SourceIndex]
										+ RightDelayLineArray[SourceIndex]) >> 1) * Tap->Scaling;
									LeftDelayLineArray[TargetIndex] += Temp;
									RightDelayLineArray[TargetIndex] += Temp;
									break;
								case eTapMonoToRight:
									RightDelayLineArray[TargetIndex] += (largefixedsigned)(
										((LeftDelayLineArray[SourceIndex]
										+ RightDelayLineArray[SourceIndex]) >> 1) * Tap->Scaling);
									break;
								case eTapRightToLeft:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)(
										RightDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
								case eTapRightToMono:
									Temp = RightDelayLineArray[SourceIndex] * Tap->Scaling;
									LeftDelayLineArray[TargetIndex] += Temp;
									RightDelayLineArray[TargetIndex] += Temp;
									break;
								case eTapRightToRight:
									RightDelayLineArray[TargetIndex] += (largefixedsigned)(
										RightDelayLineArray[SourceIndex] * Tap->Scaling);
									break;

								case eTapLeftToLeftLPF:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
								case eTapLeftToMonoLPF:
									Temp = ApplyFirstOrderLowpass(Tap->LowPassFilter,
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									LeftDelayLineArray[TargetIndex] += Temp;
									RightDelayLineArray[TargetIndex] += Temp;
									break;
								case eTapLeftToRightLPF:
									RightDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
								case eTapMonoToLeftLPF:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										((LeftDelayLineArray[SourceIndex]
										+ RightDelayLineArray[SourceIndex]) >> 1) * Tap->Scaling);
									break;
								case eTapMonoToMonoLPF:
									Temp = ApplyFirstOrderLowpass(Tap->LowPassFilter,
										((LeftDelayLineArray[SourceIndex]
										+ RightDelayLineArray[SourceIndex]) >> 1) * Tap->Scaling);
									LeftDelayLineArray[TargetIndex] += Temp;
									RightDelayLineArray[TargetIndex] += Temp;
									break;
								case eTapMonoToRightLPF:
									RightDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										((LeftDelayLineArray[SourceIndex]
										+ RightDelayLineArray[SourceIndex]) >> 1) * Tap->Scaling);
									break;
								case eTapRightToLeftLPF:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										RightDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
								case eTapRightToMonoLPF:
									Temp = ApplyFirstOrderLowpass(Tap->LowPassFilter,
										RightDelayLineArray[SourceIndex] * Tap->Scaling);
									LeftDelayLineArray[TargetIndex] += Temp;
									RightDelayLineArray[TargetIndex] += Temp;
									break;
								case eTapRightToRightLPF:
									RightDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										RightDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
							}
						/* do the next one */
						Tap = Tap->Next;
					}
				/* perform data update */
				PRNGCHK(Data,&(Data[Scan * 2 + 0]),sizeof(Data[Scan * 2 + 0]));
				PRNGCHK(Data,&(Data[Scan * 2 + 1]),sizeof(Data[Scan * 2 + 1]));
				PRNGCHK(LeftDelayLineArray,&(LeftDelayLineArray[VectorIndex]),
					sizeof(LeftDelayLineArray[VectorIndex]));
				PRNGCHK(RightDelayLineArray,&(RightDelayLineArray[VectorIndex]),
					sizeof(RightDelayLineArray[VectorIndex]));
				Data[Scan * 2 + 0] = LeftDelayLineArray[VectorIndex];
				Data[Scan * 2 + 1] = RightDelayLineArray[VectorIndex];
				/* increment index */
				VectorIndex = (VectorIndex - 1) & VectorMask;
			}
		Delay->DelayLineIndex = VectorIndex;
	}


/* apply oscillator delay processing to some stuff to mono data */
void							ApplyOscDelayLineMono(largefixedsigned* Data, long NumFrames,
										OscDelayLineRec* Delay)
	{
		long						Scan;
		long						VectorMask;
		long						VectorIndex;
		largefixedsigned*	LeftDelayLineArray;

		CheckPtrExistence(Data);
		CheckPtrExistence(Delay);
		VectorMask = Delay->DelayLineLength - 1;
		VectorIndex = Delay->DelayLineIndex;
		LeftDelayLineArray = Delay->LeftDelayLineArray;
		for (Scan = 0; Scan < NumFrames; Scan += 1)
			{
				OscDelayLineTapRec*		Tap;

				/* clear current output point */
				LeftDelayLineArray[VectorIndex] = Data[Scan];
				/* iterate over taps */
				Tap = Delay->Taps;
				while (Tap != NIL)
					{
						long							SourceIndex;
						long							TargetIndex;
						largefixedsigned	Temp;

						/* precompute some stuff */
						SourceIndex = VectorMask & (VectorIndex +  Tap->SourceOffset);
						TargetIndex = VectorMask & (VectorIndex +  Tap->TargetOffset);
						PRNGCHK(LeftDelayLineArray,&(LeftDelayLineArray[SourceIndex]),
							sizeof(LeftDelayLineArray[SourceIndex]));
						PRNGCHK(LeftDelayLineArray,&(LeftDelayLineArray[TargetIndex]),
							sizeof(LeftDelayLineArray[TargetIndex]));
						/* apply transformation */
						switch (Tap->TapProcessAlgorithm)
							{
								default:
									EXECUTE(PRERR(ForceAbort,
										"ApplyOscDelayLineMono:  unknown tap processing algorithm"));
									break;

								case eTapLeftToLeft:
								case eTapLeftToMono:
								case eTapLeftToRight:
								case eTapMonoToLeft:
								case eTapMonoToMono:
								case eTapMonoToRight:
								case eTapRightToLeft:
								case eTapRightToMono:
								case eTapRightToRight:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)(
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									break;

								case eTapLeftToLeftLPF:
								case eTapLeftToMonoLPF:
								case eTapLeftToRightLPF:
								case eTapMonoToLeftLPF:
								case eTapMonoToMonoLPF:
								case eTapMonoToRightLPF:
								case eTapRightToLeftLPF:
								case eTapRightToMonoLPF:
								case eTapRightToRightLPF:
									LeftDelayLineArray[TargetIndex] += (largefixedsigned)
										ApplyFirstOrderLowpass(Tap->LowPassFilter,
										LeftDelayLineArray[SourceIndex] * Tap->Scaling);
									break;
							}
						/* do the next one */
						Tap = Tap->Next;
					}
				/* perform data update */
				PRNGCHK(Data,&(Data[Scan]),sizeof(Data[Scan]));
				PRNGCHK(LeftDelayLineArray,&(LeftDelayLineArray[VectorIndex]),
					sizeof(LeftDelayLineArray[VectorIndex]));
				Data[Scan] = LeftDelayLineArray[VectorIndex];
				/* increment index */
				VectorIndex = (VectorIndex - 1) & VectorMask;
			}
		Delay->DelayLineIndex = VectorIndex;
	}


void							OscDelayKeyUpSustain1(OscDelayLineRec* Delay)
	{
		OscDelayLineTapRec*		Scan;

		CheckPtrExistence(Delay);
		Scan = Delay->Taps;
		while (Scan != NIL)
			{
				CheckPtrExistence(Scan);
				EnvelopeKeyUpSustain1(Scan->SourceEnvelope);
				EnvelopeKeyUpSustain1(Scan->TargetEnvelope);
				EnvelopeKeyUpSustain1(Scan->ScaleEnvelope);
				if (Scan->CutoffEnvelope != NIL)
					{
						EnvelopeKeyUpSustain1(Scan->CutoffEnvelope);
					}
				LFOGeneratorKeyUpSustain1(Scan->SourceLFO);
				LFOGeneratorKeyUpSustain1(Scan->TargetLFO);
				LFOGeneratorKeyUpSustain1(Scan->ScaleLFO);
				if (Scan->CutoffLFO != NIL)
					{
						LFOGeneratorKeyUpSustain1(Scan->CutoffLFO);
					}
				Scan = Scan->Next;
			}
	}


void							OscDelayKeyUpSustain2(OscDelayLineRec* Delay)
	{
		OscDelayLineTapRec*		Scan;

		CheckPtrExistence(Delay);
		Scan = Delay->Taps;
		while (Scan != NIL)
			{
				CheckPtrExistence(Scan);
				EnvelopeKeyUpSustain2(Scan->SourceEnvelope);
				EnvelopeKeyUpSustain2(Scan->TargetEnvelope);
				EnvelopeKeyUpSustain2(Scan->ScaleEnvelope);
				if (Scan->CutoffEnvelope != NIL)
					{
						EnvelopeKeyUpSustain2(Scan->CutoffEnvelope);
					}
				LFOGeneratorKeyUpSustain2(Scan->SourceLFO);
				LFOGeneratorKeyUpSustain2(Scan->TargetLFO);
				LFOGeneratorKeyUpSustain2(Scan->ScaleLFO);
				if (Scan->CutoffLFO != NIL)
					{
						LFOGeneratorKeyUpSustain2(Scan->CutoffLFO);
					}
				Scan = Scan->Next;
			}
	}


void							OscDelayKeyUpSustain3(OscDelayLineRec* Delay)
	{
		OscDelayLineTapRec*		Scan;

		CheckPtrExistence(Delay);
		Scan = Delay->Taps;
		while (Scan != NIL)
			{
				CheckPtrExistence(Scan);
				EnvelopeKeyUpSustain3(Scan->SourceEnvelope);
				EnvelopeKeyUpSustain3(Scan->TargetEnvelope);
				EnvelopeKeyUpSustain3(Scan->ScaleEnvelope);
				if (Scan->CutoffEnvelope != NIL)
					{
						EnvelopeKeyUpSustain3(Scan->CutoffEnvelope);
					}
				LFOGeneratorKeyUpSustain3(Scan->SourceLFO);
				LFOGeneratorKeyUpSustain3(Scan->TargetLFO);
				LFOGeneratorKeyUpSustain3(Scan->ScaleLFO);
				if (Scan->CutoffLFO != NIL)
					{
						LFOGeneratorKeyUpSustain3(Scan->CutoffLFO);
					}
				Scan = Scan->Next;
			}
	}


/* retrigger effect envelopes from the origin point */
void							OscDelayRetriggerEnvelopes(OscDelayLineRec* Delay,
										AccentParam* NewAccents, float NewHurryUp,
										float NewInitialFrequency, MyBoolean ActuallyRetrigger)
	{
		OscDelayLineTapRec*		Scan;

		CheckPtrExistence(Delay);
		Scan = Delay->Taps;
		while (Scan != NIL)
			{
				CheckPtrExistence(Scan);
				EnvelopeRetriggerFromOrigin(Scan->SourceEnvelope,NewAccents,
					NewInitialFrequency,1,NewHurryUp,
					Delay->EnvelopeTicksPerSecond,ActuallyRetrigger);
				EnvelopeRetriggerFromOrigin(Scan->TargetEnvelope,NewAccents,
					NewInitialFrequency,1,NewHurryUp,
					Delay->EnvelopeTicksPerSecond,ActuallyRetrigger);
				EnvelopeRetriggerFromOrigin(Scan->ScaleEnvelope,NewAccents,
					NewInitialFrequency,1,NewHurryUp,
					Delay->EnvelopeTicksPerSecond,ActuallyRetrigger);
				if (Scan->CutoffEnvelope != NIL)
					{
						EnvelopeRetriggerFromOrigin(Scan->CutoffEnvelope,NewAccents,
							NewInitialFrequency,1,NewHurryUp,
							Delay->EnvelopeTicksPerSecond,ActuallyRetrigger);
					}
				LFOGeneratorRetriggerFromOrigin(Scan->SourceLFO,NewAccents,
					NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
				LFOGeneratorRetriggerFromOrigin(Scan->TargetLFO,NewAccents,
					NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
				LFOGeneratorRetriggerFromOrigin(Scan->ScaleLFO,NewAccents,
					NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
				if (Scan->CutoffLFO != NIL)
					{
						LFOGeneratorRetriggerFromOrigin(Scan->CutoffLFO,NewAccents,
							NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
					}
				Scan = Scan->Next;
			}
	}
