/*----------------------------------------------------------------------------
 *
 * File:
 * eas_dlssynth.c
 *
 * Contents and purpose:
 * Implements the Mobile DLS synthesizer.
 *
 * Copyright Sonic Network Inc. 2006

 * 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_data.h"
#include "eas_report.h"
#include "eas_host.h"
#include "eas_math.h"
#include "eas_synth_protos.h"
#include "eas_wtsynth.h"
#include "eas_pan.h"
#include "eas_mdls.h"
#include "eas_dlssynth.h"

#ifdef _METRICS_ENABLED
#include "eas_perf.h"
#endif

static void DLS_UpdateEnvelope (S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel,  const S_DLS_ENVELOPE *pEnvParams, EAS_I16 *pValue, EAS_I16 *pIncrement, EAS_U8 *pState);

/*----------------------------------------------------------------------------
 * DLS_MuteVoice()
 *----------------------------------------------------------------------------
 * Mute the voice using shutdown time from the DLS articulation data
 *----------------------------------------------------------------------------
*/
void DLS_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
{
	S_WT_VOICE *pWTVoice;
	const S_DLS_ARTICULATION *pDLSArt;
	
	pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
	pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];
			
	/* clear deferred action flags */
	pVoice->voiceFlags &=
		~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF |
		VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF |
		VOICE_FLAG_DEFER_MUTE);
		
	/* set the envelope state */
    pVoiceMgr->wtVoices[voiceNum].eg1State = eEnvelopeStateRelease;
    pWTVoice->eg1Increment = pDLSArt->eg1ShutdownTime;
    pVoiceMgr->wtVoices[voiceNum].eg2State = eEnvelopeStateRelease;
    pWTVoice->eg2Increment = pDLSArt->eg2.releaseTime;
}

/*----------------------------------------------------------------------------
 * DLS_ReleaseVoice()
 *----------------------------------------------------------------------------
 * Release the selected voice.
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pVoice) standard API, pVoice may be used by other synthesizers */
void DLS_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
{
	S_WT_VOICE *pWTVoice;
	const S_DLS_ARTICULATION *pDLSArt;
	
	pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
	pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];

	/* if still in attack phase, convert units to log */
	/*lint -e{732} eg1Value is never negative */
	/*lint -e{703} use shift for performance */
	if (pWTVoice->eg1State == eEnvelopeStateAttack)
		pWTVoice->eg1Value = (EAS_I16) ((EAS_flog2(pWTVoice->eg1Value) << 1) + 2048);
			
    /* release EG1 */
    pWTVoice->eg1State = eEnvelopeStateRelease;
    pWTVoice->eg1Increment = pDLSArt->eg1.releaseTime;

    /* release EG2 */
    pWTVoice->eg2State = eEnvelopeStateRelease;
    pWTVoice->eg2Increment = pDLSArt->eg2.releaseTime;
}

/*----------------------------------------------------------------------------
 * DLS_SustainPedal()
 *----------------------------------------------------------------------------
 * The sustain pedal was just depressed. If the voice is still
 * above the sustain level, catch the voice and continue holding.
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pChannel) pChannel reserved for future use */
void DLS_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum)
{
	S_WT_VOICE *pWTVoice;
	const S_DLS_ARTICULATION *pDLSArt;
	
	pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
	pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];

	/* don't catch the voice if below the sustain level */
	if (pWTVoice->eg1Value < pDLSArt->eg1.sustainLevel)
		return;
	
    /* defer releasing this note until the damper pedal is off */
    pWTVoice->eg1State = eEnvelopeStateDecay;
	pVoice->voiceState = eVoiceStatePlay;
    pVoice->voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF;

#ifdef _DEBUG_SYNTH
    { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "DLS_SustainPedal: defer note off because sustain pedal is on\n"); */ }
#endif
}

/*----------------------------------------------------------------------------
 * DLS_UpdatePhaseInc()
 *----------------------------------------------------------------------------
 * Calculate the oscillator phase increment for the next frame
 *----------------------------------------------------------------------------
*/
static EAS_I32 DLS_UpdatePhaseInc (S_WT_VOICE *pWTVoice, const S_DLS_ARTICULATION *pDLSArt, S_SYNTH_CHANNEL *pChannel, EAS_I32 pitchCents)
{
	EAS_I32 temp;

	/* start with base mod LFO modulation */
	temp = pDLSArt->modLFOToPitch;

	/* add mod wheel effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->modLFOCC1ToPitch * pChannel->modWheel) >> 7);

	/* add channel pressure effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->modLFOChanPressToPitch * pChannel->channelPressure) >> 7);

	/* add total mod LFO effect */
	pitchCents += FMUL_15x15(temp, pWTVoice->modLFO.lfoValue);

	/* start with base vib LFO modulation */
	temp = pDLSArt->vibLFOToPitch;

	/* add mod wheel effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->vibLFOCC1ToPitch * pChannel->modWheel) >> 7);

	/* add channel pressure effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->vibLFOChanPressToPitch * pChannel->channelPressure) >> 7);

	/* add total vibrato LFO effect */
	pitchCents += FMUL_15x15(temp, pWTVoice->vibLFO.lfoValue);

	/* add EG2 effect */
	pitchCents += FMUL_15x15(pDLSArt->eg2ToPitch, pWTVoice->eg2Value);

	/* convert from cents to linear phase increment */
	return EAS_Calculate2toX(pitchCents);
}

/*----------------------------------------------------------------------------
 * DLS_UpdateGain()
 *----------------------------------------------------------------------------
 * Calculate the gain for the next frame
 *----------------------------------------------------------------------------
*/
static EAS_I32 DLS_UpdateGain (S_WT_VOICE *pWTVoice, const S_DLS_ARTICULATION *pDLSArt, S_SYNTH_CHANNEL *pChannel, EAS_I32 gain, EAS_U8 velocity)
{
	EAS_I32 temp;

	/* start with base mod LFO modulation */
	temp = pDLSArt->modLFOToGain;

	/* add mod wheel effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->modLFOCC1ToGain * pChannel->modWheel) >> 7);

	/* add channel pressure effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->modLFOChanPressToGain * pChannel->channelPressure) >> 7);

	/* add total mod LFO effect */
	gain += FMUL_15x15(temp, pWTVoice->modLFO.lfoValue);
	if (gain > 0)
		gain = 0;

	/* convert to linear gain including EG1 */
	if (pWTVoice->eg1State != eEnvelopeStateAttack)
	{
		gain = (DLS_GAIN_FACTOR * gain) >> DLS_GAIN_SHIFT;
		/*lint -e{702} use shift for performance */
#if 1
		gain += (pWTVoice->eg1Value - 32767) >> 1;
		gain = EAS_LogToLinear16(gain);
#else
		gain = EAS_LogToLinear16(gain);
		temp = EAS_LogToLinear16((pWTVoice->eg1Value - 32767) >> 1);
		gain = FMUL_15x15(gain, temp);
#endif
	}
	else
	{
		gain = (DLS_GAIN_FACTOR * gain) >> DLS_GAIN_SHIFT;
		gain = EAS_LogToLinear16(gain);
		gain = FMUL_15x15(gain, pWTVoice->eg1Value);
	}

	/* include MIDI channel gain */
	gain = FMUL_15x15(gain, pChannel->staticGain);

	/* include velocity */
	if (pDLSArt->filterQandFlags & FLAG_DLS_VELOCITY_SENSITIVE)
	{
		temp = velocity << 8;
		temp = FMUL_15x15(temp, temp);
		gain = FMUL_15x15(gain, temp);
	}

	/* return gain */
	return gain;
}

/*----------------------------------------------------------------------------
 * DLS_UpdateFilter()
 *----------------------------------------------------------------------------
 * Update the Filter parameters
 *----------------------------------------------------------------------------
*/
static void DLS_UpdateFilter (S_SYNTH_VOICE *pVoice, S_WT_VOICE *pWTVoice, S_WT_INT_FRAME *pIntFrame, S_SYNTH_CHANNEL *pChannel, const S_DLS_ARTICULATION *pDLSArt)
{
	EAS_I32 cutoff;
	EAS_I32 temp;

	/* no need to calculate filter coefficients if it is bypassed */
	if (pDLSArt->filterCutoff == DEFAULT_DLS_FILTER_CUTOFF_FREQUENCY)
	{
		pIntFrame->frame.k = 0;
		return;
	}

	/* start with base cutoff frequency */
	cutoff = pDLSArt->filterCutoff;
	
	/* get base mod LFO modulation */
	temp = pDLSArt->modLFOToFc;

	/* add mod wheel effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->modLFOCC1ToFc * pChannel->modWheel) >> 7);

	/* add channel pressure effect */
	/*lint -e{702} use shift for performance */
	temp += ((pDLSArt->modLFOChanPressToFc* pChannel->channelPressure) >> 7);

	/* add total mod LFO effect */
	cutoff += FMUL_15x15(temp, pWTVoice->modLFO.lfoValue);

	/* add EG2 effect */
	cutoff += FMUL_15x15(pWTVoice->eg2Value, pDLSArt->eg2ToFc);

	/* add velocity effect */
	/*lint -e{702} use shift for performance */
	cutoff += (pVoice->velocity * pDLSArt->velToFc) >> 7;

	/* add velocity effect */
	/*lint -e{702} use shift for performance */
	cutoff += (pVoice->note * pDLSArt->keyNumToFc) >> 7;

	/* subtract the A5 offset and the sampling frequency */
	cutoff -= FILTER_CUTOFF_FREQ_ADJUST + A5_PITCH_OFFSET_IN_CENTS;

	/* limit the cutoff frequency */
	if (cutoff > FILTER_CUTOFF_MAX_PITCH_CENTS) 
		cutoff = FILTER_CUTOFF_MAX_PITCH_CENTS;
	else if (cutoff < FILTER_CUTOFF_MIN_PITCH_CENTS) 
		cutoff = FILTER_CUTOFF_MIN_PITCH_CENTS;

	WT_SetFilterCoeffs(pIntFrame, cutoff, pDLSArt->filterQandFlags & FILTER_Q_MASK);
}

/*----------------------------------------------------------------------------
 * DLS_StartVoice()
 *----------------------------------------------------------------------------
 * Start up a DLS voice
 *----------------------------------------------------------------------------
*/
EAS_RESULT DLS_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex)
{
	S_WT_VOICE *pWTVoice;
	const S_DLS_REGION *pDLSRegion;
	const S_DLS_ARTICULATION *pDLSArt;
	S_SYNTH_CHANNEL *pChannel;
	
#ifdef _DEBUG_SYNTH
	{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "DLS_StartVoice: Voice %ld; Region %d\n", (EAS_I32) (pVoice - pVoiceMgr->voices), regionIndex); */ }
#endif

	pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
	pChannel = &pSynth->channels[pVoice->channel & 15];
	pDLSRegion = &pSynth->pDLS->pDLSRegions[regionIndex & REGION_INDEX_MASK];
	pWTVoice->artIndex = pDLSRegion->wtRegion.artIndex;
	pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];

	/* initialize the envelopes */
	pWTVoice->eg1State = eEnvelopeStateInit;
	DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg1, &pWTVoice->eg1Value, &pWTVoice->eg1Increment, &pWTVoice->eg1State);
	pWTVoice->eg2State = eEnvelopeStateInit;
	DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg2, &pWTVoice->eg2Value, &pWTVoice->eg2Increment, &pWTVoice->eg2State);

	/* initialize the LFOs */
	pWTVoice->modLFO.lfoValue = 0;
	pWTVoice->modLFO.lfoPhase = pDLSArt->modLFO.lfoDelay;
	pWTVoice->vibLFO.lfoValue = 0;
	pWTVoice->vibLFO.lfoPhase = pDLSArt->vibLFO.lfoDelay;

	/* initalize the envelopes and calculate initial gain */
	DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg1, &pWTVoice->eg1Value, &pWTVoice->eg1Increment, &pWTVoice->eg1State);
	DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg2, &pWTVoice->eg2Value, &pWTVoice->eg2Increment, &pWTVoice->eg2State);
	pVoice->gain = (EAS_I16) DLS_UpdateGain(pWTVoice, pDLSArt, pChannel, pDLSRegion->wtRegion.gain, pVoice->velocity);

#if (NUM_OUTPUT_CHANNELS == 2)
	EAS_CalcPanControl((EAS_INT) pChannel->pan - 64 + (EAS_INT) pDLSArt->pan, &pWTVoice->gainLeft, &pWTVoice->gainRight);
#endif	

	/* initialize the filter states */
	pWTVoice->filter.z1 = 0;
	pWTVoice->filter.z2 = 0;

	/* initialize the oscillator */
	pWTVoice->phaseAccum = (EAS_U32) pSynth->pDLS->pDLSSamples + pSynth->pDLS->pDLSSampleOffsets[pDLSRegion->wtRegion.waveIndex];
	if (pDLSRegion->wtRegion.region.keyGroupAndFlags & REGION_FLAG_IS_LOOPED)
	{
		pWTVoice->loopStart = pWTVoice->phaseAccum + pDLSRegion->wtRegion.loopStart;
		pWTVoice->loopEnd = pWTVoice->phaseAccum + pDLSRegion->wtRegion.loopEnd - 1;
	}
	else
		pWTVoice->loopStart = pWTVoice->loopEnd = pWTVoice->phaseAccum + pSynth->pDLS->pDLSSampleLen[pDLSRegion->wtRegion.waveIndex] - 1;

	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * DLS_UpdateVoice()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Synthesize a block of samples for the given voice.
 * Use linear interpolation.
 *
 * Inputs: 
 * pEASData - pointer to overall EAS data structure
 *			
 * Outputs:
 * number of samples actually written to buffer
 *
 * Side Effects:
 * - samples are added to the presently free buffer
 *
 *----------------------------------------------------------------------------
*/
EAS_BOOL DLS_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples)
{
	S_WT_VOICE *pWTVoice;
	S_SYNTH_CHANNEL *pChannel;
	const S_DLS_REGION *pDLSRegion;
	const S_DLS_ARTICULATION *pDLSArt;
	S_WT_INT_FRAME intFrame;
	EAS_I32 temp;
	EAS_BOOL done = EAS_FALSE;

	/* establish pointers to critical data */
	pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
	pDLSRegion = &pSynth->pDLS->pDLSRegions[pVoice->regionIndex & REGION_INDEX_MASK];
	pChannel = &pSynth->channels[pVoice->channel & 15];
	pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];

	/* update the envelopes */
	DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg1, &pWTVoice->eg1Value, &pWTVoice->eg1Increment, &pWTVoice->eg1State);
	DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg2, &pWTVoice->eg2Value, &pWTVoice->eg2Increment, &pWTVoice->eg2State);

	/* update the LFOs using the EAS synth function */
	WT_UpdateLFO(&pWTVoice->modLFO, pDLSArt->modLFO.lfoFreq);
	WT_UpdateLFO(&pWTVoice->vibLFO, pDLSArt->vibLFO.lfoFreq);

	/* calculate base frequency */
	temp = pDLSArt->tuning + pChannel->staticPitch + pDLSRegion->wtRegion.tuning +
		(((EAS_I32) pVoice->note * (EAS_I32) pDLSArt->keyNumToPitch) >> 7);

	/* don't transpose rhythm channel */
	if ((pChannel ->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL) == 0)
		temp += pSynth->globalTranspose * 100;

	/* calculate phase increment including modulation effects */	
	intFrame.frame.phaseIncrement = DLS_UpdatePhaseInc(pWTVoice, pDLSArt, pChannel, temp);

	/* calculate gain including modulation effects */
	intFrame.frame.gainTarget = DLS_UpdateGain(pWTVoice, pDLSArt, pChannel, pDLSRegion->wtRegion.gain, pVoice->velocity);
	intFrame.prevGain = pVoice->gain;
	
	DLS_UpdateFilter(pVoice, pWTVoice, &intFrame, pChannel, pDLSArt);

	/* call into engine to generate samples */
	intFrame.pAudioBuffer = pVoiceMgr->voiceBuffer;
	intFrame.pMixBuffer = pMixBuffer;
	intFrame.numSamples = numSamples;
	if (numSamples < 0)
		return EAS_FALSE;

	/* check for end of sample */
	if ((pWTVoice->loopStart != WT_NOISE_GENERATOR) && (pWTVoice->loopStart == pWTVoice->loopEnd))
		done = WT_CheckSampleEnd(pWTVoice, &intFrame, EAS_FALSE);

	WT_ProcessVoice(pWTVoice, &intFrame);

	/* clear flag */
	pVoice->voiceFlags &= ~VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET;

	/* if the update interval has elapsed, then force the current gain to the next
	 * gain since we never actually reach the next gain when ramping -- we just get
	 * very close to the target gain.
	 */
	pVoice->gain = (EAS_I16) intFrame.frame.gainTarget;

    /* if voice has finished, set flag for voice manager */
	if ((pVoice->voiceState != eVoiceStateStolen) && (pWTVoice->eg1State == eEnvelopeStateMuted))
		done = EAS_TRUE;

	return done;
} 

/*----------------------------------------------------------------------------
 * DLS_UpdateEnvelope()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Synthesize a block of samples for the given voice.
 * Use linear interpolation.
 *
 * Inputs: 
 * pEASData - pointer to overall EAS data structure
 *			
 * Outputs:
 * number of samples actually written to buffer
 *
 * Side Effects:
 * - samples are added to the presently free buffer
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pChannel) pChannel not used in this instance */
static void DLS_UpdateEnvelope (S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel,  const S_DLS_ENVELOPE *pEnvParams, EAS_I16 *pValue, EAS_I16 *pIncrement, EAS_U8 *pState)
{
	EAS_I32 temp;

	switch (*pState)
	{
		/* initial state */
		case eEnvelopeStateInit:
			*pState = eEnvelopeStateDelay;
			*pValue = 0;
			*pIncrement = pEnvParams->delayTime;
			if (*pIncrement != 0)
				return;
			/*lint -e{825} falls through to next case */

		case eEnvelopeStateDelay:
			if (*pIncrement)
			{
				*pIncrement = *pIncrement - 1;
				return;
			}

			/* calculate attack rate */
			*pState = eEnvelopeStateAttack;
			if (pEnvParams->attackTime != ZERO_TIME_IN_CENTS)
			{
				/*lint -e{702} use shift for performance */
				temp = pEnvParams->attackTime + ((pEnvParams->velToAttack * pVoice->velocity) >> 7);
				*pIncrement = ConvertRate(temp);
				return;
			}

			*pValue = SYNTH_FULL_SCALE_EG1_GAIN;
			/*lint -e{825} falls through to next case */

		case eEnvelopeStateAttack:
			if (*pValue < SYNTH_FULL_SCALE_EG1_GAIN)
			{
				temp = *pValue + *pIncrement;
				*pValue = (EAS_I16) (temp < SYNTH_FULL_SCALE_EG1_GAIN ? temp : SYNTH_FULL_SCALE_EG1_GAIN);
				return;
			}

			/* calculate hold time */
			*pState = eEnvelopeStateHold;
			if (pEnvParams->holdTime != ZERO_TIME_IN_CENTS)
			{
				/*lint -e{702} use shift for performance */
				temp = pEnvParams->holdTime + ((pEnvParams->keyNumToHold * pVoice->note) >> 7);
				*pIncrement = ConvertDelay(temp);
				return;
			}
			else
				*pIncrement = 0;
			/*lint -e{825} falls through to next case */

		case eEnvelopeStateHold:
			if (*pIncrement)
			{
				*pIncrement = *pIncrement - 1;
				return;
			}

			/* calculate decay rate */
			*pState = eEnvelopeStateDecay;
			if (pEnvParams->decayTime != ZERO_TIME_IN_CENTS)
			{
				/*lint -e{702} use shift for performance */
				temp = pEnvParams->decayTime + ((pEnvParams->keyNumToDecay * pVoice->note) >> 7);
				*pIncrement = ConvertRate(temp);
				return;
			}

//			*pValue = pEnvParams->sustainLevel;
			/*lint -e{825} falls through to next case */

		case eEnvelopeStateDecay:
			if (*pValue > pEnvParams->sustainLevel)
			{
				temp = *pValue - *pIncrement;
				*pValue = (EAS_I16) (temp > pEnvParams->sustainLevel ? temp : pEnvParams->sustainLevel);
				return;
			}

			*pState = eEnvelopeStateSustain;
			*pValue = pEnvParams->sustainLevel;
			/*lint -e{825} falls through to next case */

		case eEnvelopeStateSustain:
			return;

		case eEnvelopeStateRelease:
			temp = *pValue - *pIncrement;
			if (temp <= 0)
			{
				*pState = eEnvelopeStateMuted;
				*pValue = 0;
			}
			else
				*pValue = (EAS_I16) temp;
			break;

		case eEnvelopeStateMuted:
			*pValue = 0;
			return;

		default:
			{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Envelope in invalid state %d\n", *pState); */ }
			break;
	}
}