/*----------------------------------------------------------------------------
 *
 * File:
 * fmsynth.c
 *
 * Contents and purpose:
 * Implements the high-level FM synthesizer functions.
 *
 * Copyright Sonic Network Inc. 2004

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *----------------------------------------------------------------------------
 * Revision Control:
 *   $Revision: 795 $
 *   $Date: 2007-08-01 00:14:45 -0700 (Wed, 01 Aug 2007) $
 *----------------------------------------------------------------------------
*/

// includes
#include "eas_host.h"
#include "eas_report.h"

#include "eas_data.h"
#include "eas_synth_protos.h"
#include "eas_audioconst.h"
#include "eas_fmengine.h"
#include "eas_math.h"

/* option sanity check */
#ifdef _REVERB
#error "No reverb for FM synthesizer"
#endif
#ifdef _CHORUS
#error "No chorus for FM synthesizer"
#endif

/*
 * WARNING: These macros can cause unwanted side effects. Use them
 * with care. For example, min(x++,y++) will cause either x or y to be
 * incremented twice.
 */
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))

/* pivot point for keyboard scalars */
#define EG_SCALE_PIVOT_POINT 64
#define KEY_SCALE_PIVOT_POINT 36 

/* This number is the negative of the frequency of the note (in cents) of
 * the sine wave played at unity. The number can be calculated as follows:
 *
 * MAGIC_NUMBER = 1200 * log(base2) (SINE_TABLE_SIZE * 8.175798916 / SAMPLE_RATE)
 *
 * 8.17578 is a reference to the frequency of MIDI note 0
 */
#if	defined (_SAMPLE_RATE_8000)
#define MAGIC_NUMBER 1279
#elif	defined (_SAMPLE_RATE_16000)
#define MAGIC_NUMBER 79
#elif	defined (_SAMPLE_RATE_20000)
#define MAGIC_NUMBER -308
#elif	defined (_SAMPLE_RATE_22050)
#define MAGIC_NUMBER -477
#elif	defined (_SAMPLE_RATE_24000)
#define MAGIC_NUMBER -623
#elif defined (_SAMPLE_RATE_32000)
#define MAGIC_NUMBER -1121
#elif defined (_SAMPLE_RATE_44100)
#define MAGIC_NUMBER -1677
#elif defined (_SAMPLE_RATE_48000)
#define MAGIC_NUMBER -1823
#endif

/* externs */
extern const EAS_I16 fmControlTable[128];
extern const EAS_U16 fmRateTable[256];
extern const EAS_U16 fmAttackTable[16];
extern const EAS_U8 fmDecayTable[16];
extern const EAS_U8 fmReleaseTable[16];
extern const EAS_U8 fmScaleTable[16];

/* local prototypes */
/*lint -esym(715, pVoiceMgr) standard synthesizer interface - pVoiceMgr not used */
static EAS_RESULT FM_Initialize (S_VOICE_MGR *pVoiceMgr) { return EAS_SUCCESS; }
static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex);
static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples);
static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum);
static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum);
static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum);
static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel);


/*----------------------------------------------------------------------------
 * Synthesizer interface
 *----------------------------------------------------------------------------
*/
const S_SYNTH_INTERFACE fmSynth = 
{
	FM_Initialize,
	FM_StartVoice,
	FM_UpdateVoice,
	FM_ReleaseVoice,
	FM_MuteVoice,
	FM_SustainPedal,
	FM_UpdateChannel
};

#ifdef FM_OFFBOARD
const S_FRAME_INTERFACE fmFrameInterface = 
{
	FM_StartFrame,
	FM_EndFrame
};
#endif

/*----------------------------------------------------------------------------
 * inline functions
 *----------------------------------------------------------------------------
 */
EAS_INLINE S_FM_VOICE *GetFMVoicePtr (S_VOICE_MGR *pVoiceMgr, EAS_INT voiceNum)
{
	return &pVoiceMgr->fmVoices[voiceNum];
}
EAS_INLINE S_SYNTH_CHANNEL *GetChannelPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice)
{
	return &pSynth->channels[pVoice->channel & 15];
}
EAS_INLINE const S_FM_REGION *GetFMRegionPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice)
{
#ifdef _HYBRID_SYNTH
	return &pSynth->pEAS->pFMRegions[pVoice->regionIndex & REGION_INDEX_MASK];
#else
	return &pSynth->pEAS->pFMRegions[pVoice->regionIndex];
#endif
}

/*----------------------------------------------------------------------------
 * FM_SynthIsOutputOperator
 *----------------------------------------------------------------------------
 * Purpose:
 * Returns true if the operator is a direct output and not muted
 *
 * Inputs:
 *
 * Outputs:
 * Returns boolean
 *----------------------------------------------------------------------------
*/
static EAS_BOOL FM_SynthIsOutputOperator (const S_FM_REGION *pRegion, EAS_INT operIndex)
{

	/* see if voice is muted */
	if ((pRegion->oper[operIndex].gain & 0xfc) == 0)
		return 0;

	/* check based on mode */
	switch (pRegion->region.keyGroupAndFlags & 7)
	{

	    /* mode 0 - all operators are external */
	    case 0:
	        return EAS_TRUE;

	    /* mode 1 - operators 1-3 are external */
	    case 1:
	    	if (operIndex != 0)
	        	return EAS_TRUE;
		break;

	    /* mode 2 - operators 1 & 3 are external */
	    case 2:
	        if ((operIndex == 1) || (operIndex == 3))
	        	return EAS_TRUE;
	        break;

	    /* mode 2 - operators 1 & 2 are external */
	    case 3:
	        if ((operIndex == 1) || (operIndex == 2))
	        	return EAS_TRUE;
	        break;

	    /* modes 4 & 5 - operator 1 is external */
	    case 4:
	    case 5:
	        if (operIndex == 1)
	        	return EAS_TRUE;
	        break;

		default:
			EAS_Report(_EAS_SEVERITY_FATAL,"Invalid voice mode: %d",
				pRegion->region.keyGroupAndFlags & 7);
	}

	return EAS_FALSE;
}

/*----------------------------------------------------------------------------
 * FM_CalcEGRate()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 * Inputs:
 * nKeyNumber - MIDI note
 * nLogRate - logarithmic scale rate from patch data
 * nKeyScale - key scaling factor for this EG
 *
 * Outputs:
 * 16-bit linear multiplier
 *----------------------------------------------------------------------------
*/

static EAS_U16 FM_CalcEGRate (EAS_U8 nKeyNumber, EAS_U8 nLogRate, EAS_U8 nEGScale)
{
	EAS_I32 temp;

    /* incorporate key scaling on release rate */
    temp = (EAS_I32) nLogRate << 7;
    temp += ((EAS_I32) nKeyNumber - EG_SCALE_PIVOT_POINT) * (EAS_I32) nEGScale;

    /* saturate */
    temp = max(temp, 0);
    temp = min(temp, 32767);

    /* look up in rate table */
	/*lint -e{704} use shift for performance */
    return fmRateTable[temp >> 8];
}

/*----------------------------------------------------------------------------
 * FM_ReleaseVoice()
 *----------------------------------------------------------------------------
 * Purpose:
 * The selected voice is being released.
 *
 * Inputs:
 * psEASData - pointer to S_EAS_DATA
 * pVoice - pointer to voice to release
 *
 * Outputs:
 * None
 *----------------------------------------------------------------------------
*/
static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
{
	EAS_INT operIndex;
	const S_FM_REGION *pRegion;
	S_FM_VOICE *pFMVoice;

	/* check to see if voice responds to NOTE-OFF's */
	pRegion = GetFMRegionPtr(pSynth, pVoice);
	if (pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT)
		return;

	/* set all envelopes to release state */
	pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
	for (operIndex = 0; operIndex < 4; operIndex++)
	{
		pFMVoice->oper[operIndex].envState = eFMEnvelopeStateRelease;

		/* incorporate key scaling on release rate */
		pFMVoice->oper[operIndex].envRate = FM_CalcEGRate(
				pVoice->note,
				fmReleaseTable[pRegion->oper[operIndex].velocityRelease & 0x0f],
				fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]);
	} /* end for (operIndex = 0; operIndex < 4; operIndex++) */
}

/*----------------------------------------------------------------------------
 * FM_MuteVoice()
 *----------------------------------------------------------------------------
 * Purpose:
 * The selected voice is being muted.
 *
 * Inputs:
 * pVoice - pointer to voice to release
 *
 * Outputs:
 * None
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pSynth) standard interface, pVoiceMgr not used */
static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
{
	S_FM_VOICE *pFMVoice;

	/* clear deferred action flags */
	pVoice->voiceFlags &=
		~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF |
		VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF |
		VOICE_FLAG_DEFER_MUTE);
	
	/* set all envelopes to muted state */
	pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
	pFMVoice->oper[0].envState = eFMEnvelopeStateMuted;
	pFMVoice->oper[1].envState = eFMEnvelopeStateMuted;
	pFMVoice->oper[2].envState = eFMEnvelopeStateMuted;
	pFMVoice->oper[3].envState = eFMEnvelopeStateMuted;
}

/*----------------------------------------------------------------------------
 * FM_SustainPedal()
 *----------------------------------------------------------------------------
 * Purpose:
 * The selected voice is held due to sustain pedal
 *
 * Inputs:
 * pVoice - pointer to voice to sustain
 *
 * Outputs:
 * None
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pChannel) standard interface, pVoiceMgr not used */
static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum)
{
	const S_FM_REGION *pRegion;
	S_FM_VOICE *pFMVoice;
	EAS_INT operIndex;

	pRegion = GetFMRegionPtr(pSynth, pVoice);
	pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);

	/* check to see if any envelopes are above the sustain level */
	for (operIndex = 0; operIndex < 4; operIndex++)
	{

		/* if level control or envelope gain is zero, skip this envelope */
		if (((pRegion->oper[operIndex].gain & 0xfc) == 0) ||
			(pFMVoice->oper[operIndex].envGain == 0))
		{
			continue;
		}

		/* if the envelope gain is above the sustain level, we need to catch this voice */
		if (pFMVoice->oper[operIndex].envGain >= ((EAS_U16) (pRegion->oper[operIndex].sustain & 0xfc) << 7))
		{

			/* reset envelope to decay state */
			pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay;

			pFMVoice->oper[operIndex].envRate = FM_CalcEGRate(
					pVoice->note,
					fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f],
					fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]);

			/* set voice to decay state */
			pVoice->voiceState = eVoiceStatePlay;

			/* set sustain flag */
			pVoice->voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF;
		}
	} /* end for (operIndex = 0; operIndex < 4; operIndex++) */
}

/*----------------------------------------------------------------------------
 * FM_StartVoice()
 *----------------------------------------------------------------------------
 * Purpose:
 * Assign the region for the given instrument using the midi key number
 * and the RPN2 (coarse tuning) value. By using RPN2 as part of the
 * region selection process, we reduce the amount a given sample has
 * to be transposed by selecting the closest recorded root instead.
 *
 * This routine is the second half of SynthAssignRegion().
 * If the region was successfully found by SynthFindRegionIndex(),
 * then assign the region's parameters to the voice.
 *
 * Setup and initialize the following voice parameters:
 * m_nRegionIndex
 *
 * Inputs:
 * pVoice - ptr to the voice we have assigned for this channel
 * nRegionIndex - index of the region
 * psEASData - pointer to overall EAS data structure
 *
 * Outputs:
 * success - could find and assign the region for this voice's note otherwise
 * failure - could not find nor assign the region for this voice's note
 *
 * Side Effects:
 * psSynthObject->m_sVoice[].m_nRegionIndex is assigned
 * psSynthObject->m_sVoice[] parameters are assigned
 *----------------------------------------------------------------------------
*/
static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex)
{
	S_FM_VOICE *pFMVoice;
	S_SYNTH_CHANNEL *pChannel;
	const S_FM_REGION *pRegion;
	EAS_I32 temp;
	EAS_INT operIndex;

	/* establish pointers to data */
	pVoice->regionIndex = regionIndex;
	pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
	pChannel = GetChannelPtr(pSynth, pVoice);
	pRegion = GetFMRegionPtr(pSynth, pVoice);

	/* update static channel parameters */
	if (pChannel->channelFlags & CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS)
		FM_UpdateChannel(pVoiceMgr, pSynth, pVoice->channel & 15);

	/* init the LFO */
	pFMVoice->lfoValue = 0;
	pFMVoice->lfoPhase = 0;
	pFMVoice->lfoDelay = (EAS_U16) (fmScaleTable[pRegion->lfoFreqDelay & 0x0f] >> 1);

#if	(NUM_OUTPUT_CHANNELS == 2)
	/* calculate pan gain values only if stereo output */
	/* set up panning only at note start */
	temp = (EAS_I32) pChannel->pan - 64;
	temp += (EAS_I32) pRegion->pan;
	if (temp < -64)
		temp = -64;
	if (temp > 64)
		temp = 64;
	pFMVoice->pan = (EAS_I8) temp;
#endif /* #if (NUM_OUTPUT_CHANNELS == 2) */

	/* no samples have been synthesized for this note yet */
	pVoice->voiceFlags = VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET;

	/* initialize gain value for anti-zipper filter */
	pFMVoice->voiceGain = (EAS_I16) EAS_LogToLinear16(pChannel->staticGain);
	pFMVoice->voiceGain = (EAS_I16) FMUL_15x15(pFMVoice->voiceGain, pSynth->masterVolume);

	/* initialize the operators */
	for (operIndex = 0; operIndex < 4; operIndex++)
	{

		/* establish operator output gain level */
		/*lint -e{701} <use shift for performance> */
		pFMVoice->oper[operIndex].outputGain = EAS_LogToLinear16(((EAS_I16) (pRegion->oper[operIndex].gain & 0xfc) - 0xfc) << 7);

		/* check for linear velocity flag */
		/*lint -e{703} <use shift for performance> */
		if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_LINEAR_VELOCITY)
			temp = (EAS_I32) (pVoice->velocity - 127) << 5;
		else
			temp = (EAS_I32) fmControlTable[pVoice->velocity];

		/* scale velocity */
		/*lint -e{704} use shift for performance */
		temp = (temp * (EAS_I32)(pRegion->oper[operIndex].velocityRelease & 0xf0)) >> 7;

		/* include key scalar */
		temp -= ((EAS_I32) pVoice->note - KEY_SCALE_PIVOT_POINT) * (EAS_I32) fmScaleTable[pRegion->oper[operIndex].egKeyScale & 0x0f];

		/* saturate */
		temp = min(temp, 0);
		temp = max(temp, -32768);

		/* save static gain parameters */
		pFMVoice->oper[operIndex].baseGain = (EAS_I16) EAS_LogToLinear16(temp);

		/* incorporate key scaling on decay rate */
		pFMVoice->oper[operIndex].envRate = FM_CalcEGRate(
			pVoice->note,
			fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f],
			fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]);

		/* if zero attack time, max out envelope and jump to decay state */
		if ((pRegion->oper[operIndex].attackDecay & 0xf0) == 0xf0)
		{

			/* start out envelope at max */
			pFMVoice->oper[operIndex].envGain = 0x7fff;

			/* set envelope to decay state */
			pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay;
		}

		/* start envelope at zero and start in attack state */
		else
		{
			pFMVoice->oper[operIndex].envGain = 0;
			pFMVoice->oper[operIndex].envState = eFMEnvelopeStateAttack;
		}
	}

	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * FM_UpdateChannel()
 *----------------------------------------------------------------------------
 * Purpose:
 * Calculate and assign static channel parameters
 * These values only need to be updated if one of the controller values
 * for this channel changes.
 * Called when CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS flag is set.
 *
 * Inputs: 
 * nChannel - channel to update
 * psEASData - pointer to overall EAS data structure
 *			
 * Outputs:
 *
 * Side Effects:
 * - the given channel's static gain and static pitch are updated
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pVoiceMgr) standard interface, pVoiceMgr not used */
static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel)
{
	S_SYNTH_CHANNEL *pChannel;
	EAS_I32 temp;

	pChannel = &pSynth->channels[channel];
		
	/* convert CC7 volume controller to log scale */
	temp = fmControlTable[pChannel->volume];

	/* incorporate CC11 expression controller */
	temp += fmControlTable[pChannel->expression];

	/* saturate */
	pChannel->staticGain = (EAS_I16) max(temp,-32768);

	/* calculate pitch bend */
	/*lint -e{703} <avoid multiply for performance>*/
	temp = (((EAS_I32)(pChannel->pitchBend) << 2) - 32768);
	
	temp = FMUL_15x15(temp, pChannel->pitchBendSensitivity);

	/* include "magic number" compensation for sample rate and lookup table size */
	temp += MAGIC_NUMBER;

	/* if this is not a drum channel, then add in the per-channel tuning */
	if (!(pChannel->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL))
		temp += (pChannel->finePitch + (pChannel->coarsePitch * 100));

	/* save static pitch */
	pChannel->staticPitch = temp;

	/* Calculate LFO modulation depth */
	/* mod wheel to LFO depth */
	temp = FMUL_15x15(DEFAULT_LFO_MOD_WHEEL_TO_PITCH_CENTS,
	pChannel->modWheel << (NUM_EG1_FRAC_BITS -7));

	/* channel pressure to LFO depth */
	pChannel->lfoAmt = (EAS_I16) (temp +
	FMUL_15x15(DEFAULT_LFO_CHANNEL_PRESSURE_TO_PITCH_CENTS,
	pChannel->channelPressure << (NUM_EG1_FRAC_BITS -7)));

	/* clear update flag */
	pChannel->channelFlags &= ~CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS;
	return;
}

/*----------------------------------------------------------------------------
 * FM_UpdateLFO()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Calculate the LFO for the given voice
 *
 * Inputs:
 * pVoice - ptr to the voice whose LFO we want to update			
 * psEASData - pointer to overall EAS data structure - used for debug only
 *
 * Outputs:
 *
 * Side Effects:
 * - updates LFO values for the given voice
 *----------------------------------------------------------------------------
*/
static void FM_UpdateLFO (S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion)
{

	/* increment the LFO phase if the delay time has elapsed */
	if (!pFMVoice->lfoDelay)
	{
		/*lint -e{701} <use shift for performance> */
		pFMVoice->lfoPhase = pFMVoice->lfoPhase + (EAS_U16) (-fmControlTable[((15 - (pRegion->lfoFreqDelay >> 4)) << 3) + 4]);

		/* square wave LFO? */
		if (pRegion->region.keyGroupAndFlags & REGION_FLAG_SQUARE_WAVE)
			pFMVoice->lfoValue = (EAS_I16)(pFMVoice->lfoPhase & 0x8000 ? -32767 : 32767);

		/* trick to get a triangle wave out of a sawtooth */
		else
		{
			pFMVoice->lfoValue = (EAS_I16) (pFMVoice->lfoPhase << 1);
			/*lint -e{502} <shortcut to turn sawtooth into sine wave> */
			if ((pFMVoice->lfoPhase > 0x3fff) && (pFMVoice->lfoPhase < 0xC000))
				pFMVoice->lfoValue = ~pFMVoice->lfoValue;
		}
	}

	/* still in delay */
	else
		pFMVoice->lfoDelay--;

	return;
}

/*----------------------------------------------------------------------------
 * FM_UpdateEG()
 *----------------------------------------------------------------------------
 * Purpose:
 * Calculate the synthesis parameters for an operator to be used during
 * the next buffer
 *
 * Inputs:
 * pVoice - pointer to the voice being updated
 * psEASData - pointer to overall EAS data structure
 *
 * Outputs:
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_BOOL FM_UpdateEG (S_SYNTH_VOICE *pVoice, S_OPERATOR *pOper, const S_FM_OPER *pOperData, EAS_BOOL oneShot)
{
	EAS_U32 temp;
	EAS_BOOL done;

	/* set flag assuming the envelope is not done */
	done = EAS_FALSE;

	/* take appropriate action based on state */
	switch (pOper->envState)
	{

		case eFMEnvelopeStateAttack:

			/* the envelope is linear during the attack, so add the value */
			temp = pOper->envGain + fmAttackTable[pOperData->attackDecay >> 4];

			/* check for end of attack */
			if (temp >= 0x7fff)
			{
				pOper->envGain = 0x7fff;
				pOper->envState = eFMEnvelopeStateDecay;
			}
			else
				pOper->envGain = (EAS_U16) temp;
			break;

		case eFMEnvelopeStateDecay:

			/* decay is exponential, multiply by decay rate */
			pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate);

			/* check for sustain level reached */
			temp = (EAS_U32) (pOperData->sustain & 0xfc) << 7;
			if (pOper->envGain <= (EAS_U16) temp)
			{
				/* if this is a one-shot patch, go directly to release phase */
				if (oneShot)
				{
					pOper->envRate = FM_CalcEGRate(
					pVoice->note,
					fmReleaseTable[pOperData->velocityRelease & 0x0f],
					fmScaleTable[pOperData->egKeyScale >> 4]);
					pOper->envState = eFMEnvelopeStateRelease;
				}

				/* normal sustaining type */
				else
				{
					pOper->envGain = (EAS_U16) temp;
					pOper->envState = eFMEnvelopeStateSustain;
				}
			}
			break;

		case eFMEnvelopeStateSustain:
			pOper->envGain = (EAS_U16)((EAS_U16)(pOperData->sustain & 0xfc) << 7);
			break;

		case eFMEnvelopeStateRelease:

			/* release is exponential, multiply by release rate */
			pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate);

			/* fully released */
			if (pOper->envGain == 0)
			{
				pOper->envGain = 0;
				pOper->envState = eFMEnvelopeStateMuted;
				done = EAS_TRUE;
			}
			break;

		case eFMEnvelopeStateMuted:
			pOper->envGain = 0;
			done = EAS_TRUE;
			break;
		default:
			EAS_Report(_EAS_SEVERITY_FATAL,"Invalid operator state: %d", pOper->envState);
	} /* end switch (pOper->m_eState) */

	return done;
}

/*----------------------------------------------------------------------------
 * FM_UpdateDynamic()
 *----------------------------------------------------------------------------
 * Purpose:
 * Calculate the synthesis parameters for this voice that will be used to
 * synthesize the next buffer
 *
 * Inputs:
 * pVoice - pointer to the voice being updated
 * psEASData - pointer to overall EAS data structure
 *
 * Outputs:
 * Returns EAS_TRUE if voice will be fully ramped to zero at the end of
 * the next synthesized buffer.
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_BOOL FM_UpdateDynamic (S_SYNTH_VOICE *pVoice, S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion, S_SYNTH_CHANNEL *pChannel)
{
	EAS_I32 temp;
	EAS_I32 pitch;
	EAS_I32 lfoPitch;
	EAS_INT operIndex;
	EAS_BOOL done;

	/* increment LFO phase */
	FM_UpdateLFO(pFMVoice, pRegion);

	/* base pitch in cents */
	pitch = pVoice->note * 100;

	/* LFO amount includes LFO depth from programming + channel dynamics */
	temp = (fmScaleTable[pRegion->vibTrem >> 4] >> 1) + pChannel->lfoAmt;

	/* multiply by LFO output to get final pitch modulation */
	lfoPitch = FMUL_15x15(pFMVoice->lfoValue, temp);

	/* flag to indicate this voice is done */
	done = EAS_TRUE;

	/* iterate through operators to establish parameters */
	for (operIndex = 0; operIndex < 4; operIndex++)
	{
		EAS_BOOL bTemp;

		/* set base phase increment for each operator */
		temp = pRegion->oper[operIndex].tuning +
		pChannel->staticPitch;

		/* add vibrato effect unless it is disabled for this operator */
		if ((pRegion->oper[operIndex].flags & FM_OPER_FLAG_NO_VIBRATO) == 0)
			temp += lfoPitch;

		/* if note is monotonic, bias to MIDI note 60 */
		if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_MONOTONE)
			temp += 6000;
		else
			temp += pitch;
		pFMVoice->oper[operIndex].pitch = (EAS_I16) temp;

		/* calculate envelope, returns true if done */
		bTemp = FM_UpdateEG(pVoice, &pFMVoice->oper[operIndex], &pRegion->oper[operIndex], pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT);

		/* check if all output envelopes have completed */
		if (FM_SynthIsOutputOperator(pRegion, operIndex))
			done = done && bTemp;
	}

	return done;
}

/*----------------------------------------------------------------------------
 * FM_UpdateVoice()
 *----------------------------------------------------------------------------
 * Purpose:
 * Synthesize a block of samples for the given voice.
 *
 * Inputs:
 * psEASData - pointer to overall EAS data structure
 *
 * Outputs:
 * number of samples actually written to buffer
 *
 * Side Effects:
 * - samples are added to the presently free buffer
 *
 *----------------------------------------------------------------------------
*/
static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples)
{
	S_SYNTH_CHANNEL *pChannel;
	const S_FM_REGION *pRegion;
	S_FM_VOICE *pFMVoice;
	S_FM_VOICE_CONFIG vCfg;
	S_FM_VOICE_FRAME vFrame;
	EAS_I32 temp;
	EAS_INT oper;
	EAS_U16 voiceGainTarget;
	EAS_BOOL done;

	/* setup some pointers */
	pChannel = GetChannelPtr(pSynth, pVoice);
	pRegion = GetFMRegionPtr(pSynth, pVoice);
	pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);

	/* if the voice is just starting, get the voice configuration data */
	if (pVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET)
	{

		/* split architecture may limit the number of voice starts */
#ifdef MAX_VOICE_STARTS
		if (pVoiceMgr->numVoiceStarts == MAX_VOICE_STARTS)
			return EAS_FALSE;
		pVoiceMgr->numVoiceStarts++;
#endif	
		
		/* get initial parameters */
		vCfg.feedback = pRegion->feedback;
		vCfg.voiceGain = (EAS_U16) pFMVoice->voiceGain;

#if	(NUM_OUTPUT_CHANNELS == 2)
		vCfg.pan = pFMVoice->pan;
#endif

		/* get voice mode */
		vCfg.flags = pRegion->region.keyGroupAndFlags & 7;

		/* get operator parameters */
		for (oper = 0; oper < 4; oper++)
		{
			/* calculate initial gain */
			vCfg.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain);
			vCfg.outputGain[oper] = pFMVoice->oper[oper].outputGain;

			/* copy noise waveform flag */
			if (pRegion->oper[oper].flags & FM_OPER_FLAG_NOISE)
				vCfg.flags |= (EAS_U8) (FLAG_FM_ENG_VOICE_OP1_NOISE << oper);
		}

#ifdef FM_OFFBOARD		
		FM_ConfigVoice(voiceNum, &vCfg, pVoiceMgr->pFrameBuffer);
#else
		FM_ConfigVoice(voiceNum, &vCfg, NULL);
#endif
		
		/* clear startup flag */
		pVoice->voiceFlags &= ~VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET;
	}

	/* calculate new synthesis parameters */
	done = FM_UpdateDynamic(pVoice, pFMVoice, pRegion, pChannel);

	/* calculate LFO gain modulation */
	/*lint -e{702} <use shift for performance> */
	temp = ((fmScaleTable[pRegion->vibTrem & 0x0f] >> 1) * pFMVoice->lfoValue) >> FM_LFO_GAIN_SHIFT;

	/* include channel gain */
	temp += pChannel->staticGain;

	/* -32768 or lower is infinite attenuation */
	if (temp < -32767)
		voiceGainTarget = 0;

	/* translate to linear gain multiplier */
	else
		voiceGainTarget = EAS_LogToLinear16(temp);

	/* include synth master volume */
	voiceGainTarget = (EAS_U16) FMUL_15x15(voiceGainTarget, pSynth->masterVolume);

	/* save target values for this frame */
	vFrame.voiceGain = voiceGainTarget;

	/* assume voice output is zero */
	pVoice->gain = 0;
	
	/* save operator targets for this frame */
	for (oper = 0; oper < 4; oper++)
	{
		vFrame.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain);
		vFrame.pitch[oper] = pFMVoice->oper[oper].pitch;

		/* use the highest output envelope level as the gain for the voice stealing algorithm */
		if (FM_SynthIsOutputOperator(pRegion, oper))
			pVoice->gain = max(pVoice->gain, (EAS_I16) vFrame.gain[oper]);
	}

	/* consider voice gain multiplier in calculating gain for stealing algorithm */
	pVoice->gain = (EAS_I16) FMUL_15x15(voiceGainTarget, pVoice->gain);

	/* synthesize samples */
#ifdef FM_OFFBOARD	
	FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, pVoiceMgr->pFrameBuffer);
#else
	FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->operOutputBuffer, pMixBuffer, NULL);
#endif

	return done;
}

