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


struct NLProcRec
	{
		/* 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;

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

		/* control parameters */
		float										InputScaling;
		float										InputAccent1;
		float										InputAccent2;
		float										InputAccent3;
		float										InputAccent4;
		float										OutputScaling;
		float										OutputAccent1;
		float										OutputAccent2;
		float										OutputAccent3;
		float										OutputAccent4;
		float										WaveTableIndex;
		float										IndexAccent1;
		float										IndexAccent2;
		float										IndexAccent3;
		float										IndexAccent4;

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


static NLProcRec*				NLProcFreeList = NIL;


/* flush free list elements */
void							FlushCachedNLProcStuff(void)
	{
		while (NLProcFreeList != NIL)
			{
				NLProcRec*			Temp;

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


/* create a new nonlinear processor */
NLProcRec*				NewNLProcProcessor(struct NonlinProcSpecRec* Template,
										MyBoolean StereoFlag, float InverseVolume)
	{
		NLProcRec*			NLProc;

		CheckPtrExistence(Template);
		if (NLProcFreeList != NIL)
			{
				NLProc = NLProcFreeList;
				NLProcFreeList = NLProcFreeList->Next;
			}
		 else
			{
				NLProc = (NLProcRec*)AllocPtrCanFail(sizeof(NLProcRec),"NLProcRec");
				if (NLProc == NIL)
					{
					 FailurePoint1:
						return NIL;
					}
			}
		NLProc->InverseVolume = InverseVolume;
		switch (GetNLProcSpecWaveType(Template))
			{
				default:
					EXECUTE(PRERR(ForceAbort,"NewNLProcProcessor:  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,"NewNLProcProcessor:  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->StereoFlag = StereoFlag;
		NLProc->InputScaling = GetNLProcInputScaling(Template);
		NLProc->InputAccent1 = GetNLProcInputAccent1(Template);
		NLProc->InputAccent2 = GetNLProcInputAccent2(Template);
		NLProc->InputAccent3 = GetNLProcInputAccent3(Template);
		NLProc->InputAccent4 = GetNLProcInputAccent4(Template);
		NLProc->OutputScaling = GetNLProcOutputScaling(Template);
		NLProc->OutputAccent1 = GetNLProcOutputAccent1(Template);
		NLProc->OutputAccent2 = GetNLProcOutputAccent2(Template);
		NLProc->OutputAccent3 = GetNLProcOutputAccent3(Template);
		NLProc->OutputAccent4 = GetNLProcOutputAccent4(Template);
		NLProc->WaveTableIndex = GetNLProcWaveTableIndex(Template);
		NLProc->IndexAccent1 = GetNLProcIndexAccent1(Template);
		NLProc->IndexAccent2 = GetNLProcIndexAccent2(Template);
		NLProc->IndexAccent3 = GetNLProcIndexAccent3(Template);
		NLProc->IndexAccent4 = GetNLProcIndexAccent4(Template);
		return NLProc;
	}


/* dispose of the nonlinear processor */
void							DisposeNLProcProcessor(NLProcRec* NLProc)
	{
		CheckPtrExistence(NLProc);
		ReleasePtr((char*)NLProc->WaveTableMatrix);
		NLProc->Next = NLProcFreeList;
		NLProcFreeList = NLProc;
	}


/* update nonlinear state with accent information */
void							UpdateNLProcState(NLProcRec* NLProc, float Accent1, float Accent2,
										float Accent3, float Accent4)
	{
		float						Temp;

		CheckPtrExistence(NLProc);
		/* multiply by NLProc->InverseVolume to undo the division everyone else does */
		NLProc->CurrentInputScaling = (NLProc->InputScaling + Accent1 * NLProc->InputAccent1
			+ Accent2 * NLProc->InputAccent2 + Accent3 * NLProc->InputAccent3
			+ Accent4 * NLProc->InputAccent4) * 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 = (NLProc->OutputScaling + Accent1 * NLProc->OutputAccent1
			+ Accent2 * NLProc->OutputAccent2 + Accent3 * NLProc->OutputAccent3
			+ Accent4 * NLProc->OutputAccent4) / (NLProc->InverseVolume * MAX16BIT);
		Temp = NLProc->WaveTableIndex + Accent1 * NLProc->IndexAccent1
			+ Accent2 * NLProc->IndexAccent2 + Accent3 * NLProc->IndexAccent3
			+ Accent4 * NLProc->IndexAccent4;
		NLProc->CurrentWaveTableIndex = Double2FastFixed(Temp);
	}


/* apply nonlinear processing to some stuff */
void							ApplyNLProc(largefixedsigned* Data, long NumFrames, NLProcRec* 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);
			}
	}
