/* 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, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 */

#include "fluid_sys.h"
#include "fluid_voice.h"
#include "fluid_mod.h"
#include "fluid_chan.h"
#include "fluid_conv.h"
#include "fluid_synth.h"
#include "fluid_sys.h"
#include "fluid_sfont.h"
#include "fluid_rvoice_event.h"
#include "fluid_defsfont.h"

/* used for filter turn off optimization - if filter cutoff is above the
   specified value and filter q is below the other value, turn filter off */
#define FLUID_MAX_AUDIBLE_FILTER_FC 19000.0f
#define FLUID_MIN_AUDIBLE_FILTER_Q 1.2f

/* min vol envelope release (to stop clicks) in SoundFont timecents */
#define FLUID_MIN_VOLENVRELEASE -7200.0f /* ~16ms */


static const int32_t INT24_MAX = (1 << (16 + 8 - 1));

static int fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t *voice);
static int calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base,
                                        int gen_key2base, int is_decay);
static fluid_real_t
fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice);

#define UPDATE_RVOICE0(proc) \
  do { \
      fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \
      fluid_rvoice_eventhandler_push(voice->eventhandler, proc, voice->rvoice, param); \
  } while (0)

#define UPDATE_RVOICE_GENERIC_R1(proc, obj, rarg) \
  do { \
      fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \
      param[0].real = rarg; \
      fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \
  } while (0)

#define UPDATE_RVOICE_GENERIC_I1(proc, obj, iarg) \
  do { \
      fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \
      param[0].i = iarg; \
      fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \
  } while (0)

#define UPDATE_RVOICE_GENERIC_I2(proc, obj, iarg1, iarg2) \
  do { \
      fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \
      param[0].i = iarg1; \
      param[1].i = iarg2; \
      fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \
  } while (0)

#define UPDATE_RVOICE_GENERIC_IR(proc, obj, iarg, rarg) \
  do { \
      fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \
      param[0].i = iarg; \
      param[1].real = rarg; \
      fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \
  } while (0)


#define UPDATE_RVOICE_R1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, voice->rvoice, arg1)
#define UPDATE_RVOICE_I1(proc, arg1) UPDATE_RVOICE_GENERIC_I1(proc, voice->rvoice, arg1)

#define UPDATE_RVOICE_BUFFERS_AMP(proc, iarg, rarg) UPDATE_RVOICE_GENERIC_IR(proc, &voice->rvoice->buffers, iarg, rarg)
#define UPDATE_RVOICE_ENVLFO_R1(proc, envp, rarg) UPDATE_RVOICE_GENERIC_R1(proc, &voice->rvoice->envlfo.envp, rarg)
#define UPDATE_RVOICE_ENVLFO_I1(proc, envp, iarg) UPDATE_RVOICE_GENERIC_I1(proc, &voice->rvoice->envlfo.envp, iarg)

static FLUID_INLINE void
fluid_voice_update_volenv(fluid_voice_t *voice,
                          int enqueue,
                          fluid_adsr_env_section_t section,
                          unsigned int count,
                          fluid_real_t coeff,
                          fluid_real_t increment,
                          fluid_real_t min,
                          fluid_real_t max)
{
    fluid_rvoice_param_t param[MAX_EVENT_PARAMS];

    param[0].i = section;
    param[1].i = count;
    param[2].real = coeff;
    param[3].real = increment;
    param[4].real = min;
    param[5].real = max;

    if(enqueue)
    {
        fluid_rvoice_eventhandler_push(voice->eventhandler,
                                       fluid_adsr_env_set_data,
                                       &voice->rvoice->envlfo.volenv,
                                       param);
    }
    else
    {
        fluid_adsr_env_set_data(&voice->rvoice->envlfo.volenv, param);
    }
}

static FLUID_INLINE void
fluid_voice_update_modenv(fluid_voice_t *voice,
                          int enqueue,
                          fluid_adsr_env_section_t section,
                          unsigned int count,
                          fluid_real_t coeff,
                          fluid_real_t increment,
                          fluid_real_t min,
                          fluid_real_t max)
{
    fluid_rvoice_param_t param[MAX_EVENT_PARAMS];

    param[0].i = section;
    param[1].i = count;
    param[2].real = coeff;
    param[3].real = increment;
    param[4].real = min;
    param[5].real = max;

    if(enqueue)
    {
        fluid_rvoice_eventhandler_push(voice->eventhandler,
                                       fluid_adsr_env_set_data,
                                       &voice->rvoice->envlfo.modenv,
                                       param);
    }
    else
    {
        fluid_adsr_env_set_data(&voice->rvoice->envlfo.modenv, param);
    }
}

static FLUID_INLINE void fluid_voice_sample_unref(fluid_sample_t **sample)
{
    if(*sample != NULL)
    {
        fluid_sample_decr_ref(*sample);
        *sample = NULL;
    }
}

/*
 * Swaps the current rvoice with the current overflow_rvoice
 */
static void fluid_voice_swap_rvoice(fluid_voice_t *voice)
{
    fluid_rvoice_t *rtemp = voice->rvoice;
    int ctemp = voice->can_access_rvoice;
    voice->rvoice = voice->overflow_rvoice;
    voice->can_access_rvoice = voice->can_access_overflow_rvoice;
    voice->overflow_rvoice = rtemp;
    voice->can_access_overflow_rvoice = ctemp;
}

static void fluid_voice_initialize_rvoice(fluid_voice_t *voice, fluid_real_t output_rate)
{
    fluid_rvoice_param_t param[MAX_EVENT_PARAMS];

    FLUID_MEMSET(voice->rvoice, 0, sizeof(fluid_rvoice_t));

    /* The 'sustain' and 'finished' segments of the volume / modulation
     * envelope are constant. They are never affected by any modulator
     * or generator. Therefore it is enough to initialize them once
     * during the lifetime of the synth.
     */
    fluid_voice_update_volenv(voice, FALSE, FLUID_VOICE_ENVSUSTAIN,
                              0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f);
    fluid_voice_update_volenv(voice, FALSE, FLUID_VOICE_ENVFINISHED,
                              0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f);
    fluid_voice_update_modenv(voice, FALSE, FLUID_VOICE_ENVSUSTAIN,
                              0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f);
    fluid_voice_update_modenv(voice, FALSE, FLUID_VOICE_ENVFINISHED,
                              0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f);

    param[0].i = FLUID_IIR_LOWPASS;
    param[1].i = 0;
    fluid_iir_filter_init(&voice->rvoice->resonant_filter, param);

    param[0].i = FLUID_IIR_DISABLED;
    fluid_iir_filter_init(&voice->rvoice->resonant_custom_filter, param);

    param[0].real = output_rate;
    fluid_rvoice_set_output_rate(voice->rvoice, param);
}

/*
 * new_fluid_voice
 */
fluid_voice_t *
new_fluid_voice(fluid_rvoice_eventhandler_t *handler, fluid_real_t output_rate)
{
    fluid_voice_t *voice;
    voice = FLUID_NEW(fluid_voice_t);

    if(voice == NULL)
    {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        return NULL;
    }

    voice->can_access_rvoice = TRUE;
    voice->can_access_overflow_rvoice = TRUE;

    voice->rvoice = FLUID_NEW(fluid_rvoice_t);
    voice->overflow_rvoice = FLUID_NEW(fluid_rvoice_t);

    if(voice->rvoice == NULL || voice->overflow_rvoice == NULL)
    {
        FLUID_LOG(FLUID_ERR, "Out of memory");
        delete_fluid_voice(voice);
        return NULL;
    }

    voice->status = FLUID_VOICE_CLEAN;
    voice->chan = NO_CHANNEL;
    voice->key = 0;
    voice->vel = 0;
    voice->eventhandler = handler;
    voice->channel = NULL;
    voice->sample = NULL;
    voice->output_rate = output_rate;

    /* Initialize both the rvoice and overflow_rvoice */
    fluid_voice_initialize_rvoice(voice, output_rate);
    fluid_voice_swap_rvoice(voice);
    fluid_voice_initialize_rvoice(voice, output_rate);

    return voice;
}

/*
 * delete_fluid_voice
 */
void
delete_fluid_voice(fluid_voice_t *voice)
{
    fluid_return_if_fail(voice != NULL);

    if(!voice->can_access_rvoice || !voice->can_access_overflow_rvoice)
    {
        FLUID_LOG(FLUID_WARN, "Deleting voice %u which has locked rvoices!", voice->id);
    }

    FLUID_FREE(voice->overflow_rvoice);
    FLUID_FREE(voice->rvoice);
    FLUID_FREE(voice);
}

/* fluid_voice_init
 *
 * Initialize the synthesis process
 * inst_zone, the Instrument Zone contains the sample, Keyrange,Velrange
 * of the voice.
 * When playing legato (n1,n2) in mono mode, n2 will use n1 voices
 * as far as n2 still enters in Keyrange,Velrange of n1.
 */
int
fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample,
                 fluid_zone_range_t *inst_zone_range,
                 fluid_channel_t *channel, int key, int vel, unsigned int id,
                 unsigned int start_time, fluid_real_t gain)
{
    /* Note: The voice parameters will be initialized later, when the
     * generators have been retrieved from the sound font. Here, only
     * the 'working memory' of the voice (position in envelopes, history
     * of IIR filters, position in sample etc) is initialized. */
    int i;

    if(!voice->can_access_rvoice)
    {
        if(voice->can_access_overflow_rvoice)
        {
            fluid_voice_swap_rvoice(voice);
        }
        else
        {
            FLUID_LOG(FLUID_ERR, "Internal error: Cannot access an rvoice in fluid_voice_init!");
            return FLUID_FAILED;
        }
    }

    /* We are now guaranteed to have access to the rvoice */

    if(voice->sample)
    {
        fluid_voice_off(voice);
    }

    voice->zone_range = inst_zone_range; /* Instrument zone range for legato */
    voice->id = id;
    voice->chan = fluid_channel_get_num(channel);
    voice->key = (unsigned char) key;
    voice->vel = (unsigned char) vel;
    voice->channel = channel;
    voice->mod_count = 0;
    voice->start_time = start_time;
    voice->has_noteoff = 0;
    UPDATE_RVOICE0(fluid_rvoice_reset);

    /* Increment the reference count of the sample to prevent the
       unloading of the soundfont while this voice is playing,
       once for us and once for the rvoice. */
    fluid_sample_incr_ref(sample);
    fluid_rvoice_eventhandler_push_ptr(voice->eventhandler, fluid_rvoice_set_sample, voice->rvoice, sample);
    fluid_sample_incr_ref(sample);
    voice->sample = sample;

    i = fluid_channel_get_interp_method(channel);
    UPDATE_RVOICE_I1(fluid_rvoice_set_interp_method, i);

    /* Set all the generators to their default value, according to SF
     * 2.01 section 8.1.3 (page 48). The value of NRPN messages are
     * copied from the channel to the voice's generators. The sound font
     * loader overwrites them. The generator values are later converted
     * into voice parameters in
     * fluid_voice_calculate_runtime_synthesis_parameters.  */
    fluid_gen_init(&voice->gen[0], channel);
    UPDATE_RVOICE_I1(fluid_rvoice_set_samplemode, _SAMPLEMODE(voice));

    voice->synth_gain = gain;

    /* avoid division by zero later*/
    if(voice->synth_gain < 0.0000001f)
    {
        voice->synth_gain = 0.0000001f;
    }

    UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, voice->synth_gain);

    /* Set up buffer mapping, should be done more flexible in the future. */
    i = 2 * channel->synth->audio_groups;
    i += (voice->chan % channel->synth->effects_groups) * channel->synth->effects_channels;
    UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 2, i + SYNTH_REVERB_CHANNEL);
    UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 3, i + SYNTH_CHORUS_CHANNEL);

    i = 2 * (voice->chan % channel->synth->audio_groups);
    UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 0, i);
    UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 1, i + 1);

    return FLUID_OK;
}


/**
 * Update sample rate.
 * @note If the voice is active, it will be turned off.
 */
void
fluid_voice_set_output_rate(fluid_voice_t *voice, fluid_real_t value)
{
    if(fluid_voice_is_playing(voice))
    {
        fluid_voice_off(voice);
    }

    voice->output_rate = value;
    UPDATE_RVOICE_GENERIC_R1(fluid_rvoice_set_output_rate, voice->rvoice, value);
    UPDATE_RVOICE_GENERIC_R1(fluid_rvoice_set_output_rate, voice->overflow_rvoice, value);
}


/**
 * Set the value of a generator.
 * @param voice Voice instance
 * @param i Generator ID (#fluid_gen_type)
 * @param val Generator value
 */
void
fluid_voice_gen_set(fluid_voice_t *voice, int i, float val)
{
    voice->gen[i].val = val;
    voice->gen[i].flags = GEN_SET;

    if(i == GEN_SAMPLEMODE)
    {
        UPDATE_RVOICE_I1(fluid_rvoice_set_samplemode, (int) val);
    }
}

/**
 * Offset the value of a generator.
 * @param voice Voice instance
 * @param i Generator ID (#fluid_gen_type)
 * @param val Value to add to the existing value
 */
void
fluid_voice_gen_incr(fluid_voice_t *voice, int i, float val)
{
    voice->gen[i].val += val;
    voice->gen[i].flags = GEN_SET;
}

/**
 * Get the value of a generator.
 * @param voice Voice instance
 * @param gen Generator ID (#fluid_gen_type)
 * @return Current generator value
 */
float
fluid_voice_gen_get(fluid_voice_t *voice, int gen)
{
    return voice->gen[gen].val;
}

fluid_real_t fluid_voice_gen_value(const fluid_voice_t *voice, int num)
{
    return (fluid_real_t)(voice->gen[num].val + voice->gen[num].mod + voice->gen[num].nrpn);
}

/*
 * fluid_voice_start
 */
void fluid_voice_start(fluid_voice_t *voice)
{
    /* The maximum volume of the loop is calculated and cached once for each
     * sample with its nominal loop settings. This happens, when the sample is used
     * for the first time.*/

    fluid_voice_calculate_runtime_synthesis_parameters(voice);

#ifdef WITH_PROFILING
    voice->ref = fluid_profile_ref();
#endif

    voice->status = FLUID_VOICE_ON;

    /* Increment voice count */
    voice->channel->synth->active_voice_count++;
}

/**
 * Calculate the amplitude of a voice.
 *
 * @param gain The gain value in the range [0.0 ; 1.0]
 * @return An amplitude used by rvoice_mixer's buffers
 */
static FLUID_INLINE fluid_real_t
fluid_voice_calculate_gain_amplitude(const fluid_voice_t *voice, fluid_real_t gain)
{
    /* we use 24bit samples in fluid_rvoice_dsp. in order to normalize float
     * samples to [0.0;1.0] divide samples by the max. value of an int24 and
     * amplify them with the gain */
    return gain * voice->synth_gain / (INT24_MAX * 1.0f);
}

/* Useful to return the nominal pitch of a key */
/* The nominal pitch is dependant of voice->root_pitch,tuning, and
   GEN_SCALETUNE generator.
   This is useful to set the value of GEN_PITCH generator on noteOn.
   This is useful to get the beginning/ending pitch for portamento.
*/
fluid_real_t fluid_voice_calculate_pitch(fluid_voice_t *voice, int key)
{
    fluid_tuning_t *tuning;
    fluid_real_t x, pitch;

    /* Now the nominal pitch of the key is returned.
     * Note about SCALETUNE: SF2.01 8.1.3 says, that this generator is a
     * non-realtime parameter. So we don't allow modulation (as opposed
     * to fluid_voice_gen_value(voice, GEN_SCALETUNE) When the scale tuning is varied,
     * one key remains fixed. Here C3 (MIDI number 60) is used.
     */
    if(fluid_channel_has_tuning(voice->channel))
    {
        tuning = fluid_channel_get_tuning(voice->channel);
        x = fluid_tuning_get_pitch(tuning, (int)(voice->root_pitch / 100.0f));
        pitch = voice->gen[GEN_SCALETUNE].val / 100.0f *
                (fluid_tuning_get_pitch(tuning, key) - x) + x;
    }
    else
    {
        pitch = voice->gen[GEN_SCALETUNE].val
                * (key - voice->root_pitch / 100.0f) + voice->root_pitch;
    }

    return pitch;
}

void
fluid_voice_calculate_gen_pitch(fluid_voice_t *voice)
{
    voice->gen[GEN_PITCH].val = fluid_voice_calculate_pitch(voice, fluid_voice_get_actual_key(voice));
}

/*
 * fluid_voice_calculate_runtime_synthesis_parameters
 *
 * in this function we calculate the values of all the parameters. the
 * parameters are converted to their most useful unit for the DSP
 * algorithm, for example, number of samples instead of
 * timecents. Some parameters keep their "perceptual" unit and
 * conversion will be done in the DSP function. This is the case, for
 * example, for the pitch since it is modulated by the controllers in
 * cents. */
static int
fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t *voice)
{
    int i;
    unsigned int n;

    static int const list_of_generators_to_initialize[] =
    {
        GEN_STARTADDROFS,                    /* SF2.01 page 48 #0   */
        GEN_ENDADDROFS,                      /*                #1   */
        GEN_STARTLOOPADDROFS,                /*                #2   */
        GEN_ENDLOOPADDROFS,                  /*                #3   */
        /* GEN_STARTADDRCOARSEOFS see comment below [1]        #4   */
        GEN_MODLFOTOPITCH,                   /*                #5   */
        GEN_VIBLFOTOPITCH,                   /*                #6   */
        GEN_MODENVTOPITCH,                   /*                #7   */
        GEN_FILTERFC,                        /*                #8   */
        GEN_FILTERQ,                         /*                #9   */
        GEN_MODLFOTOFILTERFC,                /*                #10  */
        GEN_MODENVTOFILTERFC,                /*                #11  */
        /* GEN_ENDADDRCOARSEOFS [1]                            #12  */
        GEN_MODLFOTOVOL,                     /*                #13  */
        /* not defined                                         #14  */
        GEN_CHORUSSEND,                      /*                #15  */
        GEN_REVERBSEND,                      /*                #16  */
        GEN_PAN,                             /*                #17  */
        /* not defined                                         #18  */
        /* not defined                                         #19  */
        /* not defined                                         #20  */
        GEN_MODLFODELAY,                     /*                #21  */
        GEN_MODLFOFREQ,                      /*                #22  */
        GEN_VIBLFODELAY,                     /*                #23  */
        GEN_VIBLFOFREQ,                      /*                #24  */
        GEN_MODENVDELAY,                     /*                #25  */
        GEN_MODENVATTACK,                    /*                #26  */
        GEN_MODENVHOLD,                      /*                #27  */
        GEN_MODENVDECAY,                     /*                #28  */
        /* GEN_MODENVSUSTAIN [1]                               #29  */
        GEN_MODENVRELEASE,                   /*                #30  */
        /* GEN_KEYTOMODENVHOLD [1]                             #31  */
        /* GEN_KEYTOMODENVDECAY [1]                            #32  */
        GEN_VOLENVDELAY,                     /*                #33  */
        GEN_VOLENVATTACK,                    /*                #34  */
        GEN_VOLENVHOLD,                      /*                #35  */
        GEN_VOLENVDECAY,                     /*                #36  */
        /* GEN_VOLENVSUSTAIN [1]                               #37  */
        GEN_VOLENVRELEASE,                   /*                #38  */
        /* GEN_KEYTOVOLENVHOLD [1]                             #39  */
        /* GEN_KEYTOVOLENVDECAY [1]                            #40  */
        /* GEN_STARTLOOPADDRCOARSEOFS [1]                      #45  */
        GEN_KEYNUM,                          /*                #46  */
        GEN_VELOCITY,                        /*                #47  */
        GEN_ATTENUATION,                     /*                #48  */
        /* GEN_ENDLOOPADDRCOARSEOFS [1]                        #50  */
        /* GEN_COARSETUNE           [1]                        #51  */
        /* GEN_FINETUNE             [1]                        #52  */
        GEN_OVERRIDEROOTKEY,                 /*                #58  */
        GEN_PITCH,                           /*                ---  */
        GEN_CUSTOM_BALANCE,                  /*                ---  */
        GEN_CUSTOM_FILTERFC,                 /*                ---  */
        GEN_CUSTOM_FILTERQ                   /*                ---  */
    };

    /* When the voice is made ready for the synthesis process, a lot of
     * voice-internal parameters have to be calculated.
     *
     * At this point, the sound font has already set the -nominal- value
     * for all generators (excluding GEN_PITCH). Most generators can be
     * modulated - they include a nominal value and an offset (which
     * changes with velocity, note number, channel parameters like
     * aftertouch, mod wheel...) Now this offset will be calculated as
     * follows:
     *
     *  - Process each modulator once.
     *  - Calculate its output value.
     *  - Find the target generator.
     *  - Add the output value to the modulation value of the generator.
     *
     * Note: The generators have been initialized with
     * fluid_gen_init().
     */

    for(i = 0; i < voice->mod_count; i++)
    {
        fluid_mod_t *mod = &voice->mod[i];
        fluid_real_t modval = fluid_mod_get_value(mod, voice);
        int dest_gen_index = mod->dest;
        fluid_gen_t *dest_gen = &voice->gen[dest_gen_index];
        dest_gen->mod += modval;
        /*      fluid_dump_modulator(mod); */
    }

    /* Now the generators are initialized, nominal and modulation value.
     * The voice parameters (which depend on generators) are calculated
     * with fluid_voice_update_param. Processing the list of generator
     * changes will calculate each voice parameter once.
     *
     * Note [1]: Some voice parameters depend on several generators. For
     * example, the pitch depends on GEN_COARSETUNE, GEN_FINETUNE and
     * GEN_PITCH.  voice->pitch.  Unnecessary recalculation is avoided
     * by removing all but one generator from the list of voice
     * parameters.  Same with GEN_XXX and GEN_XXXCOARSE: the
     * initialisation list contains only GEN_XXX.
     */

    /* Calculate the voice parameter(s) dependent on each generator. */
    for(n = 0; n < FLUID_N_ELEMENTS(list_of_generators_to_initialize); n++)
    {
        fluid_voice_update_param(voice, list_of_generators_to_initialize[n]);
    }

    /* Start portamento if enabled */
    {
        /* fromkey note comes from "GetFromKeyPortamentoLegato()" detector.
        When fromkey is set to ValidNote , portamento is started */
        /* Return fromkey portamento */
        int fromkey = voice->channel->synth->fromkey_portamento;

        if(fluid_channel_is_valid_note(fromkey))
        {
            /* Send portamento parameters to the voice dsp */
            fluid_voice_update_portamento(voice, fromkey, fluid_voice_get_actual_key(voice));
        }
    }

    /* Make an estimate on how loud this voice can get at any time (attenuation). */
    UPDATE_RVOICE_R1(fluid_rvoice_set_min_attenuation_cB,
                     fluid_voice_get_lower_boundary_for_attenuation(voice));
    return FLUID_OK;
}

/*
 * calculate_hold_decay_buffers
 */
static int
calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base,
                             int gen_key2base, int is_decay)
{
    /* Purpose:
     *
     * Returns the number of DSP loops, that correspond to the hold
     * (is_decay=0) or decay (is_decay=1) time.
     * gen_base=GEN_VOLENVHOLD, GEN_VOLENVDECAY, GEN_MODENVHOLD,
     * GEN_MODENVDECAY gen_key2base=GEN_KEYTOVOLENVHOLD,
     * GEN_KEYTOVOLENVDECAY, GEN_KEYTOMODENVHOLD, GEN_KEYTOMODENVDECAY
     */

    fluid_real_t timecents;
    fluid_real_t seconds;
    int buffers;

    /* SF2.01 section 8.4.3 # 31, 32, 39, 40
     * GEN_KEYTOxxxENVxxx uses key 60 as 'origin'.
     * The unit of the generator is timecents per key number.
     * If KEYTOxxxENVxxx is 100, a key one octave over key 60 (72)
     * will cause (60-72)*100=-1200 timecents of time variation.
     * The time is cut in half.
     */
    timecents = (fluid_voice_gen_value(voice, gen_base) + fluid_voice_gen_value(voice, gen_key2base) * (fluid_real_t)(60 - fluid_voice_get_actual_key(voice)));

    /* Range checking */
    if(is_decay)
    {
        /* SF 2.01 section 8.1.3 # 28, 36 */
        if(timecents > 8000.f)
        {
            timecents = 8000.f;
        }
    }
    else
    {
        /* SF 2.01 section 8.1.3 # 27, 35 */
        if(timecents > 5000.f)
        {
            timecents = 5000.f;
        }

        /* SF 2.01 section 8.1.2 # 27, 35:
         * The most negative number indicates no hold time
         */
        if(timecents <= -32768.f)
        {
            return 0;
        }
    }

    /* SF 2.01 section 8.1.3 # 27, 28, 35, 36 */
    if(timecents < -12000.f)
    {
        timecents = -12000.f;
    }

    seconds = fluid_tc2sec(timecents);
    /* Each DSP loop processes FLUID_BUFSIZE samples. */

    /* round to next full number of buffers */
    buffers = (int)(((fluid_real_t)voice->output_rate * seconds)
                    / (fluid_real_t)FLUID_BUFSIZE
                    + 0.5f);

    return buffers;
}

/*
 * The value of a generator (gen) has changed.  (The different
 * generators are listed in fluidsynth.h, or in SF2.01 page 48-49)
 * Now the dependent 'voice' parameters are calculated.
 *
 * fluid_voice_update_param can be called during the setup of the
 * voice (to calculate the initial value for a voice parameter), or
 * during its operation (a generator has been changed due to
 * real-time parameter modifications like pitch-bend).
 *
 * Note: The generator holds three values: The base value .val, an
 * offset caused by modulators .mod, and an offset caused by the
 * NRPN system. fluid_voice_gen_value(voice, generator_enumerator) returns the sum
 * of all three.
 */
/**
 * Update all the synthesis parameters, which depend on generator \a gen.
 * @param voice Voice instance
 * @param gen Generator id (#fluid_gen_type)
 *
 * This is only necessary after changing a generator of an already operating voice.
 * Most applications will not need this function.
 */
void
fluid_voice_update_param(fluid_voice_t *voice, int gen)
{
    unsigned int count, z;
    fluid_real_t x = fluid_voice_gen_value(voice, gen);

    switch(gen)
    {

    case GEN_PAN:
    case GEN_CUSTOM_BALANCE:
        /* range checking is done in the fluid_pan and fluid_balance functions */
        voice->pan = fluid_voice_gen_value(voice, GEN_PAN);
        voice->balance = fluid_voice_gen_value(voice, GEN_CUSTOM_BALANCE);

        /* left amp */
        UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 0,
                                  fluid_voice_calculate_gain_amplitude(voice,
                                          fluid_pan(voice->pan, 1) * fluid_balance(voice->balance, 1)));

        /* right amp */
        UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 1,
                                  fluid_voice_calculate_gain_amplitude(voice,
                                          fluid_pan(voice->pan, 0) * fluid_balance(voice->balance, 0)));
        break;

    case GEN_ATTENUATION:
        voice->attenuation = x;

        /* Range: SF2.01 section 8.1.3 # 48
         * Motivation for range checking:
         * OHPiano.SF2 sets initial attenuation to a whooping -96 dB */
        fluid_clip(voice->attenuation, 0.f, 1440.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_attenuation, voice->attenuation);
        break;

    /* The pitch is calculated from three different generators.
     * Read comment in fluidsynth.h about GEN_PITCH.
     */
    case GEN_PITCH:
    case GEN_COARSETUNE:
    case GEN_FINETUNE:
        /* The testing for allowed range is done in 'fluid_ct2hz' */
        voice->pitch = (fluid_voice_gen_value(voice, GEN_PITCH)
                        + 100.0f * fluid_voice_gen_value(voice, GEN_COARSETUNE)
                        + fluid_voice_gen_value(voice, GEN_FINETUNE));
        UPDATE_RVOICE_R1(fluid_rvoice_set_pitch, voice->pitch);
        break;

    case GEN_REVERBSEND:
        /* The generator unit is 'tenths of a percent'. */
        voice->reverb_send = x / 1000.0f;
        fluid_clip(voice->reverb_send, 0.f, 1.f);
        UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 2, fluid_voice_calculate_gain_amplitude(voice, voice->reverb_send));
        break;

    case GEN_CHORUSSEND:
        /* The generator unit is 'tenths of a percent'. */
        voice->chorus_send = x / 1000.0f;
        fluid_clip(voice->chorus_send, 0.f, 1.f);
        UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 3, fluid_voice_calculate_gain_amplitude(voice, voice->chorus_send));
        break;

    case GEN_OVERRIDEROOTKEY:

        /* This is a non-realtime parameter. Therefore the .mod part of the generator
         * can be neglected.
         * NOTE: origpitch sets MIDI root note while pitchadj is a fine tuning amount
         * which offsets the original rate.  This means that the fine tuning is
         * inverted with respect to the root note (so subtract it, not add).
         */
        if(voice->sample != NULL)
        {
            if(voice->gen[GEN_OVERRIDEROOTKEY].val > -1)    //FIXME: use flag instead of -1
            {
                voice->root_pitch = voice->gen[GEN_OVERRIDEROOTKEY].val * 100.0f
                                    - voice->sample->pitchadj;
            }
            else
            {
                voice->root_pitch = voice->sample->origpitch * 100.0f - voice->sample->pitchadj;
            }

            x = (fluid_ct2hz_real(voice->root_pitch) * ((fluid_real_t) voice->output_rate / voice->sample->samplerate));
        }
        else
        {
            if(voice->gen[GEN_OVERRIDEROOTKEY].val > -1)     //FIXME: use flag instead of -1
            {
                voice->root_pitch = voice->gen[GEN_OVERRIDEROOTKEY].val * 100.0f;
            }
            else
            {
                voice->root_pitch = 0;
            }

            x = fluid_ct2hz_real(voice->root_pitch);
        }

        /* voice->pitch depends on voice->root_pitch, so calculate voice->pitch now */
        fluid_voice_calculate_gen_pitch(voice);
        UPDATE_RVOICE_R1(fluid_rvoice_set_root_pitch_hz, x);

        break;

    case GEN_FILTERFC:
        /* The resonance frequency is converted from absolute cents to
         * midicents .val and .mod are both used, this permits real-time
         * modulation.  The allowed range is tested in the 'fluid_ct2hz'
         * function [PH,20021214]
         */
        UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_filter, x);
        break;

    case GEN_FILTERQ:
        UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_q, &voice->rvoice->resonant_filter, x);
        break;

    /* same as the two above, only for the custom filter */
    case GEN_CUSTOM_FILTERFC:
        UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_custom_filter, x);
        break;

    case GEN_CUSTOM_FILTERQ:
        UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_q, &voice->rvoice->resonant_custom_filter, x);
        break;

    case GEN_MODLFOTOPITCH:
        fluid_clip(x, -12000.f, 12000.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_pitch, x);
        break;

    case GEN_MODLFOTOVOL:
        fluid_clip(x, -960.f, 960.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_vol, x);
        break;

    case GEN_MODLFOTOFILTERFC:
        fluid_clip(x, -12000.f, 12000.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_fc, x);
        break;

    case GEN_MODLFODELAY:
        fluid_clip(x, -12000.0f, 5000.0f);
        z = (unsigned int)(voice->output_rate * fluid_tc2sec_delay(x));
        UPDATE_RVOICE_ENVLFO_I1(fluid_lfo_set_delay, modlfo, z);
        break;

    case GEN_MODLFOFREQ:
        /* - the frequency is converted into a delta value, per buffer of FLUID_BUFSIZE samples
         * - the delay into a sample delay
         */
        fluid_clip(x, -16000.0f, 4500.0f);
        x = (4.0f * FLUID_BUFSIZE * fluid_act2hz(x) / voice->output_rate);
        UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, modlfo, x);
        break;

    case GEN_VIBLFOFREQ:
        /* vib lfo
         *
         * - the frequency is converted into a delta value, per buffer of FLUID_BUFSIZE samples
         * - the delay into a sample delay
         */
        fluid_clip(x, -16000.0f, 4500.0f);
        x = 4.0f * FLUID_BUFSIZE * fluid_act2hz(x) / voice->output_rate;
        UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, viblfo, x);
        break;

    case GEN_VIBLFODELAY:
        fluid_clip(x, -12000.0f, 5000.0f);
        z = (unsigned int)(voice->output_rate * fluid_tc2sec_delay(x));
        UPDATE_RVOICE_ENVLFO_I1(fluid_lfo_set_delay, viblfo, z);
        break;

    case GEN_VIBLFOTOPITCH:
        fluid_clip(x, -12000.f, 12000.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_viblfo_to_pitch, x);
        break;

    case GEN_KEYNUM:
        /* GEN_KEYNUM: SF2.01 page 46, item 46
         *
         * If this generator is active, it forces the key number to its
         * value.  Non-realtime controller.
         *
         * There is a flag, which should indicate, whether a generator is
         * enabled or not.  But here we rely on the default value of -1.
         */

        /* 2017-09-02: do not change the voice's key here, otherwise it will
         * never be released on a noteoff event
         */
#if 0
        x = fluid_voice_gen_value(voice, GEN_KEYNUM);

        if(x >= 0)
        {
            voice->key = x;
        }

#endif
        break;

    case GEN_VELOCITY:
        /* GEN_VELOCITY: SF2.01 page 46, item 47
         *
         * If this generator is active, it forces the velocity to its
         * value. Non-realtime controller.
         *
         * There is a flag, which should indicate, whether a generator is
         * enabled or not. But here we rely on the default value of -1.
         */
        /* 2017-09-02: do not change the voice's velocity here, use
         * fluid_voice_get_actual_velocity() to get the value of this generator
         * if active.
         */
#if 0
        x = fluid_voice_gen_value(voice, GEN_VELOCITY);

        if(x > 0)
        {
            voice->vel = x;
        }

#endif
        break;

    case GEN_MODENVTOPITCH:
        fluid_clip(x, -12000.f, 12000.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_pitch, x);
        break;

    case GEN_MODENVTOFILTERFC:
        /* Range: SF2.01 section 8.1.3 # 1
         * Motivation for range checking:
         * Filter is reported to make funny noises now and then
         */
        fluid_clip(x, -12000.f, 12000.f);
        UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_fc, x);
        break;


    /* sample start and ends points
     *
     * Range checking is initiated via the
     * voice->check_sample_sanity flag,
     * because it is impossible to check here:
     * During the voice setup, all modulators are processed, while
     * the voice is inactive. Therefore, illegal settings may
     * occur during the setup (for example: First move the loop
     * end point ahead of the loop start point => invalid, then
     * move the loop start point forward => valid again.
     */
    case GEN_STARTADDROFS:              /* SF2.01 section 8.1.3 # 0 */
    case GEN_STARTADDRCOARSEOFS:        /* SF2.01 section 8.1.3 # 4 */
        if(voice->sample != NULL)
        {
            fluid_real_t start_fine = fluid_voice_gen_value(voice, GEN_STARTADDROFS);
            fluid_real_t start_coar = fluid_voice_gen_value(voice, GEN_STARTADDRCOARSEOFS);

            z = voice->sample->start + (int)start_fine + 32768 * (int)start_coar;
            UPDATE_RVOICE_I1(fluid_rvoice_set_start, z);
        }

        break;

    case GEN_ENDADDROFS:                 /* SF2.01 section 8.1.3 # 1 */
    case GEN_ENDADDRCOARSEOFS:           /* SF2.01 section 8.1.3 # 12 */
        if(voice->sample != NULL)
        {
            fluid_real_t end_fine = fluid_voice_gen_value(voice, GEN_ENDADDROFS);
            fluid_real_t end_coar = fluid_voice_gen_value(voice, GEN_ENDADDRCOARSEOFS);

            z = voice->sample->end + (int)end_fine + 32768 * (int)end_coar;
            UPDATE_RVOICE_I1(fluid_rvoice_set_end, z);
        }

        break;

    case GEN_STARTLOOPADDROFS:           /* SF2.01 section 8.1.3 # 2 */
    case GEN_STARTLOOPADDRCOARSEOFS:     /* SF2.01 section 8.1.3 # 45 */
        if(voice->sample != NULL)
        {
            fluid_real_t lstart_fine = fluid_voice_gen_value(voice, GEN_STARTLOOPADDROFS);
            fluid_real_t lstart_coar = fluid_voice_gen_value(voice, GEN_STARTLOOPADDRCOARSEOFS);

            z = voice->sample->loopstart + (int)lstart_fine + 32768 * (int)lstart_coar;
            UPDATE_RVOICE_I1(fluid_rvoice_set_loopstart, z);
        }

        break;

    case GEN_ENDLOOPADDROFS:             /* SF2.01 section 8.1.3 # 3 */
    case GEN_ENDLOOPADDRCOARSEOFS:       /* SF2.01 section 8.1.3 # 50 */
        if(voice->sample != NULL)
        {
            fluid_real_t lend_fine = fluid_voice_gen_value(voice, GEN_ENDLOOPADDROFS);
            fluid_real_t lend_coar = fluid_voice_gen_value(voice, GEN_ENDLOOPADDRCOARSEOFS);

            z = voice->sample->loopend + (int)lend_fine + 32768 * (int)lend_coar;
            UPDATE_RVOICE_I1(fluid_rvoice_set_loopend, z);
        }

        break;

        /* Conversion functions differ in range limit */
#define NUM_BUFFERS_DELAY(_v)   (unsigned int) (voice->output_rate * fluid_tc2sec_delay(_v) / FLUID_BUFSIZE)
#define NUM_BUFFERS_ATTACK(_v)  (unsigned int) (voice->output_rate * fluid_tc2sec_attack(_v) / FLUID_BUFSIZE)
#define NUM_BUFFERS_RELEASE(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_release(_v) / FLUID_BUFSIZE)

    /* volume envelope
     *
     * - delay and hold times are converted to absolute number of samples
     * - sustain is converted to its absolute value
     * - attack, decay and release are converted to their increment per sample
     */
    case GEN_VOLENVDELAY:                /* SF2.01 section 8.1.3 # 33 */
        fluid_clip(x, -12000.0f, 5000.0f);
        count = NUM_BUFFERS_DELAY(x);
        fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVDELAY,
                                  count, 0.0f, 0.0f, -1.0f, 1.0f);
        break;

    case GEN_VOLENVATTACK:               /* SF2.01 section 8.1.3 # 34 */
        fluid_clip(x, -12000.0f, 8000.0f);
        count = 1 + NUM_BUFFERS_ATTACK(x);
        fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVATTACK,
                                  count, 1.0f, 1.0f / count, -1.0f, 1.0f);
        break;

    case GEN_VOLENVHOLD:                 /* SF2.01 section 8.1.3 # 35 */
    case GEN_KEYTOVOLENVHOLD:            /* SF2.01 section 8.1.3 # 39 */
        count = calculate_hold_decay_buffers(voice, GEN_VOLENVHOLD, GEN_KEYTOVOLENVHOLD, 0); /* 0 means: hold */
        fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVHOLD,
                                  count, 1.0f, 0.0f, -1.0f, 2.0f);
        break;

    case GEN_VOLENVDECAY:               /* SF2.01 section 8.1.3 # 36 */
    case GEN_VOLENVSUSTAIN:             /* SF2.01 section 8.1.3 # 37 */
    case GEN_KEYTOVOLENVDECAY:          /* SF2.01 section 8.1.3 # 40 */
        x = 1.0f - 0.001f * fluid_voice_gen_value(voice, GEN_VOLENVSUSTAIN);
        fluid_clip(x, 0.0f, 1.0f);
        count = calculate_hold_decay_buffers(voice, GEN_VOLENVDECAY, GEN_KEYTOVOLENVDECAY, 1); /* 1 for decay */
        fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVDECAY,
                                  count, 1.0f, count ? -1.0f / count : 0.0f, x, 2.0f);
        break;

    case GEN_VOLENVRELEASE:             /* SF2.01 section 8.1.3 # 38 */
        fluid_clip(x, FLUID_MIN_VOLENVRELEASE, 8000.0f);
        count = 1 + NUM_BUFFERS_RELEASE(x);
        fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVRELEASE,
                                  count, 1.0f, -1.0f / count, 0.0f, 1.0f);
        break;

    /* Modulation envelope */
    case GEN_MODENVDELAY:               /* SF2.01 section 8.1.3 # 25 */
        fluid_clip(x, -12000.0f, 5000.0f);
        fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVDELAY,
                                  NUM_BUFFERS_DELAY(x), 0.0f, 0.0f, -1.0f, 1.0f);
        break;

    case GEN_MODENVATTACK:               /* SF2.01 section 8.1.3 # 26 */
        fluid_clip(x, -12000.0f, 8000.0f);
        count = 1 + NUM_BUFFERS_ATTACK(x);
        fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVATTACK,
                                  count, 1.0f, 1.0f / count, -1.0f, 1.0f);
        break;

    case GEN_MODENVHOLD:               /* SF2.01 section 8.1.3 # 27 */
    case GEN_KEYTOMODENVHOLD:          /* SF2.01 section 8.1.3 # 31 */
        count = calculate_hold_decay_buffers(voice, GEN_MODENVHOLD, GEN_KEYTOMODENVHOLD, 0); /* 1 means: hold */
        fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVHOLD,
                                  count, 1.0f, 0.0f, -1.0f, 2.0f);
        break;

    case GEN_MODENVDECAY:                                   /* SF 2.01 section 8.1.3 # 28 */
    case GEN_MODENVSUSTAIN:                                 /* SF 2.01 section 8.1.3 # 29 */
    case GEN_KEYTOMODENVDECAY:                              /* SF 2.01 section 8.1.3 # 32 */
        count = calculate_hold_decay_buffers(voice, GEN_MODENVDECAY, GEN_KEYTOMODENVDECAY, 1); /* 1 for decay */
        x = 1.0f - 0.001f * fluid_voice_gen_value(voice, GEN_MODENVSUSTAIN);
        fluid_clip(x, 0.0f, 1.0f);
        fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVDECAY,
                                  count, 1.0f, count ? -1.0f / count : 0.0f, x, 2.0f);
        break;

    case GEN_MODENVRELEASE:                                  /* SF 2.01 section 8.1.3 # 30 */
        fluid_clip(x, -12000.0f, 8000.0f);
        count = 1 + NUM_BUFFERS_RELEASE(x);
        fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVRELEASE,
                                  count, 1.0f, -1.0f / count, 0.0f, 2.0f);

        break;

    } /* switch gen */
}

/**
 * Recalculate voice parameters for a given control.
 * @param voice the synthesis voice
 * @param cc flag to distinguish between a continuous control and a channel control (pitch bend, ...)
 * @param ctrl the control number:
 *   when >=0, only modulators's destination having ctrl as source are updated.
 *   when -1, all modulators's destination are updated (regardless of ctrl).
 *
 * In this implementation, I want to make sure that all controllers
 * are event based: the parameter values of the DSP algorithm should
 * only be updates when a controller event arrived and not at every
 * iteration of the audio cycle (which would probably be feasible if
 * the synth was made in silicon).
 *
 * The update is done in two steps:
 *
 * - step 1: first, we look for all the modulators that have the changed
 * controller as a source. This will yield a generator that will be changed
 * because of the controller event.
 *
 * - step 2: For this generator, calculate its new value. This is the
 * sum of its original value plus the values of all the attached modulators.
 * The generator flag is set to indicate the parameters must be updated.
 * This avoid the risk to call 'fluid_voice_update_param' several
 * times for the same generator if several modulators have that generator as
 * destination. So every changed generators are updated only once.
 */

 /* bit table for each generator being updated. The bits are packed in variables
  Each variable have NBR_BIT_BY_VAR bits represented by NBR_BIT_BY_VAR_LN2.
  The size of the table is the number of variables: SIZE_UPDATED_GEN_BIT.
 
  Note: In this implementation NBR_BIT_BY_VAR_LN2 is set to 5 (convenient for 32 bits cpu)
  but this could be set to 6 for 64 bits cpu.
 */

#define NBR_BIT_BY_VAR_LN2 5	/* for 32 bits variables */
#define NBR_BIT_BY_VAR  (1 << NBR_BIT_BY_VAR_LN2)
#define NBR_BIT_BY_VAR_ANDMASK (NBR_BIT_BY_VAR - 1)
#define	SIZE_UPDATED_GEN_BIT  ((GEN_LAST + NBR_BIT_BY_VAR_ANDMASK) / NBR_BIT_BY_VAR)

#define is_gen_updated(bit,gen)  (bit[gen >> NBR_BIT_BY_VAR_LN2] &  (1 << (gen & NBR_BIT_BY_VAR_ANDMASK)))
#define set_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] |= (1 << (gen & NBR_BIT_BY_VAR_ANDMASK)))

int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl)
{
    int i, k;
    fluid_mod_t *mod;
    uint32_t gen;
    fluid_real_t modval;

    /* Clears registered bits table of updated generators */
    uint32_t updated_gen_bit[SIZE_UPDATED_GEN_BIT] = {0};

    /*    printf("Chan=%d, CC=%d, Src=%d, Val=%d\n", voice->channel->channum, cc, ctrl, val); */

    for(i = 0; i < voice->mod_count; i++)
    {
        mod = &voice->mod[i];

        /* step 1: find all the modulators that have the changed controller
           as input source. When ctrl is -1 all modulators destination
           are updated */
        if(ctrl < 0 || fluid_mod_has_source(mod, cc, ctrl))
        {
            gen = fluid_mod_get_dest(mod);

            /* Skip if this generator has already been updated */
            if(!is_gen_updated(updated_gen_bit, gen))
            {
                modval = 0.0;

                /* step 2: for every attached modulator, calculate the modulation
                 * value for the generator gen */
                for(k = 0; k < voice->mod_count; k++)
                {
                    if(fluid_mod_has_dest(&voice->mod[k], gen))
                    {
                        modval += fluid_mod_get_value(&voice->mod[k], voice);
                    }
                }

                fluid_gen_set_mod(&voice->gen[gen], modval);

                /* now recalculate the parameter values that are derived from the
                   generator */
                fluid_voice_update_param(voice, gen);

                /* set the bit that indicates this generator is updated */
                set_gen_updated(updated_gen_bit, gen);
            }
        }
    }

    return FLUID_OK;
}

/**
 * Update all the modulators. This function is called after a
 * ALL_CTRL_OFF MIDI message has been received (CC 121).
 *
 * All destination of all modulators must be updated.
 */
int fluid_voice_modulate_all(fluid_voice_t *voice)
{
    return fluid_voice_modulate(voice, 0, -1);
}

/** legato update functions --------------------------------------------------*/
/* Updates voice portamento parameters
 *
 * @voice voice the synthesis voice
 * @fromkey the beginning pitch of portamento.
 * @tokey the ending pitch of portamento.
 *
 * The function calculates pitch offset and increment, then these parameters
 * are send to the dsp.
*/
void fluid_voice_update_portamento(fluid_voice_t *voice, int fromkey, int tokey)

{
    fluid_channel_t *channel = voice->channel;

    /* calculates pitch offset */
    fluid_real_t PitchBeg = fluid_voice_calculate_pitch(voice, fromkey);
    fluid_real_t PitchEnd = fluid_voice_calculate_pitch(voice, tokey);
    fluid_real_t pitchoffset = PitchBeg - PitchEnd;

    /* Calculates increment countinc */
    /* Increment is function of PortamentoTime (ms)*/
    unsigned int countinc = (unsigned int)(((fluid_real_t)voice->output_rate *
                                            0.001f *
                                            (fluid_real_t)fluid_channel_portamentotime(channel))  /
                                           (fluid_real_t)FLUID_BUFSIZE  + 0.5f);

    /* Send portamento parameters to the voice dsp */
    UPDATE_RVOICE_GENERIC_IR(fluid_rvoice_set_portamento, voice->rvoice, countinc, pitchoffset);
}

/*---------------------------------------------------------------*/
/*legato mode 1: multi_retrigger
 *
 * Modulates all generators dependent of key,vel.
 * Forces the voice envelopes in the attack section (legato mode 1).
 *
 * @voice voice the synthesis voice
 * @tokey the new key to be applied to this voice.
 * @vel the new velocity to be applied to this voice.
 */
void fluid_voice_update_multi_retrigger_attack(fluid_voice_t *voice,
        int tokey, int vel)
{
    voice->key = tokey;  /* new note */
    voice->vel = vel; /* new velocity */
    /* Updates generators dependent of velocity */
    /* Modulates GEN_ATTENUATION (and others ) before calling
       fluid_rvoice_multi_retrigger_attack().*/
    fluid_voice_modulate(voice, FALSE, FLUID_MOD_VELOCITY);

    /* Updates generator dependent of voice->key */
    fluid_voice_update_param(voice, GEN_KEYTOMODENVHOLD);
    fluid_voice_update_param(voice, GEN_KEYTOMODENVDECAY);
    fluid_voice_update_param(voice, GEN_KEYTOVOLENVHOLD);
    fluid_voice_update_param(voice, GEN_KEYTOVOLENVDECAY);

    /* Updates pitch generator  */
    fluid_voice_calculate_gen_pitch(voice);
    fluid_voice_update_param(voice, GEN_PITCH);

    /* updates adsr generator */
    UPDATE_RVOICE0(fluid_rvoice_multi_retrigger_attack);
}
/** end of legato update functions */

/*
 Force the voice into release stage. Useful anywhere a voice
 needs to be damped even if pedals (sustain sostenuto) are depressed.
 See fluid_synth_damp_voices_by_sustain_LOCAL(),
 fluid_synth_damp_voices_by_sostenuto_LOCAL,
 fluid_voice_noteoff().
*/
void
fluid_voice_release(fluid_voice_t *voice)
{
    unsigned int at_tick = fluid_channel_get_min_note_length_ticks(voice->channel);
    UPDATE_RVOICE_I1(fluid_rvoice_noteoff, at_tick);
    voice->has_noteoff = 1; // voice is marked as noteoff occurred
}

/*
 * fluid_voice_noteoff
 *
 * Sending a noteoff event will advance the envelopes to section 5 (release).
 * The function is convenient for polyphonic or monophonic note
 */
void
fluid_voice_noteoff(fluid_voice_t *voice)
{
    fluid_channel_t *channel;

    fluid_profile(FLUID_PROF_VOICE_NOTE, voice->ref, 0, 0);

    channel = voice->channel;

    /* Sustain a note under Sostenuto pedal */
    if(fluid_channel_sostenuto(channel) &&
            channel->sostenuto_orderid > voice->id)
    {
        // Sostenuto depressed after note
        voice->status = FLUID_VOICE_HELD_BY_SOSTENUTO;
    }
    /* Or sustain a note under Sustain pedal */
    else if(fluid_channel_sustained(channel))
    {
        voice->status = FLUID_VOICE_SUSTAINED;
    }
    /* Or force the voice to release stage */
    else
    {
        fluid_voice_release(voice);
    }
}

/*
 * fluid_voice_kill_excl
 *
 * Percussion sounds can be mutually exclusive: for example, a 'closed
 * hihat' sound will terminate an 'open hihat' sound ringing at the
 * same time. This behaviour is modeled using 'exclusive classes',
 * turning on a voice with an exclusive class other than 0 will kill
 * all other voices having that exclusive class within the same preset
 * or channel.  fluid_voice_kill_excl gets called, when 'voice' is to
 * be killed for that reason.
 */

int
fluid_voice_kill_excl(fluid_voice_t *voice)
{

    unsigned int at_tick;

    if(!fluid_voice_is_playing(voice))
    {
        return FLUID_OK;
    }

    /* Turn off the exclusive class information for this voice,
       so that it doesn't get killed twice
    */
    fluid_voice_gen_set(voice, GEN_EXCLUSIVECLASS, 0);

    /* Speed up the volume envelope */
    /* The value was found through listening tests with hi-hat samples. */
    fluid_voice_gen_set(voice, GEN_VOLENVRELEASE, -200);
    fluid_voice_update_param(voice, GEN_VOLENVRELEASE);

    /* Speed up the modulation envelope */
    fluid_voice_gen_set(voice, GEN_MODENVRELEASE, -200);
    fluid_voice_update_param(voice, GEN_MODENVRELEASE);

    at_tick = fluid_channel_get_min_note_length_ticks(voice->channel);
    UPDATE_RVOICE_I1(fluid_rvoice_noteoff, at_tick);


    return FLUID_OK;
}

/*
 * Called by fluid_synth when the overflow rvoice can be reclaimed.
 */
void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice)
{
    voice->can_access_overflow_rvoice = 1;
    fluid_voice_sample_unref(&voice->overflow_rvoice->dsp.sample);
}

/*
 * fluid_voice_off
 *
 * Force the voice into finished stage. Useful anywhere a voice
 * needs to be cancelled from MIDI API.
 */
void fluid_voice_off(fluid_voice_t *voice)
{
    UPDATE_RVOICE0(fluid_rvoice_voiceoff); /* request to finish the voice */
}

/*
 * fluid_voice_stop
 *
 * Purpose:
 * Turns off a voice, meaning that it is not processed anymore by the
 * DSP loop, i.e. contrary part to fluid_voice_start().
 */
void
fluid_voice_stop(fluid_voice_t *voice)
{
    fluid_profile(FLUID_PROF_VOICE_RELEASE, voice->ref, 0, 0);

    voice->chan = NO_CHANNEL;

    if(voice->can_access_rvoice)
    {
        fluid_voice_sample_unref(&voice->rvoice->dsp.sample);
    }

    voice->status = FLUID_VOICE_OFF;
    voice->has_noteoff = 1;

    /* Decrement the reference count of the sample. */
    fluid_voice_sample_unref(&voice->sample);

    /* Decrement voice count */
    voice->channel->synth->active_voice_count--;
}

/**
 * Adds a modulator to the voice if the modulator has valid sources.
 * @param voice Voice instance.
 * @param mod Modulator info (copied).
 * @param mode Determines how to handle an existing identical modulator.
 *   #FLUID_VOICE_ADD to add (offset) the modulator amounts,
 *   #FLUID_VOICE_OVERWRITE to replace the modulator,
 *   #FLUID_VOICE_DEFAULT when adding a default modulator - no duplicate should
 *   exist so don't check.
 */
void
fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode)
{
    /* Ignore the modulator if its sources inputs are invalid */
    if(fluid_mod_check_sources(mod, "api fluid_voice_add_mod mod"))
    {
        fluid_voice_add_mod_local(voice, mod, mode, FLUID_NUM_MOD);
    }
}

/**
 * Adds a modulator to the voice.
 * local version of fluid_voice_add_mod function. Called at noteon time.
 * @param voice, mod, mode, same as for fluid_voice_add_mod() (see above).
 * @param check_limit_count is the modulator number limit to handle with existing
 *   identical modulator(i.e mode FLUID_VOICE_OVERWRITE, FLUID_VOICE_ADD).
 *   - When FLUID_NUM_MOD, all the voices modulators (since the previous call)
 *     are checked for identity.
 *   - When check_count_limit is below the actual number of voices modulators
 *   (voice->mod_count), this will restrict identity check to this number,
 *   This is useful when we know by advance that there is no duplicate with
 *   modulators at index above this limit. This avoid wasting cpu cycles at noteon.
 */
void
fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count)
{
    int i;

    /* check_limit_count cannot be above voice->mod_count */
    if(check_limit_count > voice->mod_count)
    {
        check_limit_count = voice->mod_count;
    }

    if(mode == FLUID_VOICE_ADD)
    {

        /* if identical modulator exists, add them */
        for(i = 0; i < check_limit_count; i++)
        {
            if(fluid_mod_test_identity(&voice->mod[i], mod))
            {
                //		printf("Adding modulator...\n");
                voice->mod[i].amount += mod->amount;
                return;
            }
        }

    }
    else if(mode == FLUID_VOICE_OVERWRITE)
    {

        /* if identical modulator exists, replace it (only the amount has to be changed) */
        for(i = 0; i < check_limit_count; i++)
        {
            if(fluid_mod_test_identity(&voice->mod[i], mod))
            {
                //		printf("Replacing modulator...amount is %f\n",mod->amount);
                voice->mod[i].amount = mod->amount;
                return;
            }
        }
    }

    /* Add a new modulator (No existing modulator to add / overwrite).
       Also, default modulators (FLUID_VOICE_DEFAULT) are added without
       checking, if the same modulator already exists. */
    if(voice->mod_count < FLUID_NUM_MOD)
    {
        fluid_mod_clone(&voice->mod[voice->mod_count++], mod);
    }
    else
    {
        FLUID_LOG(FLUID_WARN, "Voice %i has more modulators than supported, ignoring.", voice->id);
    }
}

/**
 * Get the unique ID of the noteon-event.
 * @param voice Voice instance
 * @return Note on unique ID
 *
 * A SoundFont loader may store the voice processes it has created for
 * real-time control during the operation of a voice (for example: parameter
 * changes in SoundFont editor). The synth uses a pool of voices, which are
 * 'recycled' and never deallocated.
 *
 * Before modifying an existing voice, check
 * - that its state is still 'playing'
 * - that the ID is still the same
 *
 * Otherwise the voice has finished playing.
 */
unsigned int fluid_voice_get_id(const fluid_voice_t *voice)
{
    return voice->id;
}

/**
 * Check if a voice is producing sound. This is also true after a voice received a noteoff as it may be playing in release phase.
 * @param voice Voice instance
 * @return TRUE if playing, FALSE otherwise
 */
int fluid_voice_is_playing(const fluid_voice_t *voice)
{
    return (voice->status == FLUID_VOICE_ON)
           || fluid_voice_is_sustained(voice)
           || fluid_voice_is_sostenuto(voice);

}

/**
 * Check if a voice is ON. A voice is ON, if it has not yet received a noteoff event.
 * @param voice Voice instance
 * @return TRUE if on, FALSE otherwise
 * @since 1.1.7
 */
int fluid_voice_is_on(const fluid_voice_t *voice)
{
    return (voice->status == FLUID_VOICE_ON && !voice->has_noteoff);
}

/**
 * Check if a voice keeps playing after it has received a noteoff due to being held by sustain.
 * @param voice Voice instance
 * @return TRUE if sustained, FALSE otherwise
 * @since 1.1.7
 */
int fluid_voice_is_sustained(const fluid_voice_t *voice)
{
    return (voice->status == FLUID_VOICE_SUSTAINED);
}

/**
 * Check if a voice keeps playing after it has received a noteoff due to being held by sostenuto.
 * @param voice Voice instance
 * @return TRUE if sostenuto, FALSE otherwise
 * @since 1.1.7
 */
int fluid_voice_is_sostenuto(const fluid_voice_t *voice)
{
    return (voice->status == FLUID_VOICE_HELD_BY_SOSTENUTO);
}

/**
 * If the voice is playing, gets the midi channel the voice is playing on. Else the result is undefined.
 * @param voice Voice instance
 * @return The channel assigned to this voice
 * @since 1.1.7
 */
int fluid_voice_get_channel(const fluid_voice_t *voice)
{
    return voice->chan;
}

/**
 * If the voice is playing, gets the midi key the voice is actually playing at. Else the result is undefined.
 * If the voice was started from an instrument which uses a fixed key generator, it returns that.
 * Else returns the same as \c fluid_voice_get_key.
 * @param voice Voice instance
 * @return The midi key this voice is playing at
 * @since 1.1.7
 */
int fluid_voice_get_actual_key(const fluid_voice_t *voice)
{
    fluid_real_t x = fluid_voice_gen_value(voice, GEN_KEYNUM);

    if(x >= 0)
    {
        return (int)x;
    }
    else
    {
        return fluid_voice_get_key(voice);
    }
}

/**
 * If the voice is playing, gets the midi key from the noteon event, by which the voice was initially turned on with.
 * Else the result is undefined.
 * @param voice Voice instance
 * @return The midi key of the noteon event that originally turned on this voice
 * @since 1.1.7
 */
int fluid_voice_get_key(const fluid_voice_t *voice)
{
    return voice->key;
}

/**
 * If the voice is playing, gets the midi velocity the voice is actually playing at. Else the result is undefined.
 * If the voice was started from an instrument which uses a fixed velocity generator, it returns that.
 * Else returns the same as \c fluid_voice_get_velocity.
 * @param voice Voice instance
 * @return The midi velocity this voice is playing at
 * @since 1.1.7
 */
int fluid_voice_get_actual_velocity(const fluid_voice_t *voice)
{
    fluid_real_t x = fluid_voice_gen_value(voice, GEN_VELOCITY);

    if(x > 0)
    {
        return (int)x;
    }
    else
    {
        return fluid_voice_get_velocity(voice);
    }
}

/**
 * If the voice is playing, gets the midi velocity from the noteon event, by which the voice was initially
 * turned on with. Else the result is undefined.
 * @param voice Voice instance
 * @return The midi velocity which originally turned on this voice
 * @since 1.1.7
 */
int fluid_voice_get_velocity(const fluid_voice_t *voice)
{
    return voice->vel;
}

/*
 * fluid_voice_get_lower_boundary_for_attenuation
 *
 * Purpose:
 *
 * A lower boundary for the attenuation (as in 'the minimum
 * attenuation of this voice, with volume pedals, modulators
 * etc. resulting in minimum attenuation, cannot fall below x cB) is
 * calculated.  This has to be called during fluid_voice_start, after
 * all modulators have been run on the voice once.  Also,
 * voice->attenuation has to be initialized.
 * (see fluid_voice_calculate_runtime_synthesis_parameters())
 */
static fluid_real_t
fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice)
{
    int i;
    fluid_mod_t *mod;
    fluid_real_t possible_att_reduction_cB = 0;
    fluid_real_t lower_bound;

    for(i = 0; i < voice->mod_count; i++)
    {
        mod = &voice->mod[i];

        /* Modulator has attenuation as target and can change over time? */
        if((mod->dest == GEN_ATTENUATION)
                && ((mod->flags1 & FLUID_MOD_CC)
                    || (mod->flags2 & FLUID_MOD_CC)
                    || (mod->src1 == FLUID_MOD_CHANNELPRESSURE)
                    || (mod->src1 == FLUID_MOD_KEYPRESSURE)
                    || (mod->src1 == FLUID_MOD_PITCHWHEEL)
                    || (mod->src2 == FLUID_MOD_CHANNELPRESSURE)
                    || (mod->src2 == FLUID_MOD_KEYPRESSURE)
                    || (mod->src2 == FLUID_MOD_PITCHWHEEL)))
        {

            fluid_real_t current_val = fluid_mod_get_value(mod, voice);
            /* min_val is the possible minimum value for this modulator.
               it depends of 3 things :
               1)the minimum values of src1,src2 (i.e -1 if mapping is bipolar
                 or 0 if mapping is unipolar).
               2)the sign of amount.
               3)absolute value of amount.

               When at least one source mapping is bipolar:
			     min_val is -|amount| regardless the sign of amount.
               When both sources mapping are unipolar:
                 min_val is -|amount|, if amount is negative.
                 min_val is 0, if amount is positive
             */
            fluid_real_t min_val = fabs(mod->amount);

            /* Can this modulator produce a negative contribution? */
            if((mod->flags1 & FLUID_MOD_BIPOLAR)
                    || (mod->flags2 & FLUID_MOD_BIPOLAR)
                    || (mod->amount < 0))
            {
                min_val = -min_val; /* min_val = - |amount|*/
            }
            else
            {
                /* No negative value possible. But still, the minimum contribution is 0. */
                min_val = 0;
            }

            /* For example:
             * - current_val=100
             * - min_val=-4000
             * - possible reduction contribution of this modulator = current_val - min_val = 4100
             */
            if(current_val > min_val)
            {
                possible_att_reduction_cB += (current_val - min_val);
            }
        }
    }

    lower_bound = voice->attenuation - possible_att_reduction_cB;

    /* SF2.01 specs do not allow negative attenuation */
    if(lower_bound < 0)
    {
        lower_bound = 0;
    }

    return lower_bound;
}




int fluid_voice_set_param(fluid_voice_t *voice, int gen, fluid_real_t nrpn_value)
{
    voice->gen[gen].nrpn = nrpn_value;
    voice->gen[gen].flags = GEN_SET;
    fluid_voice_update_param(voice, gen);
    return FLUID_OK;
}

int fluid_voice_set_gain(fluid_voice_t *voice, fluid_real_t gain)
{
    fluid_real_t left, right, reverb, chorus;

    /* avoid division by zero*/
    if(gain < 0.0000001f)
    {
        gain = 0.0000001f;
    }

    voice->synth_gain = gain;
    left = fluid_voice_calculate_gain_amplitude(voice,
            fluid_pan(voice->pan, 1) * fluid_balance(voice->balance, 1));
    right = fluid_voice_calculate_gain_amplitude(voice,
            fluid_pan(voice->pan, 0) * fluid_balance(voice->balance, 0));
    reverb = fluid_voice_calculate_gain_amplitude(voice, voice->reverb_send);
    chorus = fluid_voice_calculate_gain_amplitude(voice, voice->chorus_send);

    UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, gain);
    UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 0, left);
    UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 1, right);
    UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 2, reverb);
    UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 3, chorus);

    return FLUID_OK;
}

/* - Scan the loop
 * - determine the peak level
 * - Calculate, what factor will make the loop inaudible
 * - Store in sample
 */
/**
 * Calculate the peak volume of a sample for voice off optimization.
 * @param s Sample to optimize
 * @return #FLUID_OK on success, #FLUID_FAILED otherwise
 *
 * If the peak volume during the loop is known, then the voice can
 * be released earlier during the release phase. Otherwise, the
 * voice will operate (inaudibly), until the envelope is at the
 * nominal turnoff point.  So it's a good idea to call
 * fluid_voice_optimize_sample() on each sample once.
 */
int
fluid_voice_optimize_sample(fluid_sample_t *s)
{
    int32_t peak_max = 0;
    int32_t peak_min = 0;
    int32_t peak;
    fluid_real_t normalized_amplitude_during_loop;
    double result;
    unsigned int i;

    /* ignore disabled samples */
    if(s->start == s->end)
    {
        return (FLUID_OK);
    }

    if(!s->amplitude_that_reaches_noise_floor_is_valid)    /* Only once */
    {
        /* Scan the loop */
        for(i = s->loopstart; i < s->loopend; i++)
        {
            int32_t val = fluid_rvoice_get_sample(s->data, s->data24, i);

            if(val > peak_max)
            {
                peak_max = val;
            }
            else if(val < peak_min)
            {
                peak_min = val;
            }
        }

        /* Determine the peak level */
        if(peak_max > -peak_min)
        {
            peak = peak_max;
        }
        else
        {
            peak = -peak_min;
        }

        if(peak == 0)
        {
            /* Avoid division by zero */
            peak = 1;
        }

        /* Calculate what factor will make the loop inaudible
         * For example: Take a peak of 3277 (10 % of 32768).  The
         * normalized amplitude is 0.1 (10 % of 32768).  An amplitude
         * factor of 0.0001 (as opposed to the default 0.00001) will
         * drop this sample to the noise floor.
         */

        /* 16 bits => 96+4=100 dB dynamic range => 0.00001 */
        normalized_amplitude_during_loop = ((fluid_real_t)peak) / (INT24_MAX * 1.0f);
        result = FLUID_NOISE_FLOOR / normalized_amplitude_during_loop;

        /* Store in sample */
        s->amplitude_that_reaches_noise_floor = (double)result;
        s->amplitude_that_reaches_noise_floor_is_valid = 1;
#if 0
        printf("Sample peak detection: factor %f\n", (double)result);
#endif
    }

    return FLUID_OK;
}

float
fluid_voice_get_overflow_prio(fluid_voice_t *voice,
                              fluid_overflow_prio_t *score,
                              unsigned int cur_time)
{
    float this_voice_prio = 0;
    int channel;

    /* Are we already overflowing? */
    if(!voice->can_access_overflow_rvoice)
    {
        return OVERFLOW_PRIO_CANNOT_KILL;
    }

    /* Is this voice on the drum channel?
     * Then it is very important.
     * Also skip the released and sustained scores.
     */
    if(voice->channel->channel_type == CHANNEL_TYPE_DRUM)
    {
        this_voice_prio += score->percussion;
    }
    else if(voice->has_noteoff)
    {
        /* Noteoff has */
        this_voice_prio += score->released;
    }
    else if(fluid_voice_is_sustained(voice) || fluid_voice_is_sostenuto(voice))
    {
        /* This voice is still active, since the sustain pedal is held down.
         * Consider it less important than non-sustained channels.
         * This decision is somehow subjective. But usually the sustain pedal
         * is used to play 'more-voices-than-fingers', so it shouldn't hurt
         * if we kill one voice.
         */
        this_voice_prio += score->sustained;
    }

    /* We are not enthusiastic about releasing voices, which have just been started.
     * Otherwise hitting a chord may result in killing notes belonging to that very same
     * chord. So give newer voices a higher score. */
    if(score->age)
    {
        cur_time -= voice->start_time;

        if(cur_time < 1)
        {
            cur_time = 1; // Avoid div by zero
        }

        this_voice_prio += (score->age * voice->output_rate) / cur_time;
    }

    /* take a rough estimate of loudness into account. Louder voices are more important. */
    if(score->volume)
    {
        fluid_real_t a = voice->attenuation;

        if(voice->has_noteoff)
        {
            // FIXME: Should take into account where on the envelope we are...?
        }

        if(a < 0.1f)
        {
            a = 0.1f; // Avoid div by zero
        }

        this_voice_prio += score->volume / a;
    }

    /* Check if this voice is on an important channel. If so, then add the
     * score for important channels */
    channel = fluid_voice_get_channel(voice);

    if(channel < score->num_important_channels && score->important_channels[channel])
    {
        this_voice_prio += score->important;
    }

    return this_voice_prio;
}


void fluid_voice_set_custom_filter(fluid_voice_t *voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags)
{
    UPDATE_RVOICE_GENERIC_I2(fluid_iir_filter_init, &voice->rvoice->resonant_custom_filter, type, flags);
}

