/* OscNLProc.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 "OscNLProc.h"
#include "FastFixedPoint.h"
#include "SampleConsts.h"
#include "NonlinearProcSpec.h"
#include "Memory.h"
#include "WaveTableObject.h"
#include "AlgoWaveTableObject.h"
#include "FloatingPoint.h"
#include "WaveIndexUtility.h"
#include "EnvelopeState.h"
#include "LFOGenerator.h"


struct OscNLProcRec
	{
		/* number of frames per table */
		long										FramesPerTable;
		/* number of tables */
		long										NumberOfTables;
		/* raw wave table data array */
		void**									WaveTableMatrix;
		/* function for indexing the wave table */
		signed long							(*WaveIndexer)(float Phase, FastFixedType TableIndex,
															long NumTables, long Frames, void** Matrix);
		/* stereo flag */
		MyBoolean								StereoFlag;
		/* number of bits */
		NumBitsType							NumBits;
		/* reciprocal of volume scaling factor */
		float										InverseVolume;
		/* number of envelope ticks per second */
		float										EnvelopeTicksPerSecond;

		/* state parameters */
		float										CurrentInputScaling;
		float										CurrentOutputScaling;
		FastFixedType						CurrentWaveTableIndex;

		/* control parameters */
		EvalEnvelopeRec*				InputScalingEnvelope;
		LFOGenRec*							InputScalingLFO;
		EvalEnvelopeRec*				OutputScalingEnvelope;
		LFOGenRec*							OutputScalingLFO;
		EvalEnvelopeRec*				WaveTableIndexEnvelope;
		LFOGenRec*							WaveTableIndexLFO;

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


static OscNLProcRec*			OscNLProcFreeList = NIL;


/* flush free list elements */
void							FlushCachedOscNLProcStuff(void)
	{
		while (OscNLProcFreeList != NIL)
			{
				OscNLProcRec*			Temp;

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


/* create a new nonlinear processor */
OscNLProcRec*			NewOscNLProcProcessor(struct NonlinProcSpecRec* Template,
										float EnvelopeTicksPerSecond, long SamplingRate,
										struct MainWindowRec* MainWindow, AccentParam* Accents, float HurryUp,
										float InitialFrequency, float FreqForMultisampling,
										long* PreOriginTimeOut, MyBoolean StereoFlag, float InverseVolume)
	{
		OscNLProcRec*		NLProc;
		long						MaxPreOrigin;
		long						OnePreOrigin;

		CheckPtrExistence(Template);
		if (OscNLProcFreeList != NIL)
			{
				NLProc = OscNLProcFreeList;
				OscNLProcFreeList = OscNLProcFreeList->Next;
			}
		 else
			{
				NLProc = (OscNLProcRec*)AllocPtrCanFail(sizeof(OscNLProcRec),"OscNLProcRec");
				if (NLProc == NIL)
					{
					 FailurePoint1:
						return NIL;
					}
			}

		MaxPreOrigin = 0;
		NLProc->InverseVolume = InverseVolume;
		NLProc->StereoFlag = StereoFlag;
		NLProc->EnvelopeTicksPerSecond = EnvelopeTicksPerSecond;

		switch (GetNLProcSpecWaveType(Template))
			{
				default:
					EXECUTE(PRERR(ForceAbort,"NewOscNLProcProcessor:  invalid nlproc wavetable type"));
					break;
				case eNLDataWaveTable:
					{
						WaveTableObjectRec*				DataWaveTable;
						long											Index;

						DataWaveTable = GetNLProcSpecDataWaveTable(Template);
						CheckPtrExistence(DataWaveTable);
						NLProc->NumberOfTables = WaveTableObjectGetNumTables(DataWaveTable);
						NLProc->FramesPerTable = WaveTableObjectEntriesPerTable(DataWaveTable);
						NLProc->NumBits = WaveTableObjectGetNumBits(DataWaveTable);
						NLProc->WaveTableMatrix = (void**)AllocPtrCanFail(sizeof(void*)
							* NLProc->NumberOfTables,"Wave table vector");
						if (NLProc->WaveTableMatrix == NIL)
							{
							 FailurePoint2:
								ReleasePtr((char*)NLProc);
								goto FailurePoint1;
							}
						for (Index = 0; Index < NLProc->NumberOfTables; Index += 1)
							{
								PRNGCHK(NLProc->WaveTableMatrix,&(((void**)(NLProc->WaveTableMatrix))[
									Index]),sizeof(((void**)(NLProc->WaveTableMatrix))[Index]));
								((void**)(NLProc->WaveTableMatrix))[Index]
									= WaveTableObjectGetRawSlice(DataWaveTable,Index);
							}
					}
					break;
				case eNLAlgoWaveTable:
					{
						AlgoWaveTableObjectRec*		AlgoWaveTable;
						long											Index;

						AlgoWaveTable = GetNLProcSpecAlgoWaveTable(Template);
						CheckPtrExistence(AlgoWaveTable);
						NLProc->NumberOfTables = AlgoWaveTableObjectGetNumTables(AlgoWaveTable);
						NLProc->FramesPerTable = AlgoWaveTableObjectGetNumFrames(AlgoWaveTable);
						NLProc->NumBits = AlgoWaveTableObjectGetNumBits(AlgoWaveTable);
						NLProc->WaveTableMatrix = (void**)AllocPtrCanFail(sizeof(void*)
							* NLProc->NumberOfTables,"Wave table vector");
						if (NLProc->WaveTableMatrix == NIL)
							{
							 FailurePoint2a:
								goto FailurePoint2;
							}
						for (Index = 0; Index < NLProc->NumberOfTables; Index += 1)
							{
								PRNGCHK(NLProc->WaveTableMatrix,&(((void**)(NLProc->WaveTableMatrix))[
									Index]),sizeof(((void**)(NLProc->WaveTableMatrix))[Index]));
								((void**)(NLProc->WaveTableMatrix))[Index]
									= AlgoWaveTableObjectGetRawSlice(AlgoWaveTable,Index);
							}
					}
					break;
			}

		switch (NLProc->NumBits)
			{
				default:
					EXECUTE(PRERR(ForceAbort,"NewOscNLProcProcessor:  bad num bits"));
					break;
				case eSample8bit:
					NLProc->WaveIndexer = (signed long (*)(float,
						FastFixedType,long,long,void**))&WaveTable8Bit;
					break;
				case eSample16bit:
					NLProc->WaveIndexer = (signed long (*)(float,
						FastFixedType,long,long,void**))&WaveTable16Bit;
					break;
			}

		NLProc->InputScalingEnvelope = NewEnvelopeStateRecord(
			GetNLProcInputEnvelope(Template),Accents,
			InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
		if (NLProc->InputScalingEnvelope == NIL)
			{
			 FailurePoint3:
				ReleasePtr((char*)NLProc->WaveTableMatrix);
				goto FailurePoint2;
			}
		if (OnePreOrigin > MaxPreOrigin)
			{
				MaxPreOrigin = OnePreOrigin;
			}
		NLProc->InputScalingLFO = NewLFOGenerator(GetNLProcInputLFO(Template),
			&OnePreOrigin,Accents,InitialFrequency,HurryUp,
			EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
		if (NLProc->InputScalingLFO == NIL)
			{
			 FailurePoint4:
				DisposeEnvelopeStateRecord(NLProc->InputScalingEnvelope);
				goto FailurePoint3;
			}
		if (OnePreOrigin > MaxPreOrigin)
			{
				MaxPreOrigin = OnePreOrigin;
			}
		NLProc->OutputScalingEnvelope = NewEnvelopeStateRecord(
			GetNLProcOutputEnvelope(Template),Accents,
			InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
		if (NLProc->OutputScalingEnvelope == NIL)
			{
			 FailurePoint5:
				DisposeLFOGenerator(NLProc->InputScalingLFO);
				goto FailurePoint4;
			}
		if (OnePreOrigin > MaxPreOrigin)
			{
				MaxPreOrigin = OnePreOrigin;
			}
		NLProc->OutputScalingLFO = NewLFOGenerator(GetNLProcOutputLFO(Template),
			&OnePreOrigin,Accents,InitialFrequency,HurryUp,
			EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
		if (NLProc->OutputScalingLFO == NIL)
			{
			 FailurePoint6:
				DisposeEnvelopeStateRecord(NLProc->OutputScalingEnvelope);
				goto FailurePoint5;
			}
		if (OnePreOrigin > MaxPreOrigin)
			{
				MaxPreOrigin = OnePreOrigin;
			}
		NLProc->WaveTableIndexEnvelope = NewEnvelopeStateRecord(
			GetNLProcIndexEnvelope(Template),Accents,
			InitialFrequency,1,HurryUp,EnvelopeTicksPerSecond,&OnePreOrigin);
		if (NLProc->WaveTableIndexEnvelope == NIL)
			{
			 FailurePoint7:
				DisposeLFOGenerator(NLProc->OutputScalingLFO);
				goto FailurePoint6;
			}
		if (OnePreOrigin > MaxPreOrigin)
			{
				MaxPreOrigin = OnePreOrigin;
			}
		NLProc->WaveTableIndexLFO = NewLFOGenerator(GetNLProcIndexLFO(Template),
			&OnePreOrigin,Accents,InitialFrequency,HurryUp,
			EnvelopeTicksPerSecond,1,1,FreqForMultisampling);
		if (NLProc->WaveTableIndexLFO == NIL)
			{
			 FailurePoint8:
				DisposeEnvelopeStateRecord(NLProc->WaveTableIndexEnvelope);
				goto FailurePoint7;
			}
		if (OnePreOrigin > MaxPreOrigin)
			{
				MaxPreOrigin = OnePreOrigin;
			}

		*PreOriginTimeOut = MaxPreOrigin;

		return NLProc;
	}


/* fix up the origin time so that envelopes start at the proper times */
void							OscOscNLProcProcessorFixEnvelopeOrigins(OscNLProcRec* NLProc,
										long ActualPreOriginTime)
	{
		CheckPtrExistence(NLProc);
		EnvelopeStateFixUpInitialDelay(NLProc->InputScalingEnvelope,ActualPreOriginTime);
		EnvelopeStateFixUpInitialDelay(NLProc->OutputScalingEnvelope,ActualPreOriginTime);
		EnvelopeStateFixUpInitialDelay(NLProc->WaveTableIndexEnvelope,ActualPreOriginTime);
		LFOGeneratorFixEnvelopeOrigins(NLProc->InputScalingLFO,ActualPreOriginTime);
		LFOGeneratorFixEnvelopeOrigins(NLProc->OutputScalingLFO,ActualPreOriginTime);
		LFOGeneratorFixEnvelopeOrigins(NLProc->WaveTableIndexLFO,ActualPreOriginTime);
	}


/* dispose of the nonlinear processor */
void							DisposeOscNLProcProcessor(OscNLProcRec* NLProc)
	{
		CheckPtrExistence(NLProc);
		DisposeEnvelopeStateRecord(NLProc->InputScalingEnvelope);
		DisposeEnvelopeStateRecord(NLProc->OutputScalingEnvelope);
		DisposeEnvelopeStateRecord(NLProc->WaveTableIndexEnvelope);
		DisposeLFOGenerator(NLProc->InputScalingLFO);
		DisposeLFOGenerator(NLProc->OutputScalingLFO);
		DisposeLFOGenerator(NLProc->WaveTableIndexLFO);
		ReleasePtr((char*)NLProc->WaveTableMatrix);
		NLProc->Next = OscNLProcFreeList;
		OscNLProcFreeList = NLProc;
	}


/* update nonlinear state with accent information */
void							OscNLProcUpdateEnvelopes(OscNLProcRec* NLProc,
										float OscillatorFrequency)
	{
		CheckPtrExistence(NLProc);
		/* multiply by NLProc->InverseVolume to undo the division everyone else does */
		NLProc->CurrentInputScaling = FastFixed2Float(LFOGenUpdateCycle(
			NLProc->InputScalingLFO,EnvelopeUpdate(NLProc->InputScalingEnvelope,
			OscillatorFrequency),OscillatorFrequency)) * NLProc->InverseVolume;
		/* divide by NLProc->InverseVolume to restore the correct volume, and by */
		/* MAX16BIT to correct for value returned from wave index routine */
		NLProc->CurrentOutputScaling = FastFixed2Float(LFOGenUpdateCycle(
			NLProc->OutputScalingLFO,EnvelopeUpdate(NLProc->OutputScalingEnvelope,
			OscillatorFrequency),OscillatorFrequency)) / (NLProc->InverseVolume * MAX16BIT);
		NLProc->CurrentWaveTableIndex = LFOGenUpdateCycle(NLProc->WaveTableIndexLFO,
			EnvelopeUpdate(NLProc->WaveTableIndexEnvelope,OscillatorFrequency),
			OscillatorFrequency);
	}


/* apply nonlinear processing to some stuff */
void							ApplyOscNLProc(largefixedsigned* Data, long NumFrames,
										OscNLProcRec* NLProc)
	{
		long						Scan;

		CheckPtrExistence(Data);
		CheckPtrExistence(NLProc);
		if (NLProc->StereoFlag)
			{
				NumFrames *= 2;
			}
		for (Scan = 0; Scan < NumFrames; Scan += 1)
			{
				float						Temp;

				PRNGCHK(Data,&(Data[Scan]),sizeof(Data[Scan]));
				Temp = (*NLProc->WaveIndexer)((1 + largefixed2single(Data[Scan])
					* NLProc->CurrentInputScaling) / 2 * (NLProc->FramesPerTable - 1),
					NLProc->CurrentWaveTableIndex,NLProc->NumberOfTables,
					NLProc->FramesPerTable,NLProc->WaveTableMatrix)
					* NLProc->CurrentOutputScaling;
				Data[Scan] = double2largefixed(Temp);
			}
	}


void							OscNLProcKeyUpSustain1(OscNLProcRec* NLProc)
	{
		CheckPtrExistence(NLProc);
		EnvelopeKeyUpSustain1(NLProc->InputScalingEnvelope);
		EnvelopeKeyUpSustain1(NLProc->OutputScalingEnvelope);
		EnvelopeKeyUpSustain1(NLProc->WaveTableIndexEnvelope);
		LFOGeneratorKeyUpSustain1(NLProc->InputScalingLFO);
		LFOGeneratorKeyUpSustain1(NLProc->OutputScalingLFO);
		LFOGeneratorKeyUpSustain1(NLProc->WaveTableIndexLFO);
	}


void							OscNLProcKeyUpSustain2(OscNLProcRec* NLProc)
	{
		CheckPtrExistence(NLProc);
		EnvelopeKeyUpSustain2(NLProc->InputScalingEnvelope);
		EnvelopeKeyUpSustain2(NLProc->OutputScalingEnvelope);
		EnvelopeKeyUpSustain2(NLProc->WaveTableIndexEnvelope);
		LFOGeneratorKeyUpSustain2(NLProc->InputScalingLFO);
		LFOGeneratorKeyUpSustain2(NLProc->OutputScalingLFO);
		LFOGeneratorKeyUpSustain2(NLProc->WaveTableIndexLFO);
	}


void							OscNLProcKeyUpSustain3(OscNLProcRec* NLProc)
	{
		CheckPtrExistence(NLProc);
		EnvelopeKeyUpSustain3(NLProc->InputScalingEnvelope);
		EnvelopeKeyUpSustain3(NLProc->OutputScalingEnvelope);
		EnvelopeKeyUpSustain3(NLProc->WaveTableIndexEnvelope);
		LFOGeneratorKeyUpSustain3(NLProc->InputScalingLFO);
		LFOGeneratorKeyUpSustain3(NLProc->OutputScalingLFO);
		LFOGeneratorKeyUpSustain3(NLProc->WaveTableIndexLFO);
	}


/* retrigger effect envelopes from the origin point */
void							OscNLProcRetriggerEnvelopes(OscNLProcRec* NLProc,
										AccentParam* NewAccents, float NewHurryUp,
										float NewInitialFrequency, MyBoolean ActuallyRetrigger)
	{
		CheckPtrExistence(NLProc);
		EnvelopeRetriggerFromOrigin(NLProc->InputScalingEnvelope,NewAccents,
			NewInitialFrequency,1,NewHurryUp,
			NLProc->EnvelopeTicksPerSecond,ActuallyRetrigger);
		EnvelopeRetriggerFromOrigin(NLProc->OutputScalingEnvelope,NewAccents,
			NewInitialFrequency,1,NewHurryUp,
			NLProc->EnvelopeTicksPerSecond,ActuallyRetrigger);
		EnvelopeRetriggerFromOrigin(NLProc->WaveTableIndexEnvelope,NewAccents,
			NewInitialFrequency,1,NewHurryUp,
			NLProc->EnvelopeTicksPerSecond,ActuallyRetrigger);
		LFOGeneratorRetriggerFromOrigin(NLProc->InputScalingLFO,NewAccents,
			NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
		LFOGeneratorRetriggerFromOrigin(NLProc->OutputScalingLFO,NewAccents,
			NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
		LFOGeneratorRetriggerFromOrigin(NLProc->WaveTableIndexLFO,NewAccents,
			NewInitialFrequency,NewHurryUp,1,1,ActuallyRetrigger);
	}
