/*----------------------------------------------------------------------------
 *
 * 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_options.h"
#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"
#include <string.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 */
    // if it is already in release state, it still dies after shutdown time
    pWTVoice->eg1State = eEnvelopeStateRelease;
    pWTVoice->eg1Increment = -pDLSArt->eg1ShutdownTime;
    
    pWTVoice->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 dB to linear */
    /*lint -e{732} eg1Value is never negative */
    /*lint -e{703} use shift for performance */
    if (pWTVoice->eg1State == eEnvelopeStateAttack) {
        pWTVoice->eg1Value = (EAS_I16) EAS_LogToLinear16((DLS_GAIN_FACTOR * 
            (pWTVoice->eg1Value - SYNTH_FULL_SCALE_EG1_GAIN) * 960 / (1 << NUM_EG1_FRAC_BITS)
        ) >> DLS_GAIN_FACTOR_FRAC_BITS); // full scale: 0dB, 0: -96dB
    }

    /* release EG1 */
    if (pWTVoice->eg1State != eEnvelopeStateRelease && 
        pWTVoice->eg1State != eEnvelopeStateMuted)
    {
        pWTVoice->eg1State = eEnvelopeStateRelease;
        pWTVoice->eg1Increment = -pDLSArt->eg1.releaseTime;
    }

    /* release EG2 */
    if (pWTVoice->eg2State != eEnvelopeStateRelease && 
        pWTVoice->eg2State != eEnvelopeStateMuted)
    {
        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);

    /* convert to linear gain including EG1 */
    // DAHDSR
    // only attack is linear gain, others are in dB
    if (pWTVoice->eg1State == eEnvelopeStateAttack)
    {
        gain = (DLS_GAIN_FACTOR * gain) >> DLS_GAIN_FACTOR_FRAC_BITS;
        gain = EAS_LogToLinear16(gain);
        gain = FMUL_15x15(gain, pWTVoice->eg1Value);
    }
    else
    {
        gain -= (SYNTH_FULL_SCALE_EG1_GAIN - pWTVoice->eg1Value) * 960 / (1 << NUM_EG1_FRAC_BITS); // full scale: 0dB, 0: -96dB
        gain = (DLS_GAIN_FACTOR * gain) >> DLS_GAIN_FACTOR_FRAC_BITS;
        gain = EAS_LogToLinear16(gain);
    }

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

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

    if (gain > SYNTH_FULL_SCALE_EG1_GAIN) {
        return gain;
    }
    if (gain < 0) {
        return 0;
    }

    /* 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)
    {
#ifdef _FLOAT_DCF
        pIntFrame->frame.b02 = 0.0f;
#else
        pIntFrame->frame.k = 0;
#endif
        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;

    if (cutoff == 13500) // bypass filter
    {
#ifdef _FLOAT_DCF
        pIntFrame->frame.b02 = 0.0f;
#else
        pIntFrame->frame.k = 0;
#endif
        return;
    }

    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 */
    memset(&pWTVoice->filter, 0, sizeof(S_FILTER_CONTROL));

    /* initialize the oscillator */
#if defined (_8_BIT_SAMPLES)
    pWTVoice->phaseAccum = pSynth->pDLS->pDLSSamples + pSynth->pDLS->pDLSSampleOffsets[pDLSRegion->wtRegion.waveIndex];
#else //_16_BIT_SAMPLES
    pWTVoice->phaseAccum = pSynth->pDLS->pDLSSamples + pSynth->pDLS->pDLSSampleOffsets[pDLSRegion->wtRegion.waveIndex] / 2;
#endif
    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
    {
#if defined (_8_BIT_SAMPLES)
       pWTVoice->loopStart = pWTVoice->loopEnd = pWTVoice->phaseAccum
                + pSynth->pDLS->pDLSSampleLen[pDLSRegion->wtRegion.waveIndex] - 1;
#else //_16_BIT_SAMPLES
        pWTVoice->loopStart = pWTVoice->loopEnd = pWTVoice->phaseAccum
                + pSynth->pDLS->pDLSSampleLen[pDLSRegion->wtRegion.waveIndex] / 2 - 1;
#endif
    }

    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);

    if (intFrame.numSamples < BUFFER_SIZE_IN_MONO_SAMPLES)
        memset(pMixBuffer + intFrame.numSamples * NUM_OUTPUT_CHANNELS, 0, (BUFFER_SIZE_IN_MONO_SAMPLES - intFrame.numSamples) * NUM_OUTPUT_CHANNELS * sizeof(EAS_I32));

    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:
            // init -> delay
            *pState = eEnvelopeStateDelay;
            *pValue = 0;
            *pIncrement = pEnvParams->delayTime;
            if (*pIncrement != 0)
                return;
            /*lint -e{825} falls through to next case */

        case eEnvelopeStateDelay:
            if (*pIncrement) // remaining delay time
            {
                *pIncrement -= 1;
                return;
            }
            // delay -> attack
            /* 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) / 128;
                *pIncrement = DLSConvertRate(temp);
                if (*pIncrement == 0) {
                    *pIncrement = 1; // ensure no infinite attack
                }
                return;
            }

            // no attack, fall through to hold
            *pValue = SYNTH_FULL_SCALE_EG1_GAIN;
            /*lint -e{825} falls through to next case */

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

            // attack -> hold
            /* 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) / 128;
                *pIncrement = DLSConvertDelay(temp);
                if (*pIncrement != 0) {
                    return;
                }
            }

            // no hold
            *pIncrement = 0;
            /*lint -e{825} falls through to next case */

        case eEnvelopeStateHold:
            if (*pIncrement) // remaining hold time
            {
                *pIncrement -= 1;
                return;
            }

            // hold -> decay
            /* calculate decay rate */
            *pState = eEnvelopeStateDecay;
            if (pEnvParams->decayTime != ZERO_TIME_IN_CENTS)
            {
                temp = pEnvParams->decayTime + (pEnvParams->keyNumToDecay * pVoice->note) / 128;
                temp = DLSConvertDelay(temp);
                if (temp == 0) {
                    // very small decay time, regard it as no decay
                    goto nodecay;
                }
                *pIncrement = SYNTH_FULL_SCALE_EG1_GAIN / temp;
                if (*pIncrement == 0) {
                    *pIncrement = 1; // ensure no infinite decay
                }
                return;
            }
nodecay:
            // no decay, fall to sustain
            *pValue = pEnvParams->sustainLevel;
            /*lint -e{825} falls through to next case */

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

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

        case eEnvelopeStateSustain:
            return;

        case eEnvelopeStateRelease:
            // calculate release rate
            if (*pIncrement == 0) { // this is release time = 0
                *pState = eEnvelopeStateMuted; // no release, mute
                *pValue = 0;
                return;
            } else if (*pIncrement < 0) { // this is release time
                temp = *pValue / (-*pIncrement);
                if (temp <= 0) {
                    temp = 1; // ensure no infinite release
                }
                *pIncrement = (EAS_I16) temp;
            }

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

        case eEnvelopeStateMuted:
            *pValue = 0;
            return;

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

