/* FluidSynth - A Software Synthesizer
 *
 * Copyright (C) 2003  Peter Hanappe and others.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "fluid_rvoice.h"
#include "fluid_conv.h"
#include "fluid_sys.h"


static void fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks);

/**
 * @return -1 if voice is quiet, 0 if voice has finished, 1 otherwise
 */
static FLUID_INLINE int
fluid_rvoice_calc_amp(fluid_rvoice_t *voice)
{
    fluid_real_t target_amp;	/* target amplitude */

    if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVDELAY)
    {
        return -1;    /* The volume amplitude is in hold phase. No sound is produced. */
    }

    if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVATTACK)
    {
        /* the envelope is in the attack section: ramp linearly to max value.
         * A positive modlfo_to_vol should increase volume (negative attenuation).
         */
        target_amp = fluid_cb2amp(voice->dsp.attenuation)
                     * fluid_cb2amp(fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol)
                     * fluid_adsr_env_get_val(&voice->envlfo.volenv);
    }
    else
    {
        fluid_real_t amplitude_that_reaches_noise_floor;
        fluid_real_t amp_max;

        target_amp = fluid_cb2amp(voice->dsp.attenuation)
                     * fluid_cb2amp(FLUID_PEAK_ATTENUATION * (1.0f - fluid_adsr_env_get_val(&voice->envlfo.volenv))
                                    + fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol);

        /* We turn off a voice, if the volume has dropped low enough. */

        /* A voice can be turned off, when an estimate for the volume
         * (upper bound) falls below that volume, that will drop the
         * sample below the noise floor.
         */

        /* If the loop amplitude is known, we can use it if the voice loop is within
         * the sample loop
         */

        /* Is the playing pointer already in the loop? */
        if(voice->dsp.has_looped)
        {
            amplitude_that_reaches_noise_floor = voice->dsp.amplitude_that_reaches_noise_floor_loop;
        }
        else
        {
            amplitude_that_reaches_noise_floor = voice->dsp.amplitude_that_reaches_noise_floor_nonloop;
        }

        /* voice->attenuation_min is a lower boundary for the attenuation
         * now and in the future (possibly 0 in the worst case).  Now the
         * amplitude of sample and volenv cannot exceed amp_max (since
         * volenv_val can only drop):
         */

        amp_max = fluid_cb2amp(voice->dsp.min_attenuation_cB) *
                  fluid_adsr_env_get_val(&voice->envlfo.volenv);

        /* And if amp_max is already smaller than the known amplitude,
         * which will attenuate the sample below the noise floor, then we
         * can safely turn off the voice. Duh. */
        if(amp_max < amplitude_that_reaches_noise_floor)
        {
            return 0;
        }
    }

    /* Volume increment to go from voice->amp to target_amp in FLUID_BUFSIZE steps */
    voice->resonant_filter.amp_incr = (target_amp - voice->resonant_filter.amp) / FLUID_BUFSIZE;

    fluid_check_fpe("voice_write amplitude calculation");

    /* no volume and not changing? - No need to process */
    if ((voice->resonant_filter.amp == 0.0f) && (voice->resonant_filter.amp_incr == 0.0f))
    {
        return -1;
    }

    return 1;
}


/* these should be the absolute minimum that FluidSynth can deal with */
#define FLUID_MIN_LOOP_SIZE 2
#define FLUID_MIN_LOOP_PAD 0

#define FLUID_SAMPLESANITY_CHECK (1 << 0)
#define FLUID_SAMPLESANITY_STARTUP (1 << 1)

/* Purpose:
 *
 * Make sure, that sample start / end point and loop points are in
 * proper order. When starting up, calculate the initial phase.
 * TODO: Investigate whether this can be moved from rvoice to voice.
 */
static void
fluid_rvoice_check_sample_sanity(fluid_rvoice_t *voice)
{
    int min_index_nonloop = (int) voice->dsp.sample->start;
    int max_index_nonloop = (int) voice->dsp.sample->end;

    /* make sure we have enough samples surrounding the loop */
    int min_index_loop = (int) voice->dsp.sample->start + FLUID_MIN_LOOP_PAD;
    int max_index_loop = (int) voice->dsp.sample->end - FLUID_MIN_LOOP_PAD + 1;	/* 'end' is last valid sample, loopend can be + 1 */
    fluid_check_fpe("voice_check_sample_sanity start");

#if 0
    printf("Sample from %i to %i\n", voice->dsp.sample->start, voice->dsp.sample->end);
    printf("Sample loop from %i %i\n", voice->dsp.sample->loopstart, voice->dsp.sample->loopend);
    printf("Playback from %i to %i\n", voice->dsp.start, voice->dsp.end);
    printf("Playback loop from %i to %i\n", voice->dsp.loopstart, voice->dsp.loopend);
#endif

    /* Keep the start point within the sample data */
    if(voice->dsp.start < min_index_nonloop)
    {
        voice->dsp.start = min_index_nonloop;
    }
    else if(voice->dsp.start > max_index_nonloop)
    {
        voice->dsp.start = max_index_nonloop;
    }

    /* Keep the end point within the sample data */
    if(voice->dsp.end < min_index_nonloop)
    {
        voice->dsp.end = min_index_nonloop;
    }
    else if(voice->dsp.end > max_index_nonloop)
    {
        voice->dsp.end = max_index_nonloop;
    }

    /* Keep start and end point in the right order */
    if(voice->dsp.start > voice->dsp.end)
    {
        int temp = voice->dsp.start;
        voice->dsp.start = voice->dsp.end;
        voice->dsp.end = temp;
        /*FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Changing order of start / end points!"); */
    }

    /* Zero length? */
    if(voice->dsp.start == voice->dsp.end)
    {
        fluid_rvoice_voiceoff(voice, NULL);
        return;
    }

    if((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE)
            || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE))
    {
        /* Keep the loop start point within the sample data */
        if(voice->dsp.loopstart < min_index_loop)
        {
            voice->dsp.loopstart = min_index_loop;
        }
        else if(voice->dsp.loopstart > max_index_loop)
        {
            voice->dsp.loopstart = max_index_loop;
        }

        /* Keep the loop end point within the sample data */
        if(voice->dsp.loopend < min_index_loop)
        {
            voice->dsp.loopend = min_index_loop;
        }
        else if(voice->dsp.loopend > max_index_loop)
        {
            voice->dsp.loopend = max_index_loop;
        }

        /* Keep loop start and end point in the right order */
        if(voice->dsp.loopstart > voice->dsp.loopend)
        {
            int temp = voice->dsp.loopstart;
            voice->dsp.loopstart = voice->dsp.loopend;
            voice->dsp.loopend = temp;
            /*FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Changing order of loop points!"); */
        }

        /* Loop too short? Then don't loop. */
        if(voice->dsp.loopend < voice->dsp.loopstart + FLUID_MIN_LOOP_SIZE)
        {
            voice->dsp.samplemode = FLUID_UNLOOPED;
        }

        /* The loop points may have changed. Obtain a new estimate for the loop volume. */
        /* Is the voice loop within the sample loop? */
        if((int)voice->dsp.loopstart >= (int)voice->dsp.sample->loopstart
                && (int)voice->dsp.loopend <= (int)voice->dsp.sample->loopend)
        {
            /* Is there a valid peak amplitude available for the loop, and can we use it? */
            if(voice->dsp.sample->amplitude_that_reaches_noise_floor_is_valid && voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)
            {
                voice->dsp.amplitude_that_reaches_noise_floor_loop = voice->dsp.sample->amplitude_that_reaches_noise_floor / voice->dsp.synth_gain;
            }
            else
            {
                /* Worst case */
                voice->dsp.amplitude_that_reaches_noise_floor_loop = voice->dsp.amplitude_that_reaches_noise_floor_nonloop;
            };
        };

    } /* if sample mode is looped */

    /* Run startup specific code (only once, when the voice is started) */
    if(voice->dsp.check_sample_sanity_flag & FLUID_SAMPLESANITY_STARTUP)
    {
        if(max_index_loop - min_index_loop < FLUID_MIN_LOOP_SIZE)
        {
            if((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE)
                    || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE))
            {
                voice->dsp.samplemode = FLUID_UNLOOPED;
            }
        }

        /* Set the initial phase of the voice (using the result from the
        start offset modulators). */
        fluid_phase_set_int(voice->dsp.phase, voice->dsp.start);
    } /* if startup */

    /* Is this voice run in loop mode, or does it run straight to the
       end of the waveform data? */
    if(((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) &&
            (fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE))
            || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE))
    {
        /* Yes, it will loop as soon as it reaches the loop point.  In
         * this case we must prevent, that the playback pointer (phase)
         * happens to end up beyond the 2nd loop point, because the
         * point has moved.  The DSP algorithm is unable to cope with
         * that situation.  So if the phase is beyond the 2nd loop
         * point, set it to the start of the loop. No way to avoid some
         * noise here.  Note: If the sample pointer ends up -before the
         * first loop point- instead, then the DSP loop will just play
         * the sample, enter the loop and proceed as expected => no
         * actions required.
         */
        int index_in_sample = fluid_phase_index(voice->dsp.phase);

        if(index_in_sample >= voice->dsp.loopend)
        {
            /* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Phase after 2nd loop point!"); */
            fluid_phase_set_int(voice->dsp.phase, voice->dsp.loopstart);
        }
    }

    /*    FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Sample from %i to %i, loop from %i to %i", voice->dsp.start, voice->dsp.end, voice->dsp.loopstart, voice->dsp.loopend); */

    /* Sample sanity has been assured. Don't check again, until some
       sample parameter is changed by modulation. */
    voice->dsp.check_sample_sanity_flag = 0;
#if 0
    printf("Sane? playback loop from %i to %i\n", voice->dsp.loopstart, voice->dsp.loopend);
#endif
    fluid_check_fpe("voice_check_sample_sanity");
}


/**
 * Synthesize a voice to a buffer.
 *
 * @param voice rvoice to synthesize
 * @param dsp_buf Audio buffer to synthesize to (#FLUID_BUFSIZE in length)
 * @return Count of samples written to dsp_buf. (-1 means voice is currently
 * quiet, 0 .. #FLUID_BUFSIZE-1 means voice finished.)
 *
 * Panning, reverb and chorus are processed separately. The dsp interpolation
 * routine is in (fluid_rvoice_dsp.c).
 */
int
fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf)
{
    int ticks = voice->envlfo.ticks;
    int count, is_looping;
    fluid_real_t modenv_val, fmod;

    /******************* sample sanity check **********/

    if(!voice->dsp.sample)
    {
        return 0;
    }

    if(voice->dsp.check_sample_sanity_flag)
    {
        fluid_rvoice_check_sample_sanity(voice);
    }

    /******************* noteoff check ****************/

    if(voice->envlfo.noteoff_ticks != 0 &&
            voice->envlfo.ticks >= voice->envlfo.noteoff_ticks)
    {
        fluid_rvoice_noteoff_LOCAL(voice, 0);
    }

    voice->envlfo.ticks += FLUID_BUFSIZE;

    /******************* vol env **********************/

    fluid_adsr_env_calc(&voice->envlfo.volenv);
    fluid_check_fpe("voice_write vol env");

    if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVFINISHED)
    {
        return 0;
    }

    /******************* mod env **********************/

    fluid_adsr_env_calc(&voice->envlfo.modenv);
    fluid_check_fpe("voice_write mod env");

    /******************* lfo **********************/

    fluid_lfo_calc(&voice->envlfo.modlfo, ticks);
    fluid_check_fpe("voice_write mod LFO");
    fluid_lfo_calc(&voice->envlfo.viblfo, ticks);
    fluid_check_fpe("voice_write vib LFO");

    /******************* amplitude **********************/

    count = fluid_rvoice_calc_amp(voice);
    if(count == 0)
    {
        // Voice has finished, remove from dsp loop
        return 0;
    }
    // else if count is negative, still process the voice


    /******************* phase **********************/

    /* SF2.04 section 8.1.2 #26:
     * attack of modEnv is convex ?!?
     */
    modenv_val = (fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK)
                 ? fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv))
                 : fluid_adsr_env_get_val(&voice->envlfo.modenv);
    /* Calculate the number of samples, that the DSP loop advances
     * through the original waveform with each step in the output
     * buffer. It is the ratio between the frequencies of original
     * waveform and output waveform.*/
    voice->dsp.phase_incr = fluid_ct2hz_real(voice->dsp.pitch +
                            voice->dsp.pitchoffset +
                            fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch
                            + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch
                            + modenv_val * voice->envlfo.modenv_to_pitch)
                            / 
                            voice->dsp.root_pitch_hz;

    /******************* portamento ****************/
    /* pitchoffset is updated if enabled.
       Pitchoffset will be added to dsp pitch at next phase calculation time */

    /* In most cases portamento will be disabled. Thus first verify that portamento is
     * enabled before updating pitchoffset and before disabling portamento when necessary,
     * in order to keep the performance loss at minimum.
     * If the algorithm would first update pitchoffset and then verify if portamento
     * needs to be disabled, there would be a significant performance drop on a x87 FPU
     */
    if(voice->dsp.pitchinc > 0.0f)
    {
        /* portamento is enabled, so update pitchoffset */
        voice->dsp.pitchoffset += voice->dsp.pitchinc;

        /* when pitchoffset reaches 0.0f, portamento is disabled */
        if(voice->dsp.pitchoffset > 0.0f)
        {
            voice->dsp.pitchoffset = voice->dsp.pitchinc = 0.0f;
        }
    }
    else if(voice->dsp.pitchinc < 0.0f)
    {
        /* portamento is enabled, so update pitchoffset */
        voice->dsp.pitchoffset += voice->dsp.pitchinc;

        /* when pitchoffset reaches 0.0f, portamento is disabled */
        if(voice->dsp.pitchoffset < 0.0f)
        {
            voice->dsp.pitchoffset = voice->dsp.pitchinc = 0.0f;
        }
    }

    fluid_check_fpe("voice_write phase calculation");

    /* if phase_incr is not advancing, set it to the minimum fraction value (prevent stuckage) */
    if(voice->dsp.phase_incr == 0)
    {
        voice->dsp.phase_incr = 1;
    }

    /* loop mode release? if not in release, the voice is silent
     * note: this intentionally processes the volenv before returning silence,
     * since that's what polyphone does (PR #1400) */
    if(voice->dsp.samplemode == FLUID_START_ON_RELEASE && fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE)
    {
        return -1;
    }

    /* voice is currently looping? */
    is_looping = voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE
                 || (voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE
                     && fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE);

    /*************** resonant filter ******************/
    // Only "prepare" the filter here, the filter itself will be applied in the dsp_interpolation routines below.
    // This is to satisfy SF2 Section 9.1.8, particularly, the filtered output must be gain-adjusted by the volEnv.
    // Applying the filter after applying the gain from the volEnv might cause audible clicks for when turning off
    // voices that are filtered by a high Q, see https://github.com/FluidSynth/fluidsynth/issues/1427
    //
    // Note that at this point we are using voice->dsp.output_rate which is set to the synth's output rate, because
    // the filter will receive the interpolated waveform.

    fmod = fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + modenv_val * voice->envlfo.modenv_to_fc;
    fluid_iir_filter_calc(&voice->resonant_filter, voice->dsp.output_rate, fmod);

    fluid_check_fpe("voice_write IIR coefficients");

    /* additional custom filter */
    fmod = voice->resonant_custom_filter.flags & FLUID_IIR_BEANLAND
         ? (voice->dsp.pitch + voice->dsp.pitchoffset) - (voice->dsp.sample->origpitch * 100 + voice->dsp.sample->pitchadj)
         : 0;
    fluid_iir_filter_calc(&voice->resonant_custom_filter, voice->dsp.output_rate, fmod);

    fluid_check_fpe("voice_write IIR (custom) coefficients");

    /*********************** run the dsp chain ************************
     * The sample is mixed with the output buffer.
     * The buffer has to be filled from 0 to FLUID_BUFSIZE-1.
     * Depending on the position in the loop and the loop size, this
     * may require several runs. */

    if(count < 0)
    {
        // The voice is quite, i.e. either in delay phase or zero volume.
        // We need to update the rvoice's dsp phase, as the delay phase shall not "postpone" the sound, rather
        // it should be played silently, see https://github.com/FluidSynth/fluidsynth/issues/1312
        return fluid_rvoice_dsp_silence(voice, dsp_buf, is_looping);
    }

    count = fluid_rvoice_dsp_interpolate(voice, dsp_buf, is_looping);

    fluid_check_fpe("voice_write interpolation");

    if (count == 0)
    {
        // voice has finished
        return count;
    }
    
    fluid_iir_filter_apply(&voice->resonant_filter, &voice->resonant_custom_filter, dsp_buf, count);
    fluid_check_fpe("voice_filter fluid_iir_filter_apply()");

    return count;
}

/**
 * Initialize buffers up to (and including) bufnum
 */
static int
fluid_rvoice_buffers_check_bufnum(fluid_rvoice_buffers_t *buffers, unsigned int bufnum)
{
    unsigned int i;

    if(bufnum < buffers->count)
    {
        return FLUID_OK;
    }

    if(bufnum >= FLUID_RVOICE_MAX_BUFS)
    {
        return FLUID_FAILED;
    }

    for(i = buffers->count; i <= bufnum; i++)
    {
        buffers->bufs[i].target_amp = 0.0f;
        buffers->bufs[i].current_amp = 0.0f;
    }

    buffers->count = bufnum + 1;
    return FLUID_OK;
}


DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_amp)
{
    fluid_rvoice_buffers_t *buffers = obj;
    unsigned int bufnum = param[0].i;
    fluid_real_t value = param[1].real;

    if(fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK)
    {
        return;
    }

    buffers->bufs[bufnum].target_amp = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_mapping)
{
    fluid_rvoice_buffers_t *buffers = obj;
    unsigned int bufnum = param[0].i;
    int mapping = param[1].i;

    if(fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK)
    {
        return;
    }

    buffers->bufs[bufnum].mapping = mapping;
}


DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_reset)
{
    fluid_rvoice_t *voice = obj;

    voice->dsp.has_looped = 0;
    voice->envlfo.ticks = 0;
    voice->envlfo.noteoff_ticks = 0;

    /* legato initialization */
    voice->dsp.pitchoffset = 0.0;   /* portamento initialization */
    voice->dsp.pitchinc = 0.0;

    /* mod env initialization*/
    fluid_adsr_env_reset(&voice->envlfo.modenv);

    /* vol env initialization */
    fluid_adsr_env_reset(&voice->envlfo.volenv);

    /* Fixme: Retrieve from any other existing
       voice on this channel to keep LFOs in
       unison? */
    fluid_lfo_reset(&voice->envlfo.viblfo);
    fluid_lfo_reset(&voice->envlfo.modlfo);

    /* Clear sample history in filter */
    fluid_iir_filter_reset(&voice->resonant_filter);
    fluid_iir_filter_reset(&voice->resonant_custom_filter);

    /* Force setting of the phase at the first DSP loop run
     * This cannot be done earlier, because it depends on modulators.
       [DH] Is that comment really true? */
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_noteoff)
{
    fluid_rvoice_t *rvoice = obj;
    unsigned int min_ticks = param[0].i;

    fluid_rvoice_noteoff_LOCAL(rvoice, min_ticks);
}

static void
fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks)
{
    if(min_ticks > voice->envlfo.ticks)
    {
        /* Delay noteoff */
        voice->envlfo.noteoff_ticks = min_ticks;
        return;
    }

    voice->envlfo.noteoff_ticks = 0;

    if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVATTACK)
    {
        /* A voice is turned off during the attack section of the volume
         * envelope.  The attack section ramps up linearly with
         * amplitude. The other sections use logarithmic scaling. Calculate new
         * volenv_val to achieve equivalent amplitude during the release phase
         * for seamless volume transition.
         */
        if(fluid_adsr_env_get_val(&voice->envlfo.volenv) > 0)
        {
            fluid_real_t lfo = fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol;
            fluid_real_t amp = fluid_adsr_env_get_val(&voice->envlfo.volenv) * fluid_cb2amp(lfo);
            fluid_real_t env_value = - (((-200.f / FLUID_M_LN10) * FLUID_LOGF(amp) - lfo) / FLUID_PEAK_ATTENUATION - 1);
            fluid_clip(env_value, 0.0f, 1.0f);
            fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value);
        }
    }

	if(fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK)
    {
        /* A voice is turned off during the attack section of the modulation
         * envelope. The attack section use convex scaling with pitch and filter
		 * frequency cutoff (see fluid_rvoice_write(): modenv_val = fluid_convex(127 * modenv.val)
		 * The other sections use linear scaling: modenv_val = modenv.val
		 * 
		 * Calculate new modenv.val to achieve equivalent modenv_val during the release phase
         * for seamless pitch and filter frequency cutoff transition.
         */
        if(fluid_adsr_env_get_val(&voice->envlfo.modenv) > 0)
        {
            fluid_real_t env_value = fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv));
            fluid_clip(env_value, 0.0, 1.0);
            fluid_adsr_env_set_val(&voice->envlfo.modenv, env_value);
        }
    }

    fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVRELEASE);
    fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVRELEASE);
}

/**
 * skips to Attack section
 *
 * Updates vol and attack data
 * Correction on volume val to achieve equivalent amplitude at noteOn legato
 *
 * @param voice the synthesis voice to be updated
*/
static FLUID_INLINE void fluid_rvoice_local_retrigger_attack(fluid_rvoice_t *voice)
{
    /* skips to Attack section */
    /* Once in Attack section, current count must be reset, to be sure
    that the section will be not be prematurely finished. */
    fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVATTACK);
    {
        /* Correction on volume val to achieve equivalent amplitude at noteOn legato */
        fluid_env_data_t *env_data;
        fluid_real_t peak = fluid_cb2amp(voice->dsp.attenuation);
        fluid_real_t prev_peak = fluid_cb2amp(voice->dsp.prev_attenuation);
        voice->envlfo.volenv.val = (voice->envlfo.volenv.val  * prev_peak) / peak;
        /* Correction on slope direction for Attack section */
        env_data = &voice->envlfo.volenv.data[FLUID_VOICE_ENVATTACK];

        if(voice->envlfo.volenv.val <= 1.0f)
        {
            /* slope attack for legato note needs to be positive from val  up to 1 */
            env_data->increment = 1.0f / env_data->count;
            env_data->min = -1.0f;
            env_data->max =  1.0f;
        }
        else
        {
            /* slope attack for legato note needs to be negative: from val  down to 1 */
            env_data->increment = -voice->envlfo.volenv.val / env_data->count;
            env_data->min = 1.0f;
            env_data->max = voice->envlfo.volenv.val;
        }
    }
}

/**
 * Used by legato Mode : multi_retrigger
 *  see fluid_synth_noteon_mono_legato_multi_retrigger()
 * @param voice the synthesis voice to be updated
*/
DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack)
{
    fluid_rvoice_t *voice = obj;
    int section; /* volume or modulation section */

    /*-------------------------------------------------------------------------
     Section skip for volume envelope
    --------------------------------------------------------------------------*/
    section = fluid_adsr_env_get_section(&voice->envlfo.volenv);
    if(section >= FLUID_VOICE_ENVHOLD)
    {
        /* DECAY, SUSTAIN,RELEASE section use logarithmic scaling. Calculates new
        volenv_val to achieve equivalent amplitude during the attack phase
        for seamless volume transition. */
        fluid_real_t amp_cb, env_value;
        amp_cb = FLUID_PEAK_ATTENUATION *
                 (1.0f - fluid_adsr_env_get_val(&voice->envlfo.volenv));
        env_value = fluid_cb2amp(amp_cb); /* a bit of optimization */
        fluid_clip(env_value, 0.0, 1.0);
        fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value);
        /* next, skips to Attack section */
    }

    /* skips to Attack section from any section */
    /* Update vol and  attack data */
    fluid_rvoice_local_retrigger_attack(voice);
    
    /*-------------------------------------------------------------------------
     Section skip for modulation envelope
    --------------------------------------------------------------------------*/
    section = fluid_adsr_env_get_section(&voice->envlfo.modenv);
    if(section >= FLUID_VOICE_ENVHOLD)
    {
        /* DECAY, SUSTAIN,RELEASE section use linear scaling. 
        Since v 2.1 , as recommended by soundfont 2.01/2.4 spec, ATTACK section
        uses convex shape (see fluid_rvoice_write() - fluid_convex()).
        Calculate new modenv value (new_value) for seamless attack transition.
        Here we need the inverse of fluid_convex() function defined as:
        new_value = pow(10, (1 - current_val) . FLUID_PEAK_ATTENUATION / -200 . 2.0)
        For performance reason we use fluid_cb2amp(Val) = pow(10, val/-200) with
        val = (1 - current_val) . FLUID_PEAK_ATTENUATION / 2.0 
        */
        fluid_real_t new_value; /* new modenv value */
        new_value = fluid_cb2amp((1.0f - fluid_adsr_env_get_val(&voice->envlfo.modenv))
                                  * FLUID_PEAK_ATTENUATION / 2.0);
        fluid_clip(new_value, 0.0, 1.0);
        fluid_adsr_env_set_val(&voice->envlfo.modenv, new_value);
    }
    /* Skips from any section to ATTACK section */
    fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVATTACK);
}

/**
 * sets the portamento dsp parameters: dsp.pitchoffset, dsp.pitchinc
 * @param voice rvoice to set portamento.
 * @param countinc increment count number.
 * @param pitchoffset pitch offset to apply to voice dsp.pitch.
 *
 * Notes:
 * 1) To get continuous portamento between consecutive noteOn (n1,n2,n3...),
 *   pitchoffset is accumulated in current dsp pitchoffset.
 * 2) And to get constant portamento duration, dsp pitch increment is updated.
*/
DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_portamento)
{
    fluid_rvoice_t *voice = obj;
    unsigned int countinc = param[0].i;
    fluid_real_t pitchoffset = param[1].real;

    if(countinc)
    {
        voice->dsp.pitchoffset += pitchoffset;
        voice->dsp.pitchinc = - voice->dsp.pitchoffset / countinc;
    }

    /* Then during the voice processing (in fluid_rvoice_write()),
    dsp.pitchoffset will be incremented by dsp pitchinc. */
}


DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_output_rate)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->dsp.output_rate = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_interp_method)
{
    fluid_rvoice_t *voice = obj;
    int value = param[0].i;

    voice->dsp.interp_method = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_root_pitch_hz)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->dsp.root_pitch_hz = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_pitch)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->dsp.pitch = value;
}


DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_attenuation)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->dsp.prev_attenuation = voice->dsp.attenuation;
    voice->dsp.attenuation = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_min_attenuation_cB)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->dsp.min_attenuation_cB = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_viblfo_to_pitch)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->envlfo.viblfo_to_pitch = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_pitch)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->envlfo.modlfo_to_pitch = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_vol)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->envlfo.modlfo_to_vol = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_fc)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->envlfo.modlfo_to_fc = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_fc)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->envlfo.modenv_to_fc = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_pitch)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->envlfo.modenv_to_pitch = value;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_synth_gain)
{
    fluid_rvoice_t *voice = obj;
    fluid_real_t value = param[0].real;

    voice->dsp.synth_gain = value;

    /* For a looped sample, this value will be overwritten as soon as the
     * loop parameters are initialized (they may depend on modulators).
     * This value can be kept, it is a worst-case estimate.
     */
    voice->dsp.amplitude_that_reaches_noise_floor_nonloop = FLUID_NOISE_FLOOR / value;
    voice->dsp.amplitude_that_reaches_noise_floor_loop = FLUID_NOISE_FLOOR / value;
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_start)
{
    fluid_rvoice_t *voice = obj;
    int value = param[0].i;

    voice->dsp.start = value;
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_end)
{
    fluid_rvoice_t *voice = obj;
    int value = param[0].i;

    voice->dsp.end = value;
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopstart)
{
    fluid_rvoice_t *voice = obj;
    int value = param[0].i;

    voice->dsp.loopstart = value;
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopend)
{
    fluid_rvoice_t *voice = obj;
    int value = param[0].i;

    voice->dsp.loopend = value;
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_samplemode)
{
    fluid_rvoice_t *voice = obj;
    enum fluid_loop value = param[0].i;

    voice->dsp.samplemode = value;
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}


DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_sample)
{
    fluid_rvoice_t *voice = obj;
    fluid_sample_t *value = param[0].ptr;

    voice->dsp.sample = value;

    if(value)
    {
        voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP;
    }
}

DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_voiceoff)
{
    fluid_rvoice_t *voice = obj;

    fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVFINISHED);
    fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVFINISHED);
}


