#include <stdio.h>

#include "aSfxDsp.hpp"
#include "3dl2.h"

#include <stdlib.h>
#include <string.h>	// for memmove and FMOD_memcpy

#include "../../../src/fmod_memory.h"
#include "../../../src/fmod_types.h"

#define ADD_ANTI_DENORMALS_TO_INPUT

#define SfxDataMove(source, dest, bytes)	memmove(dest, source, (unsigned)bytes)

//-----------------------------------------------------------------------------

int ASfxDsp::init(float rate)
{
	int delayLine;
	int error = 0;

	mEarlyLateDelay = NULL;
	mEarlyDelay = NULL;
	
	ZeroWritePointers();
	
	mEarlyLateSec[0] = 0.040f;
	mEarlyLateSamples[0] = (int)(mEarlyLateSec[0] * rate);
	SetLate_EarlyLateDelayTaps(0.060f, kEarlyLateNextLengthSec, kEarlyLateDelayRatio, rate);


    for (delayLine=0; delayLine<kNumEarlyLateDelayTaps; delayLine++)
	{
		mOldEarlyLateSamples[delayLine] = mEarlyLateSamples[delayLine];
	}
	for (delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		mLateMask[delayLine] = 0;
    }

	SetEarlyDelay(kEarlyFirstDelaySec, kEarlyDelayRatio, rate);
	SetAllpassDelays(rate);	
	SetLateDelays(LOWEST_DELAY_LINE_LENGTH_SEC, DELAY_LINE_RATIO, LOWEST_DELAY_LINE_LENGTH_B_SEC, DELAY_LINE_RATIO_B, rate);

	for (delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		mOldLateDelayLenSamples[delayLine] = mLateDelayLenSamples[delayLine];
		mLateChanged[delayLine] = false;
		mDecayHFChanged[delayLine] = false;
    }

	if (error = AllocateLateDelays(kNumLateReverbDelays, mLateDelayLenSec, rate))	// assignment, not comparison!
	{
		return error;
	}
	
	if (error = AllocateEarlyLateDelay(mEarlyLateSec, rate))
	{
		return error;
	}
	
	if (error = AllocateAllpassDelays(kNumAllpassDelays, mAllpassDelayLenSec, rate))
	{
		return error;
	}
	
	if (error = AllocateEarlyDelay(mEarlyDelayLenSec[kNumEarlyDelayTaps-1], rate))
	{
		return error;
	}

	mNumAllocatedInBuffSamples = 0;
	
	mInBuff = mInBuffMemory = 0;	
	mMatrix   = (float *)FMOD_ALIGNPOINTER(mMatrixMem, 16);
	mzDecayHF = (float *)FMOD_ALIGNPOINTER(mzDecayHFMem, 16);
			
	for (delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		mOldFeedback[delayLine] = mPrevFeedback[delayLine] = mLatchFeedback[delayLine] = mFeedback[delayLine] = 0.32f;	
		mLatchDecayHF[delayLine] = mPrevDecayHF[delayLine] = mOldDecayHF[delayLine] = mDecayHF[delayLine] = 0.40f;			
		mLatchOneMinusDecayHF[delayLine] = 1.0f - mDecayHF[delayLine];
	}

	mOldHadamard = mPrevHadamard = mLatchHadamard = mHadamard = 1.0f;  
	mOldDiffusionScale = mPrevDiffusionScale = mLatchDiffusionScale = mDiffusionScale = 1.0f;  
	mOldRoomHF = mPrevRoomHF = mLatchRoomHF = mRoomHF = 0.98f;	
	mRoomHFChanged = false;	
	
    //FMOD_memset(&mOldRoomLF, 0, sizeof(struct coeff2ndorder));
    //FMOD_memset(&mPrevRoomLF, 0, sizeof(struct coeff2ndorder));
    //FMOD_memset(&mLatchRoomLF, 0, sizeof(struct coeff2ndorder));
    FMOD_memset(&mRoomLFcoeffs, 0, sizeof(struct coeff2ndorder));
	// mOldRoomLF.a0 = mPrevRoomLF.a0 = mLatchRoomLF.a0 = 
    mRoomLFcoeffs.a0 = 1.0f;	
    mRoomLF = 0.0f;
	//mRoomLFChanged = false;	

	mDiffusionScale = 0.25f;	// 0.3535;	// cos^4(PI/4)
	mAllpassGain = 0.63f;	// ?
	mPrevLRgain = mOldLRgain = mLatchLRgain = mLRgain = 0.0f;	// ?
	mLRgainChanged = false;
	mPrevERgain = mOldERgain = mLatchERgain = mERgain = 0.0f;		// ?
	mERgainChanged = false;
	ClearReverbInternalBuffers();
	
	return 0;
}

void ASfxDsp::close()
{
	if (mInBuffMemory)
	{
		FMOD_Memory_Free(mInBuffMemory);
    	mInBuffMemory = mInBuff = 0;
	}

	DeallocateLateDelays();
	DeallocateEarlyLateDelay();
    DeallocateAllpassDelays();
    DeallocateEarlyDelay();
}


int ASfxDsp::AllocateLateDelays(int numDelays, float *delaySec, float sampleRate)
{
	int delaySamples;
	
	DeallocateLateDelays();		// *** But if it hasn't changed, we may want to skip all this... ***
	
	for (int delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		delaySamples = (int)(delaySec[delayLine] * sampleRate) + 1;
	
		delaySamples = NextPowerOf2(delaySamples);

		// Allocate delay
		
		if ((mLateDelays[delayLine] = (float*) FMOD_Memory_Calloc(delaySamples * sizeof(float))) == NULL)
		{
			return REVERB_LATE_ALLOCATION_ERR;
		}
		
		mLateDelaySamplesAllocated[delayLine] = delaySamples;
		mLateMask[delayLine] = delaySamples - 1;
		mLateWritePointer[delayLine] = 0;	
	}
	
	return 0;
}

int ASfxDsp::AllocateEarlyLateDelay(float *delaySec, float sampleRate)
{
	// Allocate it to the max Ref & Rev delays, so we don't have to reallocate it if the delays change.
	float maxDelaySec = I3DL2LISTENER_MAXREFLECTIONSDELAY + I3DL2LISTENER_MAXREVERBDELAY
				+ delaySec[kNumEarlyLateDelayTaps-1] - delaySec[kNumEarly_EarlyLateDelayTaps];

	int delaySamples = (int)(maxDelaySec * sampleRate) + 1;
	
	delaySamples = NextPowerOf2(delaySamples);
	
	DeallocateEarlyLateDelay();		// *** But if it hasn't changed, we may want to skip all this... ***
	
	// Allocate delay
	
	if ((mEarlyLateDelay = (float*) FMOD_Memory_Calloc(delaySamples * sizeof(float))) == NULL)
	{
		return REVERB_EARLYLATE_ALLOCATION_ERR;
	}
	
	mEarlyLateSamplesAllocated = delaySamples;
	mEarlyLateMask = delaySamples - 1;
	
	return 0;
}

int ASfxDsp::AllocateAllpassDelays(int numDelays, float *delaySec, float sampleRate)
{
	int		delaySamples;
	
	DeallocateAllpassDelays();		// *** But if it hasn't changed, we may want to skip all this... ***
	
	for (int delayLine=0; delayLine<kNumAllpassDelays; delayLine++)
	{
		delaySamples = (int)(delaySec[delayLine] * sampleRate) + 1;
		delaySamples = NextPowerOf2(delaySamples);

		// Allocate delay
		
		if ((mAllpassDelays[delayLine] = (float*) FMOD_Memory_Calloc(delaySamples * sizeof(float))) == NULL)
		{
			return REVERB_ALLPASS_ALLOCATION_ERR;
		}
		
		mAllpassSamplesAllocated[delayLine] = delaySamples;
		mAllpassMask[delayLine] = delaySamples - 1;
		mAllpassWritePointer[delayLine] = 0;		// Do this here?
	}
	
	return 0;
}

int ASfxDsp::AllocateEarlyDelay(float delaySec, float sampleRate)
{
	int delaySamples = (int)(delaySec * sampleRate) + 1;
	
	delaySamples = NextPowerOf2(delaySamples);

	DeallocateEarlyDelay();		// *** But if it hasn't changed, we may want to skip all this... ***
	
	// Allocate delay
	
	if ((mEarlyDelay = (float*) FMOD_Memory_Calloc(delaySamples * sizeof(float))) == NULL)
	{
		return REVERB_EARLY_ALLOCATION_ERR;
	}
	
	mEarlyDelaySamplesAllocated = delaySamples;
	mEarlyMask = delaySamples - 1;
	
	return 0;
}

int	ASfxDsp::NextPowerOf2(int delaySamples)
{
	return 1 << ((int)(FMOD_LOG((float)delaySamples) / FMOD_LOG(2.0f)) + 1);
}


void ASfxDsp::DeallocateLateDelays()
{
	for (int delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		if (mLateDelays[delayLine])
		{
			FMOD_Memory_Free(mLateDelays[delayLine]);
		}

		mLateDelays[delayLine] = NULL;
	}
}

void ASfxDsp::DeallocateAllpassDelays()
{
	for (int delayLine=0; delayLine<kNumAllpassDelays; delayLine++)
	{
		if (mAllpassDelays[delayLine])
		{
			FMOD_Memory_Free(mAllpassDelays[delayLine]);
		}

		mAllpassDelays[delayLine] = NULL;
	}
}

void ASfxDsp::DeallocateEarlyDelay()
{
	if (mEarlyDelay)
	{
		FMOD_Memory_Free(mEarlyDelay);
	}
	
	mEarlyDelay = NULL;
}

void ASfxDsp::DeallocateEarlyLateDelay()
{
	if (mEarlyLateDelay)
	{
		FMOD_Memory_Free(mEarlyLateDelay);
	}
	
	mEarlyLateDelay = NULL;
}

//-----------------------------------------------------------------------------
//#pragma export(UpdateBufferSize)
int ASfxDsp::UpdateBufferSize(int newMaxBlockSize)
{
	// nothing to do if the maximum number of samples per processing block hasn't changed
	if (newMaxBlockSize == mNumAllocatedInBuffSamples)
    {
		return 0;	// buffer size wasn't changed
    }

	// update with the new value
	mNumAllocatedInBuffSamples = newMaxBlockSize;

	// (re)allocate audio buffers in mInBuff
	if (mInBuffMemory != NULL)
	{
		FMOD_Memory_Free(mInBuffMemory);
	}
	
	mInBuffMemory = (float*) FMOD_Memory_Alloc((newMaxBlockSize * sizeof(float)) + 16); 
    if (!mInBuffMemory)
    {
        return REVERB_INBUFF_ALLOCATION_ERR;
    }
    mInBuff = (float *)FMOD_ALIGNPOINTER(mInBuffMemory, 16);

	return 0;	// buffer size changed
}

//-----------------------------------------------------------------------------
//#pragma export (ASfxDsp::ClearBuffers)
void ASfxDsp::ClearBuffers()
{
	ClearInBuff();
	ClearReverbInternalBuffers();
}


//-----------------------------------------------------------------------------
void ASfxDsp::ClearInBuff()
{
	if (mInBuff != NULL)
	{
		// CHANGE THIS TO A BLOCK MOVE
		for (int i=0; i < mNumAllocatedInBuffSamples; i++)
		{
			mInBuff[i] = 0.0f;
		}
	}
}


//-----------------------------------------------------------------------------
void ASfxDsp::ClearReverbInternalBuffers()
{
	int delayLine;
	
	if (mLateDelays)
	{
		for (delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
		{
			if (mLateDelays[delayLine])
			{
				// *** CHANGE THESE TO BLOCK MOVES FOR BETTER EFFICIENCY ***
				for (int samp=0; samp<mLateDelaySamplesAllocated[delayLine]; samp++)	
				{
					mLateDelays[delayLine][samp] = 0.0f;
				}
			}
		}
	}
	
	// Clear earlyLate delay line.
	// Replace this with a block move.
	int samp;
    for (samp=0; samp<mEarlyLateSamplesAllocated; samp++)
	{
		mEarlyLateDelay[samp] = 0.0f;
	}
	
	// Clear early delay line.
	// Replace this with a block move.
	for (samp=0; samp<mEarlyDelaySamplesAllocated; samp++)
	{
		mEarlyDelay[samp] = 0.0f;
	}
	
	// Clear allpass delays
	for (delayLine=0; delayLine<kNumAllpassDelays; delayLine++)
	{
		for (int samp=0; samp<mAllpassSamplesAllocated[delayLine]; samp++)
		{
			mAllpassDelays[delayLine][samp] = 0.0f;
		}
	}
	
	// Zero the internal variables
   FMOD_memset(mzDecayHF, 0, kNumLateReverbDelays * sizeof(float));
   FMOD_memset(mMatrix, 0, kNumMatrixStages * kNumLateReverbDelays * sizeof(float));
   mzRoomHF0 = 0.0f;
   mzRoomHF1 = 0.0f;
   mzRoomLF0 = 0.0f;
   mzRoomLF1 = 0.0f;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~ DoDSPProcessing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ASfxDsp::DoDSPProcessing (float * inAudio, float * outAudio, int channels, unsigned int l_sampframes,
							float rate, float drymix, unsigned short speakermask)
{
	unsigned int	i;
	int				ch;	
	float           eruOut[kNumEarlyDelayTaps + 1];       
	float			val, oldVal;
	int				delayLine, delayTap;
	unsigned int	sampframes = (unsigned int)l_sampframes;
	unsigned int	sampframesmi;	// sampframes - i
	float			invSampframes = 1.0f / (float)sampframes;

#ifdef ADD_ANTI_DENORMALS_TO_INPUT
#define VERY_SMALL_BUT_NOT_DENORMAL_VALUE	(1.0e-15f)
	// mix very quiet noise (-300 dB) into the input signal to hopefully avoid any denormal values
	for (ch=0; ch < channels; ch++)
	{
		for (i=0; i < sampframes; i += 2)
        {
			outAudio[ch+channels*i] = inAudio[ch+channels*i] + VERY_SMALL_BUT_NOT_DENORMAL_VALUE;
        }
		for (i=1; i < sampframes; i += 2)
        {
			outAudio[ch+channels*i] = inAudio[ch+channels*i] - VERY_SMALL_BUT_NOT_DENORMAL_VALUE;
        }
	}
#undef VERY_SMALL_BUT_NOT_DENORMAL_VALUE
#endif

    // Copy input into our input buffer. This may be faster than doing it inside the big loop.
	BlockProcessInput(sampframes, channels, outAudio, rate);	

	// Adjust the EarlyLate read pointers in case Ref or Rev delays have changed
	for (delayTap=0; delayTap<kNumEarlyLateDelayTaps; delayTap++)
	{
		mEarlyLateReadPointer[delayTap] = (mEarlyLateSamples[delayTap] + mEarlyLateWritePointer - 1) & mEarlyLateMask;

        if (mEarlyLateSamples[delayTap] == mOldEarlyLateSamples[delayTap])
		{
			mEarlyLateChanged[delayTap] = false;
		}
		else	// If the delay has changed, we need to do two reads and crossfade them.
		{
			mEarlyLateChanged[delayTap] = true;
			mOldEarlyLateReadPointer[delayTap] = (mOldEarlyLateSamples[delayTap] + mEarlyLateWritePointer - 1) & mEarlyLateMask;
			mOldEarlyLateSamples[delayTap] = mEarlyLateSamples[delayTap];	// For next time...
		}
    }
	// Deal with interpolations and crossfades
	for (delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		// Adjust the Late read pointers in case late delay sizes have changed
		mLateReadPointer[delayLine] = (mLateDelayLenSamples[delayLine] + mLateWritePointer[delayLine] - 1) 
										& mLateMask[delayLine];
		mLateReadPointerB[delayLine] = (mLateDelayLenBSamples[delayLine] + mLateWritePointer[delayLine] - 1) 
										& mLateMask[delayLine];
		if (mLateDelayLenSamples[delayLine] == mOldLateDelayLenSamples[delayLine])
		{
			mLateChanged[delayLine] = false;
		}
		else	// If the late delays changed, we need to do two reads and crossfade them.
		{
			mLateChanged[delayLine] = true;
			mOldLateReadPointer[delayLine] = (mOldLateDelayLenSamples[delayLine] + mLateWritePointer[delayLine] - 1) 
										& mLateMask[delayLine];
			mOldLateDelayLenSamples[delayLine] = mLateDelayLenSamples[delayLine];	// For next time...
		}
		
		mLatchFeedback[delayLine] = mFeedback[delayLine];		// Latch it in case it changes in the interim.
		
		if (mLatchFeedback[delayLine] == mOldFeedback[delayLine])
		{
			mPrevFeedback[delayLine] = mLatchFeedback[delayLine];	// Use it for our crossfade (in case mFeedback hasn't changed)
		}
		else
		{
			if (!mLateChanged[delayLine])	// if we haven't made these changes above
			{
				mLateChanged[delayLine] = true;
				mOldLateReadPointer[delayLine] = (mOldLateDelayLenSamples[delayLine] + mLateWritePointer[delayLine] - 1) 
									& mLateMask[delayLine];
				// We do this inside the 'if', because mOldLateDelayLenSamples gets changed above, 
				// which would change mOldLateReadPointer back to mLateReadPointer.
			}
			
			mPrevFeedback[delayLine] = mOldFeedback[delayLine];	// For our crossfade
			mOldFeedback[delayLine] = mLatchFeedback[delayLine];		// For next time...
		}
		
		mLatchDecayHF[delayLine] = mDecayHF[delayLine];		// Latch it for our interpolation.
		mLatchOneMinusDecayHF[delayLine] = 1.0f - mLatchDecayHF[delayLine];
		
		if (mLatchDecayHF[delayLine] == mOldDecayHF[delayLine])
		{
			mDecayHFChanged[delayLine] = false;
		}
		else
		{
			mDecayHFChanged[delayLine] = true;
			mPrevDecayHF[delayLine] = mOldDecayHF[delayLine];	// For our interpolation
			mOldDecayHF[delayLine] = mLatchDecayHF[delayLine];	// For next time...
		}
	}

	mLatchLRgain = mLRgain;
	
	if (mLatchLRgain == mOldLRgain)
	{
		mLRgainChanged = false;
	}
	else
	{
		mLRgainChanged = true;
		mPrevLRgain = mOldLRgain;		// For our interpolation
		mOldLRgain = mLatchLRgain;		// For next time
	}

	mLatchERgain = mERgain;
	
	if (mLatchERgain == mOldERgain)
	{
		mERgainChanged = false;
	}
	else
	{
		mERgainChanged = true;
		mPrevERgain = mOldERgain;		// For our interpolation
		mOldERgain = mLatchERgain;		// For next time
	}

	mLatchRoomHF = mRoomHF;
	
	if (mLatchRoomHF == mOldRoomHF)
	{
		mRoomHFChanged = false;
	}
	else
	{
		mRoomHFChanged = true;
		mPrevRoomHF = mOldRoomHF;		// For our interpolation
		mOldRoomHF = mLatchRoomHF;		// For next time
	}

	mLatchHadamard = mHadamard;

	if (mLatchHadamard == mOldHadamard)
	{
		mHadamardChanged = false;
	}
	else
	{
		mHadamardChanged = true;
		mPrevHadamard = mOldHadamard;		// For our interpolation
		mOldHadamard = mLatchHadamard;		// For next time
	}

	mLatchDiffusionScale = mDiffusionScale;

    if (mLatchDiffusionScale == mOldDiffusionScale)
	{
		mDiffusionScaleChanged = false;
	}
	else
	{
		mDiffusionScaleChanged = true;
		mPrevDiffusionScale = mOldDiffusionScale;		// For our interpolation
		mOldDiffusionScale = mLatchDiffusionScale;		// For next time
	}

    /*
	mLatchRoomLF = mRoomLF;
	
	if (mLatchRoomLF == mOldRoomLF)
	{
		mRoomLFChanged = false;
	}
	else
	{
		mRoomLFChanged = true;
		mPrevRoomLF = mOldRoomLF;		// For our interpolation
		mOldRoomLF = mLatchRoomLF;		// For next time
	}
    */

	// Now start the Big DSP Loop...
	for (i=0; i<sampframes; i++)	// for each sample
	{
		sampframesmi = sampframes - i;
        val = (float)mInBuff[i];
					
		// Room HF low-pass filters
		float roomHF, hadamard, diffusionscale;
		
		if (mRoomHFChanged)
		{
			roomHF = ((mPrevRoomHF * sampframesmi) + (mLatchRoomHF * i)) * invSampframes;
		}
		else
		{
			roomHF = mLatchRoomHF;
		}

		if (mHadamardChanged)
		{
			hadamard = ((mPrevHadamard * sampframesmi) + (mLatchHadamard * i)) * invSampframes;
		}
		else
		{
			hadamard = mLatchHadamard;
		}

		if (mDiffusionScaleChanged)
		{
			diffusionscale = ((mPrevDiffusionScale * sampframesmi) + (mLatchDiffusionScale * i)) * invSampframes;
		}
		else
		{
			diffusionscale = mLatchDiffusionScale;
		}


        /*
        Room LF - 2nd order high pass filter, don't interpolate yet

        if(mRoomLFChanged)
        {
			roomLF = ((mPrevRoomLF * sampframesmi) + (mLatchRoomLF * i)) * invSampframes;
        }
        else
        {
			roomLF = mLatchRoomLF;
        }
        */

		float oneMinusRoomHF = 1.0f - roomHF;
		mzRoomHF0 = val*roomHF + mzRoomHF0*oneMinusRoomHF;
		mzRoomHF1 = mzRoomHF0*roomHF + mzRoomHF1*oneMinusRoomHF;

        float fout;
        if(mRoomLF != 0.0f)
        {
            fout = mzRoomHF1 * mRoomLFcoeffs.a0 + mzRoomLF0;
            mzRoomLF0  = mzRoomHF1 * mRoomLFcoeffs.a1 + mzRoomLF1 + fout * mRoomLFcoeffs.b1;
            mzRoomLF1  = mzRoomHF1 * mRoomLFcoeffs.a2 + fout * mRoomLFcoeffs.b2;
        }
        else
        {
            fout = mzRoomHF1;
        }

		// Insert Delay here
		mEarlyLateWritePointer = --mEarlyLateWritePointer & mEarlyLateMask;
		mEarlyLateDelay[mEarlyLateWritePointer] = fout;
				
		// ================== Early Reflections ================== 
		
		mEarlyLateReadPointer[0] = --mEarlyLateReadPointer[0] & mEarlyLateMask;
		float	input, output, ERgain;
		
		if (mERgainChanged)
		{
			ERgain = ((mPrevERgain * sampframesmi) + (mLatchERgain * i)) * invSampframes;
		}
		else
		{
			ERgain = mLatchERgain;
		}
		
		input = mEarlyLateDelay[mEarlyLateReadPointer[0]] * ERgain;

		if (mEarlyLateChanged[0])	// Crossfade the old and new reads
		{
			mOldEarlyLateReadPointer[0] = --mOldEarlyLateReadPointer[0] & mEarlyLateMask;
			float oldInput = mEarlyLateDelay[mOldEarlyLateReadPointer[0]] * ERgain;
			input = ((oldInput * sampframesmi) + (input * i)) * invSampframes;
		}	
        
		// 2 Allpasses in series

		for (delayLine=0; delayLine<kNumAllpassDelays; delayLine++)
		{
			mAllpassWritePointer[delayLine] = --mAllpassWritePointer[delayLine] & mAllpassMask[delayLine];
			
			mAllpassReadPointer[delayLine] = --mAllpassReadPointer[delayLine] & mAllpassMask[delayLine];
			output = mAllpassDelays[delayLine][mAllpassReadPointer[delayLine]]
										- input * mAllpassGain;		// feedforward loop
			
			mAllpassDelays[delayLine][mAllpassWritePointer[delayLine]] =
										input + output * mAllpassGain;	// feedback loop
			input = output;
		}
		
		// Early Delay FIR
		
		eruOut[0] = output;
		
		mEarlyWritePointer = --mEarlyWritePointer & mEarlyMask;
		mEarlyDelay[mEarlyWritePointer] = output;
		
		for (delayTap=0; delayTap<kNumEarlyDelayTaps; delayTap++)
		{
			mEarlyReadPointer[delayTap] = --mEarlyReadPointer[delayTap] & mEarlyMask;
			eruOut[delayTap+1] = mEarlyDelay[mEarlyReadPointer[delayTap]];
		}

		
		// ================== Late Reverb ====================
		
		// This assumes mNumLateReverbDelays = kNumLate_EarlyLateDelayTaps.
		for (delayLine=0; delayLine<mNumLateReverbDelays; delayLine++)
		{
			delayTap = kNumEarly_EarlyLateDelayTaps + delayLine;
			
			mEarlyLateReadPointer[delayTap] = --mEarlyLateReadPointer[delayTap] & mEarlyLateMask;
			val = mEarlyLateDelay[mEarlyLateReadPointer[delayTap]];	

			if (mEarlyLateChanged[delayTap])	// Crossfade the old and new reads
			{
				mOldEarlyLateReadPointer[delayTap] = --mOldEarlyLateReadPointer[delayTap] & mEarlyLateMask;
				oldVal = mEarlyLateDelay[mOldEarlyLateReadPointer[delayTap]];
				val = ((oldVal * sampframesmi) + (val * i)) * invSampframes;
			}

			if (mLRgainChanged)
			{
				float LRgain = ((mPrevLRgain * sampframesmi) + (mLatchLRgain * i)) * invSampframes;
				val *= LRgain;
			}
			else
			{
				val *= mLatchLRgain;
			}

			mLateReadPointer[delayLine] = --mLateReadPointer[delayLine] & mLateMask[delayLine];
			if (mLateChanged[delayLine])	// Crossfade
			{
				mOldLateReadPointer[delayLine] = --mOldLateReadPointer[delayLine] & mLateMask[delayLine];
				oldVal = mPrevFeedback[delayLine] * mLateDelays[delayLine][mOldLateReadPointer[delayLine]];
				float thisVal = mLatchFeedback[delayLine] * mLateDelays[delayLine][mLateReadPointer[delayLine]];
				val += (((oldVal * sampframesmi) + (thisVal * i)) * invSampframes);
			}
			else
			{
				val += (mLatchFeedback[delayLine] * mLateDelays[delayLine][mLateReadPointer[delayLine]]);
			}

			// Gain to compensate for one matrix gain, based on the diffusion.
			// val *= mDiffusionScale;
			val *= diffusionscale;
			
			// Low-pass filter for HF Decay ratio
			if (mDecayHFChanged[delayLine])
			{
				// Interpolate decayHF
				float decayHF = ((mPrevDecayHF[delayLine] * sampframesmi) + (mLatchDecayHF[delayLine] * i)) * invSampframes;
				float oneMinusDecayHF = 1.0f - decayHF;
				mzDecayHF[delayLine] = val * decayHF + mzDecayHF[delayLine] * oneMinusDecayHF;
			}
			else
			{
				mzDecayHF[delayLine] = val * mLatchDecayHF[delayLine] + mzDecayHF[delayLine] * mLatchOneMinusDecayHF[delayLine];
			}
			
			mLateWritePointer[delayLine] = --mLateWritePointer[delayLine] & mLateMask[delayLine];			
		}


		mMatrix[0 * kNumLateReverbDelays + 0] = mzDecayHF[0] + mzDecayHF[1] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 1] = mzDecayHF[1] - mzDecayHF[0] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 2] = mzDecayHF[2] + mzDecayHF[3] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 3] = mzDecayHF[3] - mzDecayHF[2] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 4] = mzDecayHF[4] + mzDecayHF[5] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 5] = mzDecayHF[5] - mzDecayHF[4] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 6] = mzDecayHF[6] + mzDecayHF[7] * hadamard;
		mMatrix[0 * kNumLateReverbDelays + 7] = mzDecayHF[7] - mzDecayHF[6] * hadamard;
		
		mMatrix[1 * kNumLateReverbDelays + 0] = mMatrix[0 * kNumLateReverbDelays + 0] + mMatrix[0 * kNumLateReverbDelays + 2] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 1] = mMatrix[0 * kNumLateReverbDelays + 1] + mMatrix[0 * kNumLateReverbDelays + 3] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 2] = mMatrix[0 * kNumLateReverbDelays + 2] - mMatrix[0 * kNumLateReverbDelays + 0] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 3] = mMatrix[0 * kNumLateReverbDelays + 3] - mMatrix[0 * kNumLateReverbDelays + 1] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 4] = mMatrix[0 * kNumLateReverbDelays + 4] + mMatrix[0 * kNumLateReverbDelays + 6] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 5] = mMatrix[0 * kNumLateReverbDelays + 5] + mMatrix[0 * kNumLateReverbDelays + 7] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 6] = mMatrix[0 * kNumLateReverbDelays + 6] - mMatrix[0 * kNumLateReverbDelays + 4] * hadamard;
		mMatrix[1 * kNumLateReverbDelays + 7] = mMatrix[0 * kNumLateReverbDelays + 7] - mMatrix[0 * kNumLateReverbDelays + 5] * hadamard;
		
		mMatrix[2 * kNumLateReverbDelays + 0] = mMatrix[1 * kNumLateReverbDelays + 0] + mMatrix[1 * kNumLateReverbDelays + 4] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 1] = mMatrix[1 * kNumLateReverbDelays + 1] + mMatrix[1 * kNumLateReverbDelays + 5] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 2] = mMatrix[1 * kNumLateReverbDelays + 2] + mMatrix[1 * kNumLateReverbDelays + 6] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 3] = mMatrix[1 * kNumLateReverbDelays + 3] + mMatrix[1 * kNumLateReverbDelays + 7] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 4] = mMatrix[1 * kNumLateReverbDelays + 4] - mMatrix[1 * kNumLateReverbDelays + 0] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 5] = mMatrix[1 * kNumLateReverbDelays + 5] - mMatrix[1 * kNumLateReverbDelays + 1] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 6] = mMatrix[1 * kNumLateReverbDelays + 6] - mMatrix[1 * kNumLateReverbDelays + 2] * hadamard;
		mMatrix[2 * kNumLateReverbDelays + 7] = mMatrix[1 * kNumLateReverbDelays + 7] - mMatrix[1 * kNumLateReverbDelays + 3] * hadamard;
		
		// Write to the lateDelays
		mLateDelays[0][mLateWritePointer[0]] = mMatrix[2 * kNumLateReverbDelays + 1];
		mLateDelays[1][mLateWritePointer[1]] = mMatrix[2 * kNumLateReverbDelays + 2];
		mLateDelays[2][mLateWritePointer[2]] = mMatrix[2 * kNumLateReverbDelays + 3];
		mLateDelays[3][mLateWritePointer[3]] = mMatrix[2 * kNumLateReverbDelays + 4];
		mLateDelays[4][mLateWritePointer[4]] = mMatrix[2 * kNumLateReverbDelays + 5];
		mLateDelays[5][mLateWritePointer[5]] = mMatrix[2 * kNumLateReverbDelays + 6];
		mLateDelays[6][mLateWritePointer[6]] = mMatrix[2 * kNumLateReverbDelays + 7];
		mLateDelays[7][mLateWritePointer[7]] = mMatrix[2 * kNumLateReverbDelays + 0];

		for (ch=0; ch<channels; ch++)		// for each channel
		{
			if (!((1 << ch) & speakermask))
			{
				outAudio[i*channels+ch] = inAudio[i*channels+ch];
			}
			else
			{
				float lruSum = 0.0f;				
				float eruSum = 0.0f;
				int nextDelay = ch;

				for (int delay=0; delay<(kNumLateReverbDelays/channels); delay++)
				{
					mLateReadPointerB[nextDelay] = --mLateReadPointerB[nextDelay] & mLateMask[nextDelay];
					lruSum += mLateDelays[nextDelay][mLateReadPointerB[nextDelay]];
					
					eruSum += eruOut[nextDelay];
					nextDelay += channels;
				}
										
				if (drymix == 0.0f)
				{
					outAudio[i*channels+ch] = eruSum + lruSum;	
				}
				else
				{
					outAudio[i*channels+ch] = (inAudio[i*channels+ch] * drymix) + eruSum + lruSum;	
				}
			}

		}	// end for each channel
	}	// end for each sample
		
}	// end DoDSPProcessing


// Copy input into our input buffer. This may be faster than doing it inside the big loop (?).
void ASfxDsp::BlockProcessInput(unsigned int sampframes, int channels, float *inAudio, float rate)
{
	unsigned int i;

	if (channels == 1)            // mono   
	{
       SfxDataMove(inAudio, mInBuff, sampframes*sizeof(float));
	}
    else if (channels == 6)
    {
        float *inptr = inAudio;
        float *outptr = mInBuff;

        for (i = 0; i < sampframes / 4; i++)    // for each sample
        {
            outptr[0] = inptr[0]  + inptr[1]  + inptr[2]  + inptr[3]  + inptr[4]  + inptr[5];
            outptr[1] = inptr[6]  + inptr[7]  + inptr[8]  + inptr[9]  + inptr[10] + inptr[11];
            outptr[2] = inptr[12] + inptr[13] + inptr[14] + inptr[15] + inptr[16] + inptr[17];
            outptr[3] = inptr[18] + inptr[19] + inptr[20] + inptr[21] + inptr[22] + inptr[23];
            inptr  += 24;
            outptr += 4;
        }
    }
    else if (channels == 8)
    {
        float *inptr = inAudio;
        float *outptr = mInBuff;

        for (i = 0; i < sampframes / 4; i++)    // for each sample
        {
            outptr[0] = inptr[0]  + inptr[1]  + inptr[2]  + inptr[3]  + inptr[4]  + inptr[5]  + inptr[6]  + inptr[7];
            outptr[1] = inptr[8]  + inptr[9]  + inptr[10] + inptr[11] + inptr[12] + inptr[13] + inptr[14] + inptr[15];
            outptr[2] = inptr[16] + inptr[17] + inptr[18] + inptr[19] + inptr[20] + inptr[21] + inptr[22] + inptr[23];
            outptr[3] = inptr[24] + inptr[25] + inptr[26] + inptr[27] + inptr[28] + inptr[29] + inptr[30] + inptr[31];
            inptr  += 32;
            outptr += 4;

        }
    }
	else 
	{
    	int ch;

    	for (i=0; i<sampframes; i++)    // for each sample
		{
			mInBuff[i] = 0.0f;
			for (ch=0; ch<channels; ++ch)        // for each channel
			{
				mInBuff[i] += inAudio[i*channels+ch];	// *** Would it be more efficient to reverse the loops? ***
			}
           // mInBuff[i] /= (float)channels;	// Don't average them!
		}          
	}
}	




void ASfxDsp::SetLate_EarlyLateDelayTaps(float refPlusRevDelaySec, float nextLengthSec, float delayRatio, float rate)	
{
    mEarlyLateSec[kNumEarly_EarlyLateDelayTaps] = refPlusRevDelaySec;       // Tap 1 = Ref+Rev delay
    int earlyLateSamples = (int)(refPlusRevDelaySec * rate);       
    if (earlyLateSamples == 0)
    {
        earlyLateSamples++;             // Avoid long wrap-around delay.
    }
    mEarlyLateSamples[kNumEarly_EarlyLateDelayTaps] = earlyLateSamples;
	
	// Set the remaining EarlyLate delay taps (2 - 8)		
	for (int delayTap=kNumEarly_EarlyLateDelayTaps+1; delayTap<kNumEarlyLateDelayTaps; delayTap++)
	{
		mEarlyLateSec[delayTap] = refPlusRevDelaySec + nextLengthSec;
		mEarlyLateSamples[delayTap] = (int)(mEarlyLateSec[delayTap] * rate);
		nextLengthSec *= delayRatio;
	}
}

void ASfxDsp::SetAllpassDelays(float rate)
{
	mAllpassDelayLenSec[0] = kAllpassDelayLenSec0;
	mAllpassDelayLenSec[1] = kAllpassDelayLenSec1;

	for (int delayLine = 0; delayLine<kNumAllpassDelays; delayLine++)
	{
		mAllpassDelayLenSamples[delayLine] = (int)(mAllpassDelayLenSec[delayLine] * rate);
		mAllpassReadPointer[delayLine] = mAllpassDelayLenSamples[delayLine] - 1;
	}
}

void ASfxDsp::SetEarlyDelay(float nextLengthSec, float delayRatio, float rate)
{	
	// Set the early delay taps (0 - 6)
	for (int delayLine=0; delayLine<kNumEarlyDelayTaps; delayLine++)
	{
		mEarlyDelayLenSec[delayLine] = nextLengthSec;
		mEarlyDelayLenSamples[delayLine] = (int)(nextLengthSec * rate);
		nextLengthSec *= delayRatio;

		mEarlyReadPointer[delayLine] = mEarlyDelayLenSamples[delayLine] - 1;
	}
}

void ASfxDsp::SetLateDelays(float nextLengthSec, float delayRatio, float nextLengthBSec, float delayRatioB, float rate)
{
	float	nextLengthSamples;

	float	nextLengthBSamples = nextLengthBSec * rate;
	
	for (int delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		// Set main delay line lengths
		mLateDelayLenSec[delayLine] = nextLengthSec;
		nextLengthSamples = nextLengthSec * rate;
		mLateDelayLenSamples[delayLine] = (int)(nextLengthSamples + 0.5);	
		nextLengthSec *= delayRatio;
		mLateReadPointer[delayLine] = (mLateDelayLenSamples[delayLine] + mLateWritePointer[delayLine] - 1) 
										& mLateMask[delayLine];

		// Set delay line lengths for B reads
		mLateDelayLenBSamples[delayLine] = (int)(nextLengthBSamples + 0.5);	// Short delays to keep outputs from cancelling.
		nextLengthBSamples *= delayRatioB;
		mLateReadPointerB[delayLine] = (mLateDelayLenBSamples[delayLine] + mLateWritePointer[delayLine] - 1) 
										& mLateMask[delayLine];
	}
}

void ASfxDsp::ZeroWritePointers()
{
	int delayLine;
	
	mEarlyWritePointer = 0;	
	mEarlyLateWritePointer = 0;	
	
	for (delayLine=0; delayLine<kNumAllpassDelays; delayLine++)
	{
		mAllpassWritePointer[delayLine] = 0;
	}
	
	for (delayLine=0; delayLine<kNumLateReverbDelays; delayLine++)
	{
		mLateWritePointer[delayLine] = 0;
	}
}
