    /* 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 Library General Public License
     * as published by the Free Software Foundation; either version 2 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
     * Library General Public License for more details.
     *
     * You should have received a copy of the GNU Library General Public
     * License along with this library; if not, write to the Free
     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
     * 02111-1307, USA
     */

    #include "conv.h"
    #include "fluid.h"
    #include "sfont.h"
    #include "gen.h"
    #include "voice.h"

    namespace FluidS {

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

    #define fluid_clip(_val, _min, _max) \
       { (_val) = ((_val) < (_min))? (_min) : (((_val) > (_max))? (_max) : (_val)); }

    /* 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

    /* Smallest amplitude that can be perceived (full scale is +/- 0.5)
     * 16 bits => 96+4=100 dB dynamic range => 0.00001
     * 0.00001 * 2 is approximately 0.00003 :)
     */
    #define FLUID_NOISE_FLOOR 0.00003

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

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

    //---------------------------------------------------------
    //   triangle - calc value of triangle function for lfos
    //---------------------------------------------------------

    float triangle(int dur,int pos) {
          pos += dur/4;
          pos %= dur;
          if (pos>dur/2)
                return 2*(0.5-((pos/(0.5*dur))-1));
          else
                return 2*((pos/(0.5*dur))-0.5);
          }

    //---------------------------------------------------------
    //  samplesToNextTurningPoint
    //  calculate how many samples it is to the next change
    //  from rising to falling in a triangle function
    //---------------------------------------------------------

    int samplesToNextTurningPoint(int dur, int pos) {
          pos += dur/4;
          return ((dur/2)-(pos%(dur/2))) % (dur/2);
          }

    //---------------------------------------------------------
    //   Voice
    //---------------------------------------------------------

    Voice::Voice(Fluid* f)
          {
          _fluid  = f;
          status  = FLUID_VOICE_OFF;
          chan    = NO_CHANNEL;
          key     = 0;
          vel     = 0;
          channel = 0;
          sample  = 0;

          /* 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.
           */
          volenv_data[FLUID_VOICE_ENVSUSTAIN].count = 0xffffffff;
          volenv_data[FLUID_VOICE_ENVSUSTAIN].coeff = 1.0f;
          volenv_data[FLUID_VOICE_ENVSUSTAIN].incr  = 0.0f;
          volenv_data[FLUID_VOICE_ENVSUSTAIN].min   = -1.0f;
          volenv_data[FLUID_VOICE_ENVSUSTAIN].max   = 2.0f;

          volenv_data[FLUID_VOICE_ENVFINISHED].count = 0xffffffff;
          volenv_data[FLUID_VOICE_ENVFINISHED].coeff = 0.0f;
          volenv_data[FLUID_VOICE_ENVFINISHED].incr  = 0.0f;
          volenv_data[FLUID_VOICE_ENVFINISHED].min   = -1.0f;
          volenv_data[FLUID_VOICE_ENVFINISHED].max   = 1.0f;

          modenv_data[FLUID_VOICE_ENVSUSTAIN].count = 0xffffffff;
          modenv_data[FLUID_VOICE_ENVSUSTAIN].coeff = 1.0f;
          modenv_data[FLUID_VOICE_ENVSUSTAIN].incr  = 0.0f;
          modenv_data[FLUID_VOICE_ENVSUSTAIN].min   = -1.0f;
          modenv_data[FLUID_VOICE_ENVSUSTAIN].max   = 2.0f;

          modenv_data[FLUID_VOICE_ENVFINISHED].count = 0xffffffff;
          modenv_data[FLUID_VOICE_ENVFINISHED].coeff = 0.0f;
          modenv_data[FLUID_VOICE_ENVFINISHED].incr  = 0.0f;
          modenv_data[FLUID_VOICE_ENVFINISHED].min   = -1.0f;
          modenv_data[FLUID_VOICE_ENVFINISHED].max   = 1.0f;
          }

    //---------------------------------------------------------
    //   init
    //    Initialize the synthesis process
    //---------------------------------------------------------

    void Voice::init(Sample* _sample, Channel* _channel, int _key, int _vel,
       unsigned int _id, double tuning)
          {
          // 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.

          id             = _id;
          _noteTuning    = tuning;
          chan           = _channel->getNum();
          key            = _key;
          vel            = _vel;
          channel        = _channel;
          mod_count      = 0;
          sample         = _sample;
          ticks          = 0;
          debug          = 0;
          has_looped     = false; // Will be set during voice_write when the 2nd loop point is reached
          last_fres      = -1;    // The filter coefficients have to be calculated later in the DSP loop.
          filter_startup = 1;     // Set the filter immediately, don't fade between old and new settings
          interp_method  = _channel->getInterpMethod();

          // vol env initialization
          volenv_count   = 0;
          volenv_section = 0;
          volenv_val     = 0.0f;
          amp            = 0.0f;  // The last value of the volume envelope, used to
                                  // calculate the volume increment during
                                  // processing

          // mod env initialization
          modenv_count   = 0;
          modenv_section = 0;
          modenv_val     = 0.0f;

          /* mod lfo */
          modlfo_val = 0.0;       // Fixme: Retrieve from any other existing
                                  // voice on this channel to keep LFOs in
                                  // unison?
          modlfo_pos = 0;

          /* vib lfo */
          viblfo_val = 0.0f;      // Fixme: See mod lfo

          /* Clear sample history in filter */
          hist1 = 0;
          hist2 = 0;

          /* 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 calculate_runtime_synthesis_parameters.
           */
          fluid_gen_init(&gen[0], channel);

         /* 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.
          */

          amplitude_that_reaches_noise_floor_nonloop = FLUID_NOISE_FLOOR;
          amplitude_that_reaches_noise_floor_loop    = FLUID_NOISE_FLOOR;
          }

    //---------------------------------------------------------
    //   gen_set
    //---------------------------------------------------------

    void Voice::gen_set(int i, float val)
          {
          gen[i].val   = val;
          gen[i].flags = GEN_SET;
          }

    //---------------------------------------------------------
    //   gen_incr
    //---------------------------------------------------------

    void Voice::gen_incr(int i, float val)
          {
          gen[i].val += val;
          gen[i].flags = GEN_SET;
          }

    //---------------------------------------------------------
    //   gen_get
    //---------------------------------------------------------

    float Voice::gen_get(int g)
          {
          return gen[g].val;
          }

    inline void Voice::calcVolEnv(int n, fluid_env_data_t *env_data)
          {
          float x;
          /* calculate the envelope value and check for valid range */
          x = env_data->coeff * volenv_val + env_data->incr * n;
          if (x < env_data->min)
                x = env_data->min;
          else if (x > env_data->max)
                x = env_data->max;
          volenv_val = x;
          }

    //-----------------------------------------------------------------------------
    // write
    //
    // This is where it all happens. This function is called by the
    // synthesizer to generate the sound samples. The synthesizer passes
    // four audio buffers: left, right, reverb out, and chorus out.
    //
    // The biggest part of this function sets the correct values for all
    // the dsp parameters (all the control data boil down to only a few
    // dsp parameters). The dsp routine is #included in several places (fluid_dsp_core.c).
    //-----------------------------------------------------------------------------

    void Voice::write(unsigned n, float* out, float* reverb, float* chorus)
          {
          /* make sure we're playing and that we have sample data */
          if (!PLAYING())
                return;
          if (!sample) {
                printf("!sample\n");
                off();
                return;
                }

          qreal target_amp;    /* target amplitude */
          fluid_env_data_t* env_data;
          float x;
          float _fres;

          /* Range checking for sample- and loop-related parameters
           * Initial phase is calculated here*/
          check_sample_sanity();

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

          env_data = &volenv_data[volenv_section];
          Sample2AmpInc.clear();
          std::map<int, int> sample2VolEnvSection;
          std::set<int> volumeChanges;

          int restN = n;

          if (volenv_section >= FLUID_VOICE_ENVFINISHED) {
                off();
                return;
                }

          unsigned int curVolEnvCount = volenv_count;

          // determine points where volume envelope changes
          while (curVolEnvCount+restN >= env_data->count) {
                restN -= env_data->count - curVolEnvCount;

                sample2VolEnvSection.insert(std::pair<int, int>(n-restN, volenv_section));
                volumeChanges.insert(n-restN);

                curVolEnvCount = 0;
                volenv_section++;

                env_data = &volenv_data[volenv_section];
                }

          sample2VolEnvSection.insert(std::pair<int, int>(n, volenv_section));
          volumeChanges.insert(n);

          fluid_check_fpe ("voice_write vol env");

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

          // if we wouldn't calculate sample accurate volume
          // we wouldn't advance beyond FLUID_VOICE_ENVDELAY
          // and effectively always skip the first buffer rendering thus adding n to modenv_count
          if (volenv_section > FLUID_VOICE_ENVDELAY)
                modenv_count += n;

          env_data = &modenv_data[modenv_section];

          /* skip to the next section of the envelope if necessary */
          while (modenv_count >= env_data->count) {
                env_data = &modenv_data[++modenv_section];
                modenv_count = 0;
                }

          /* calculate the envelope value and check for valid range */
          x = env_data->coeff * modenv_val + env_data->incr * n;

          if (x < env_data->min) {
                x = env_data->min;
                modenv_section++;
                modenv_count = 0;
                }
          else if (x > env_data->max) {
                x = env_data->max;
                modenv_section++;
                modenv_count = 0;
                }

          modenv_val = x;
          modenv_count += n;
          fluid_check_fpe ("voice_write mod env");

          /******************* mod lfo **********************/
          // calculate all points where we need to consider
          // the mod lfo (where it changes its slope)

          int modLfoStart = -1;

          if (fabs(modlfo_to_vol) > 0) {
                if (ticks >= modlfo_delay)
                      modLfoStart = 0;
                else if (n >= modlfo_delay)
                      modLfoStart = modlfo_delay;

                if (modLfoStart >= 0) {
                      if (modLfoStart > 0)
                            volumeChanges.insert(modLfoStart);

                      unsigned int modLfoNextTurn = samplesToNextTurningPoint(modlfo_dur, modlfo_pos);

                      while (modLfoNextTurn+modLfoStart < n) {
                            volumeChanges.insert(modLfoNextTurn+modLfoStart);
                            modLfoNextTurn++;
                            modLfoNextTurn += samplesToNextTurningPoint(modlfo_dur, modLfoNextTurn);
                            }
                      }
                }

          fluid_check_fpe ("voice_write mod LFO");

          /******************* vib lfo **********************/

          if (ticks >= viblfo_delay) {
                viblfo_val += viblfo_incr * n;

                if (viblfo_val > (float) 1.0) {
                      viblfo_incr = -viblfo_incr;
                      viblfo_val = (float) 2.0 - viblfo_val;
                      }
                else if (viblfo_val < -1.0) {
                      viblfo_incr = -viblfo_incr;
                      viblfo_val = (float) -2.0 - viblfo_val;
                      }
                }

          fluid_check_fpe ("voice_write Vib LFO");

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

          if (volenv_section == FLUID_VOICE_ENVDELAY) {
                ticks += n;
                return;     /* The volume amplitude is in hold phase. No sound is produced. */
                }

          qreal oldTargetAmp = amp;
          int lastPos = 0;
          auto oldVolEnvSection = sample2VolEnvSection.begin();
          auto curVolEnvSection = oldVolEnvSection;

          for (auto curPos : volumeChanges)
                {
                if (modLfoStart >= 0 && curPos >= modLfoStart)
                      modlfo_val = triangle(modlfo_dur, modlfo_pos+curPos-modLfoStart);
                else
                      modlfo_val = 0;

                // never calculate anything for the very first sample
                // everything should have been calculated in the last
                // cycle - it would also cause a divion by zero later
                if (curPos == 0) {
                      curPos = 1;

                      // if we should calulate for position 1 already make sure we don't do it twice
                      // could lead to curPos==lastPos which causes devision by zero
                      if (volumeChanges.find(1) != volumeChanges.end())
                            volumeChanges.erase(volumeChanges.find(1));
                      }

                // just go to the next volume section if we're below last volume point
                if (curPos >= curVolEnvSection->first && (unsigned int) curVolEnvSection->first < n)
                      curVolEnvSection++;

                volenv_count += curPos-lastPos;
                calcVolEnv(curPos-lastPos, &volenv_data[oldVolEnvSection->second]);

                volenv_section = oldVolEnvSection->second;

                if (volenv_section <= 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_atten2amp (attenuation)
                         * fluid_cb2amp (modlfo_val * -modlfo_to_vol)
                         * volenv_val;
                      }
                else {
                      //float amplitude_that_reaches_noise_floor;
                      //float amp_max;

                      target_amp = fluid_atten2amp (attenuation)
                         * fluid_cb2amp (960.0f * (1.0f - volenv_val)
                         + modlfo_val * -modlfo_to_vol);

                      /* 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
                       */

                       float amplitude_that_reaches_noise_floor;
                       /* Is the playing pointer already in the loop? */
                       if (has_looped)
                             amplitude_that_reaches_noise_floor = amplitude_that_reaches_noise_floor_loop;
                       else
                             amplitude_that_reaches_noise_floor = 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):
                        */

                       float amp_max = fluid_atten2amp (min_attenuation_cB) * volenv_val;

                       /* 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) {
                             positionToTurnOff = curPos;
                             }

                      }

                if (curVolEnvSection->second != oldVolEnvSection->second) {
                      if (oldVolEnvSection->second == FLUID_VOICE_ENVDECAY) {
                            env_data = &volenv_data[oldVolEnvSection->second];
                            volenv_val = env_data->min * env_data->coeff;
                            }
                      volenv_count = 0;
                      oldVolEnvSection = curVolEnvSection;
                      }
                /* Volume increment to go from voice->amp to target_amp in FLUID_BUFSIZE steps */
                amp_incr = (target_amp - oldTargetAmp) / (curPos - lastPos);
                lastPos = curPos;
                Sample2AmpInc.insert(std::pair<int, qreal>(curPos, amp_incr));

                // if voice is turned off after this no need to calculate any more values
                if (positionToTurnOff > 0)
                      break;

                oldTargetAmp = target_amp;
                }

          if (modLfoStart >= 0) {
                modlfo_pos += n-modLfoStart;
                modlfo_val = triangle(modlfo_dur, modlfo_pos-modLfoStart);
                }

          fluid_check_fpe ("voice_write amplitude calculation");

          /* 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.*/

          {
          float cent = pitch + modlfo_val * modlfo_to_pitch
                       + viblfo_val * viblfo_to_pitch
                       + modenv_val * modenv_to_pitch;
          phase_incr = _fluid->ct2hz_real(cent) / root_pitch;
          }

          /* if phase_incr is not advancing, set it to the minimum fraction value (prevent stuckage) */
          if (phase_incr == 0)
                phase_incr = 1;

          /*************** resonant filter ******************/

          /* calculate the frequency of the resonant filter in Hz */
          _fres = _fluid->ct2hz_real(fres
                           + modlfo_val * modlfo_to_fc
                           + modenv_val * modenv_to_fc);

            /* FIXME - Still potential for a click during turn on, can we interpolate
               between 20khz cutoff and 0 Q? */

            /* I removed the optimization of turning the filter off when the
             * resonance frequence is above the maximum frequency. Instead, the
             * filter frequency is set to a maximum of 0.45 times the sampling
             * rate. For a 44100 kHz sampling rate, this amounts to 19845
             * Hz. The reason is that there were problems with anti-aliasing when the
             * synthesizer was run at lower sampling rates. Thanks to Stephan
             * Tassart for pointing me to this bug. By turning the filter on and
             * clipping the maximum filter frequency at 0.45*srate, the filter
             * is used as an anti-aliasing filter. */

          if (_fres > 0.45f * _fluid->sample_rate)
                _fres = 0.45f * _fluid->sample_rate;
          else if (_fres < 5)
                _fres = 5;

          /* if filter enabled and there is a significant frequency change.. */
          if ((qAbs(_fres - last_fres) > 0.01)) {
                    /* The filter coefficients have to be recalculated (filter
                    * parameters have changed). Recalculation for various reasons is
                    * forced by setting last_fres to -1.  The flag filter_startup
                    * indicates, that the DSP loop runs for the first time, in this
                    * case, the filter is set directly, instead of smoothly fading
                    * between old and new settings.
                    *
                    * Those equations from Robert Bristow-Johnson's `Cookbook
                    * formulae for audio EQ biquad filter coefficients', obtained
                    * from Harmony-central.com / Computer / Programming. They are
                    * the result of the bilinear transform on an analogue filter
                    * prototype. To quote, `BLT frequency warping has been taken
                    * into account for both significant frequency relocation and for
                    * bandwidth readjustment'. */

                float omega = (float) (2.0 * M_PI * (_fres / ((float) _fluid->sample_rate)));
                float sin_coeff = (float) sin(omega);
                float cos_coeff = (float) cos(omega);
                float alpha_coeff = sin_coeff / (2.0f * q_lin);
                float a0_inv = 1.0f / (1.0f + alpha_coeff);

                   /* Calculate the filter coefficients. All coefficients are
                    * normalized by a0. Think of `a1' as `a1/a0'.
                    *
                    * Here a couple of multiplications are saved by reusing common expressions.
                    * The original equations should be:
                    *  b0=(1.-cos_coeff)*a0_inv*0.5*voice->filter_gain;
                    *  b1=(1.-cos_coeff)*a0_inv*voice->filter_gain;
                    *  b2=(1.-cos_coeff)*a0_inv*0.5*voice->filter_gain; */

                float a1_temp = -2.0f * cos_coeff * a0_inv;
                float a2_temp = (1.0f - alpha_coeff) * a0_inv;
                float b1_temp = (1.0f - cos_coeff) * a0_inv * filter_gain;
                /* both b0 -and- b2 */
                float b02_temp = b1_temp * 0.5f;

                if (filter_startup) {
                      /* The filter is calculated, because the voice was started up.
                      * In this case set the filter coefficients without delay.
                      */
                      a1 = a1_temp;
                      a2 = a2_temp;
                      b02 = b02_temp;
                      b1 = b1_temp;
                      filter_coeff_incr_count = 0;
                      filter_startup = 0;
                      //       printf("Setting initial filter coefficients.\n");
                         }
                   else {
                            /* The filter frequency is changed.  Calculate an increment
                             * factor, so that the new setting is reached after one buffer
                             * length. x_incr is added to the current value FLUID_BUFSIZE
                             * times. The length is arbitrarily chosen. Longer than one
                             * buffer will sacrifice some performance, though.  Note: If
                             * the filter is still too 'grainy', then increase this number
                             * at will.
                             */

                      #define FILTER_TRANSITION_SAMPLES 64     // (FLUID_BUFSIZE)

                            a1_incr = (a1_temp - a1) / FILTER_TRANSITION_SAMPLES;
                            a2_incr = (a2_temp - a2) / FILTER_TRANSITION_SAMPLES;
                            b02_incr = (b02_temp - b02) / FILTER_TRANSITION_SAMPLES;
                            b1_incr = (b1_temp - b1) / FILTER_TRANSITION_SAMPLES;
                            /* Have to add the increments filter_coeff_incr_count times. */
                            filter_coeff_incr_count = FILTER_TRANSITION_SAMPLES;
                          }
                    last_fres = _fres;
                    fluid_check_fpe ("voice_write filter calculation");
                  }

            fluid_check_fpe ("voice_write DSP 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. */

          float l_dsp_buf[n];
          dsp_buf = l_dsp_buf;
          memset(dsp_buf, 0, n*sizeof(float)); // init the arry with zeros so we can skip silent parts
          unsigned count;
          switch (interp_method) {
                case FLUID_INTERP_NONE:
                      count = dsp_float_interpolate_none(n);
                      break;
                case FLUID_INTERP_LINEAR:
                      count = dsp_float_interpolate_linear(n);
                      break;
                case FLUID_INTERP_4THORDER:
                default:
                      count = dsp_float_interpolate_4th_order(n);
                      break;
                case FLUID_INTERP_7THORDER:
                      count = dsp_float_interpolate_7th_order(n);
                      break;
                }

          if (count > 0)
                effects(count, out, reverb, chorus);

          /* turn off voice if short count (sample ended and not looping) or voice reached noise floor*/
          if (count < n || positionToTurnOff > 0)
                off();

          ticks += n;
          }

    //---------------------------------------------------------
    //   voice_start
    //---------------------------------------------------------

    void Voice::voice_start()
          {
          /* 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.*/

          /*
           * 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 const int list_of_generators_to_initialize[35] = {
                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,                           //                ---
                -1                                   // end-of-list marker
                };

         /* 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_set_default_values.
          */

          for (int i = 0; i < mod_count; i++) {
                Mod* m              = &mod[i];
                float modval        = m->get_value(channel, this);
                int dest_gen_index  = m->dest;
                Generator* dest_gen = &gen[dest_gen_index];
                dest_gen->mod      += modval;
                }

         /* The GEN_PITCH is a hack to fit the pitch bend controller into the
          * modulator paradigm.  Now the nominal pitch of the key is set.
          * 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 _GEN(voice, GEN_SCALETUNE) When the scale tuning is varied,
          * one key remains fixed. Here C3 (MIDI number 60) is used.
          */

          gen[GEN_PITCH].val = _noteTuning + _fluid->getPitch(60) + (gen[GEN_SCALETUNE].val * .01 *
                   (_fluid->getPitch(key) - _fluid->getPitch(60)));

         /* Now the generators are initialized, nominal and modulation value.
          * The voice parameters (which depend on generators) are calculated
          * with 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 (int i = 0; list_of_generators_to_initialize[i] != -1; i++)
                update_param(list_of_generators_to_initialize[i]);

          /* Make an estimate on how loud this voice can get at any time (attenuation). */
          min_attenuation_cB = get_lower_boundary_for_attenuation();

    //      qDebug("DELAY (%d) %d", FLUID_VOICE_ENVDELAY,volenv_data[FLUID_VOICE_ENVDELAY].count);
    //      qDebug("ATTACK (%d) %d", FLUID_VOICE_ENVATTACK,volenv_data[FLUID_VOICE_ENVATTACK].count);
    //      qDebug("HOLD (%d) %d", FLUID_VOICE_ENVHOLD, volenv_data[FLUID_VOICE_ENVHOLD].count);
    //      qDebug("DECAY (%d) %d", FLUID_VOICE_ENVDECAY, volenv_data[FLUID_VOICE_ENVDECAY].count);
    //      qDebug("SUSTAIN (%d) %d", FLUID_VOICE_ENVSUSTAIN, volenv_data[FLUID_VOICE_ENVSUSTAIN].count);
    //      qDebug("RELEASE (%d) %d", FLUID_VOICE_ENVRELEASE, volenv_data[FLUID_VOICE_ENVRELEASE].count);

          /* Force setting of the phase at the first DSP loop run
           * This cannot be done earlier, because it depends on modulators.
           */
          check_sample_sanity_flag = FLUID_SAMPLESANITY_STARTUP;
          positionToTurnOff = -1;

          status = FLUID_VOICE_ON;
          }

    /*
     * calculate_hold_decay_frames
     */
    int Voice::calculate_hold_decay_frames(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
           *
           * 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.
           */
          float timecents = (GEN(gen_base) + GEN(gen_key2base) * (60.0 - key));

          /* Range checking */
          if (is_decay){
                /* SF 2.01 section 8.1.3 # 28, 36 */
                if (timecents > 8000.0)
                      timecents = 8000.0;
                }
          else {
                /* SF 2.01 section 8.1.3 # 27, 35 */
                if (timecents > 5000)
                      timecents = 5000.0;
                /* SF 2.01 section 8.1.2 # 27, 35:
                 * The most negative number indicates no hold time
                 */
                if (timecents <= -32768.)
                      return 0;
                }
          /* SF 2.01 section 8.1.3 # 27, 28, 35, 36 */
          if (timecents < -12000.0)
                timecents = -12000.0;

          float seconds = fluid_tc2sec(timecents);
          return (int)((float)_fluid->sample_rate * seconds);
          }

    /*
     * update_param
     *
     * Purpose:
     *
     * The value of a generator (gen) has changed.  (The different
     * generators are listed in fluid.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. _GEN(voice, generator_enumerator) returns the sum
     * of all three.
     */
    void Voice::update_param(int _gen)
          {
          double q_dB;
          float x;
          float y;
          unsigned int count;
          // Alternate attenuation scale used by EMU10K1 cards when setting the attenuation at the preset or instrument level within the SoundFont bank.
          static const float ALT_ATTENUATION_SCALE = 0.4;

          double gain = 1.0 / 32768.0f;
          switch (_gen) {
                case GEN_PAN:
                      /* range checking is done in the fluid_pan function */
                      pan       = GEN(GEN_PAN);
                      amp_left  = fluid_pan(pan, 1) * gain;
                      amp_right = fluid_pan(pan, 0) * gain;
                      break;

                case GEN_ATTENUATION:
                      attenuation = gen[GEN_ATTENUATION].val * ALT_ATTENUATION_SCALE + gen[GEN_ATTENUATION].mod + gen[GEN_ATTENUATION].nrpn;

                      /* Range: SF2.01 section 8.1.3 # 48
                       * Motivation for range checking:
                       * OHPiano.SF2 sets initial attenuation to a whooping -96 dB
                       */
                      attenuation = qBound(0.0f, attenuation, 1440.0f);
                      break;

          /* The pitch is calculated from three different generators.
           * Read comment in fluid.h about GEN_PITCH.
           */
                case GEN_PITCH:
                case GEN_COARSETUNE:
                case GEN_FINETUNE:
                      /* The testing for allowed range is done in 'fluid_ct2hz' */
                      pitch = GEN(GEN_PITCH) + 100.0f * GEN(GEN_COARSETUNE) + GEN(GEN_FINETUNE);
                      break;

                case GEN_REVERBSEND:
                      /* The generator unit is 'tenths of a percent'. */
                      // reverb_send = GEN(GEN_REVERBSEND) / 1000.0f;
                      reverb_send = float(channel->cc[EFFECTS_DEPTH1]) / 128.0;
                      // fluid_clip(reverb_send, 0.0, 1.0);
                      amp_reverb = reverb_send * gain;
                      break;

                case GEN_CHORUSSEND:
                      /* The generator unit is 'tenths of a percent'. */
                      chorus_send = GEN(GEN_CHORUSSEND) / 1000.0f;
                      fluid_clip(chorus_send, 0.0, 1.0);
                      amp_chorus = chorus_send * gain;
                      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 (gen[GEN_OVERRIDEROOTKEY].val > -1) {   //FIXME: use flag instead of -1
                            root_pitch = gen[GEN_OVERRIDEROOTKEY].val * 100.0f - sample->pitchadj;
                            }
                      else {
                            root_pitch = sample->origpitch * 100.0f - sample->pitchadj;
                            }
                      root_pitch = _fluid->ct2hz(root_pitch);
                      if (sample != 0)
                            root_pitch *= (float) _fluid->sample_rate / sample->samplerate;
                      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]
                       */
                      fres = GEN(GEN_FILTERFC);

                      /* The synthesis loop will have to recalculate the filter
                       * coefficients. */
                      last_fres = -1.0f;
                      break;

                case GEN_FILTERQ:
                      /* The generator contains 'centibels' (1/10 dB) => divide by 10 to
                       * obtain dB
                       */
                      q_dB = GEN(GEN_FILTERQ) / 10.0f;

                      /* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */
                      fluid_clip(q_dB, 0.0f, 96.0f);

                      /* Short version: Modify the Q definition in a way, that a Q of 0
                       * dB leads to no resonance hump in the freq. response.
                       *
                       * Long version: From SF2.01, page 39, item 9 (initialFilterQ):
                       * "The gain at the cutoff frequency may be less than zero when
                       * zero is specified".  Assume q_dB=0 / q_lin=1: If we would leave
                       * q as it is, then this results in a 3 dB hump slightly below
                       * fc. At fc, the gain is exactly the DC gain (0 dB).  What is
                       * (probably) meant here is that the filter does not show a
                       * resonance hump for q_dB=0. In this case, the corresponding
                       * q_lin is 1/sqrt(2)=0.707.  The filter should have 3 dB of
                       * attenuation at fc now.  In this case Q_dB is the height of the
                       * resonance peak not over the DC gain, but over the frequency
                       * response of a non-resonant filter.  This idea is implemented as
                       * follows:
                       */
                      q_dB -= 3.01f;

                      /* The 'sound font' Q is defined in dB. The filter needs a linear
                         q. Convert.
                       */
                      q_lin = (float) (pow(10.0f, q_dB / 20.0f));

                      /* SF 2.01 page 59:
                       *
                       *  The SoundFont specs ask for a gain reduction equal to half the
                       *  height of the resonance peak (Q).  For example, for a 10 dB
                       *  resonance peak, the gain is reduced by 5 dB.  This is done by
                       *  multiplying the total gain with sqrt(1/Q).  `Sqrt' divides dB
                       *  by 2 (100 lin = 40 dB, 10 lin = 20 dB, 3.16 lin = 10 dB etc)
                       *  The gain is later factored into the 'b' coefficients
                       *  (numerator of the filter equation).  This gain factor depends
                       *  only on Q, so this is the right place to calculate it.
                       */
                      filter_gain = (float) (1.0 / sqrt(q_lin));

                      /* The synthesis loop will have to recalculate the filter coefficients. */
                      last_fres = -1.;
                      break;

                case GEN_MODLFOTOPITCH:
                      modlfo_to_pitch = GEN(GEN_MODLFOTOPITCH);
                      fluid_clip(modlfo_to_pitch, -12000.0, 12000.0);
                      break;

                case GEN_MODLFOTOVOL:
                      modlfo_to_vol = GEN(GEN_MODLFOTOVOL);
                      fluid_clip(modlfo_to_vol, -960.0, 960.0);
                      break;

                case GEN_MODLFOTOFILTERFC:
                      modlfo_to_fc = GEN(GEN_MODLFOTOFILTERFC);
                      fluid_clip(modlfo_to_fc, -12000, 12000);
                      break;

                case GEN_MODLFODELAY:
                      x = GEN(GEN_MODLFODELAY);
                      fluid_clip(x, -12000.0f, 5000.0f);
                      modlfo_delay = (unsigned int) (_fluid->sample_rate * fluid_tc2sec_delay(x));
                      break;

                case GEN_MODLFOFREQ:
                      {
                      /* - the frequency is converted into a delta value, per frame
                       * - the delay into a sample delay
                       */
                      unsigned int old_modlfo_dur = modlfo_dur;
                      x = GEN(GEN_MODLFOFREQ);
                      fluid_clip(x, -16000.0f, 4500.0f);
                      modlfo_dur = _fluid->sample_rate / fluid_act2hz(x);
                      if (old_modlfo_dur > 0)
                            modlfo_pos = (modlfo_pos/old_modlfo_dur) * modlfo_dur;
                      break;
                      }

                case GEN_VIBLFOFREQ:
                      /* vib lfo
                       *
                       * - the frequency is converted into a delta value per frame
                       * - the delay into a sample delay
                       */
                      x = GEN(GEN_VIBLFOFREQ);
                      fluid_clip(x, -16000.0f, 4500.0f);
                      viblfo_incr = (4.0f * fluid_act2hz(x) / _fluid->sample_rate);
                      break;

                case GEN_VIBLFODELAY:
                      x = GEN(GEN_VIBLFODELAY);
                      fluid_clip(x, -12000.0f, 5000.0f);
                      viblfo_delay = (unsigned int) (_fluid->sample_rate * fluid_tc2sec_delay(x));
                      break;

                case GEN_VIBLFOTOPITCH:
                      viblfo_to_pitch = GEN(GEN_VIBLFOTOPITCH);
                      fluid_clip(viblfo_to_pitch, -12000.0, 12000.0);
                      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.
                      */
                      x = GEN(GEN_KEYNUM);
                      if (x >= 0)
                            key = x;
                      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.
                      */
                      x = GEN(GEN_VELOCITY);
                      if (x > 0)
                            vel = x;
                      break;

                case GEN_MODENVTOPITCH:
                      modenv_to_pitch = GEN(GEN_MODENVTOPITCH);
                      fluid_clip(modenv_to_pitch, -12000.0, 12000.0);
                      break;

                case GEN_MODENVTOFILTERFC:
                      modenv_to_fc = GEN(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(modenv_to_fc, -12000.0, 12000.0);
                      break;

        /* sample start and ends points
         *
         * Range checking is initiated via the
         * 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 (sample != 0) {
                            start = (sample->start
    			         + (int) GEN(GEN_STARTADDROFS)
    			         + 32768 * (int) GEN(GEN_STARTADDRCOARSEOFS));
                            check_sample_sanity_flag = FLUID_SAMPLESANITY_CHECK;
                            }
                      break;

                case GEN_ENDADDROFS:                 /* SF2.01 section 8.1.3 # 1 */
                case GEN_ENDADDRCOARSEOFS:           /* SF2.01 section 8.1.3 # 12 */
                      if (sample != 0) {
                            end = (sample->end
    			         + (int) GEN(GEN_ENDADDROFS)
    			         + 32768 * (int) GEN(GEN_ENDADDRCOARSEOFS));
                            check_sample_sanity_flag = FLUID_SAMPLESANITY_CHECK;
                            }
                      break;

                case GEN_STARTLOOPADDROFS:           /* SF2.01 section 8.1.3 # 2 */
                case GEN_STARTLOOPADDRCOARSEOFS:     /* SF2.01 section 8.1.3 # 45 */
                      if (sample != 0) {
                            loopstart = (sample->loopstart
    				  + (int) GEN(GEN_STARTLOOPADDROFS)
    				  + 32768 * (int) GEN(GEN_STARTLOOPADDRCOARSEOFS));
                            check_sample_sanity_flag = FLUID_SAMPLESANITY_CHECK;
                            }
                      break;

                case GEN_ENDLOOPADDROFS:             /* SF2.01 section 8.1.3 # 3 */
                case GEN_ENDLOOPADDRCOARSEOFS:       /* SF2.01 section 8.1.3 # 50 */
                      if (sample != 0) {
                            loopend = (sample->loopend
    				   + (int) GEN(GEN_ENDLOOPADDROFS)
    				   + 32768 * (int) GEN(GEN_ENDLOOPADDRCOARSEOFS));
                            check_sample_sanity_flag = FLUID_SAMPLESANITY_CHECK;
                            }
                      break;

        /* Conversion functions differ in range limit */
    #define NUM_FRAMES_DELAY(_v)   (unsigned int) (_fluid->sample_rate * fluid_tc2sec_delay(_v))
    #define NUM_FRAMES_ATTACK(_v)  (unsigned int) (_fluid->sample_rate * fluid_tc2sec_attack(_v))
    #define NUM_FRAMES_RELEASE(_v) (unsigned int) (_fluid->sample_rate * fluid_tc2sec_release(_v))

               /* 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 */
                      x = GEN(GEN_VOLENVDELAY);
                      fluid_clip(x, -12000.0f, 5000.0f);
                      count = NUM_FRAMES_DELAY(x);
                      volenv_data[FLUID_VOICE_ENVDELAY].count = count;
                      volenv_data[FLUID_VOICE_ENVDELAY].coeff = 0.0f;
                      volenv_data[FLUID_VOICE_ENVDELAY].incr = 0.0f;
                      volenv_data[FLUID_VOICE_ENVDELAY].min = -1.0f;
                      volenv_data[FLUID_VOICE_ENVDELAY].max = 1.0f;
                      break;

                case GEN_VOLENVATTACK:               /* SF2.01 section 8.1.3 # 34 */
                      x = GEN(GEN_VOLENVATTACK);
                      fluid_clip(x, -12000.0f, 8000.0f);
                      count = 1 + NUM_FRAMES_ATTACK(x);
                      volenv_data[FLUID_VOICE_ENVATTACK].count = count;
                      volenv_data[FLUID_VOICE_ENVATTACK].coeff = 1.0f;
                      volenv_data[FLUID_VOICE_ENVATTACK].incr = count ? 1.0f / count : 0.0f;
                      volenv_data[FLUID_VOICE_ENVATTACK].min = -1.0f;
                      volenv_data[FLUID_VOICE_ENVATTACK].max = 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_frames(GEN_VOLENVHOLD, GEN_KEYTOVOLENVHOLD, 0); /* 0 means: hold */
                      volenv_data[FLUID_VOICE_ENVHOLD].count = count;
                      volenv_data[FLUID_VOICE_ENVHOLD].coeff = 1.0f;
                      volenv_data[FLUID_VOICE_ENVHOLD].incr = 0.0f;
                      volenv_data[FLUID_VOICE_ENVHOLD].min = -1.0f;
                      volenv_data[FLUID_VOICE_ENVHOLD].max = 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 */
                      y = 1.0f - 0.001f * GEN(GEN_VOLENVSUSTAIN);
                      fluid_clip(y, 0.0f, 1.0f);
                      count = calculate_hold_decay_frames(GEN_VOLENVDECAY, GEN_KEYTOVOLENVDECAY, 1); /* 1 for decay */
                      volenv_data[FLUID_VOICE_ENVDECAY].count = count;
                      volenv_data[FLUID_VOICE_ENVDECAY].coeff = 1.0f;
                      volenv_data[FLUID_VOICE_ENVDECAY].incr = count ? -1.0f / count : 0.0f;
                      volenv_data[FLUID_VOICE_ENVDECAY].min = y;
                      volenv_data[FLUID_VOICE_ENVDECAY].max = 2.0f;
                      break;

                case GEN_VOLENVRELEASE:             /* SF2.01 section 8.1.3 # 38 */
                      x = GEN(GEN_VOLENVRELEASE);
                      fluid_clip(x, FLUID_MIN_VOLENVRELEASE, 8000.0f);
                      count = 1 + NUM_FRAMES_RELEASE(x);
                      volenv_data[FLUID_VOICE_ENVRELEASE].count = count;
                      volenv_data[FLUID_VOICE_ENVRELEASE].coeff = 1.0f;
                      volenv_data[FLUID_VOICE_ENVRELEASE].incr = count ? -1.0f / count : 0.0f;
                      volenv_data[FLUID_VOICE_ENVRELEASE].min = 0.0f;
                      volenv_data[FLUID_VOICE_ENVRELEASE].max = 1.0f;
                      break;

                /* Modulation envelope */
                case GEN_MODENVDELAY:               /* SF2.01 section 8.1.3 # 25 */
                      x = GEN(GEN_MODENVDELAY);
                      fluid_clip(x, -12000.0f, 5000.0f);
                      modenv_data[FLUID_VOICE_ENVDELAY].count = NUM_FRAMES_DELAY(x);
                      modenv_data[FLUID_VOICE_ENVDELAY].coeff = 0.0f;
                      modenv_data[FLUID_VOICE_ENVDELAY].incr = 0.0f;
                      modenv_data[FLUID_VOICE_ENVDELAY].min = -1.0f;
                      modenv_data[FLUID_VOICE_ENVDELAY].max = 1.0f;
                      break;

                case GEN_MODENVATTACK:               /* SF2.01 section 8.1.3 # 26 */
                      x = GEN(GEN_MODENVATTACK);
                      fluid_clip(x, -12000.0f, 8000.0f);
                      count = 1 + NUM_FRAMES_ATTACK(x);
                      modenv_data[FLUID_VOICE_ENVATTACK].count = count;
                      modenv_data[FLUID_VOICE_ENVATTACK].coeff = 1.0f;
                      modenv_data[FLUID_VOICE_ENVATTACK].incr = count ? 1.0f / count : 0.0f;
                      modenv_data[FLUID_VOICE_ENVATTACK].min = -1.0f;
                      modenv_data[FLUID_VOICE_ENVATTACK].max = 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_frames(GEN_MODENVHOLD, GEN_KEYTOMODENVHOLD, 0); /* 1 means: hold */
                      modenv_data[FLUID_VOICE_ENVHOLD].count = count;
                      modenv_data[FLUID_VOICE_ENVHOLD].coeff = 1.0f;
                      modenv_data[FLUID_VOICE_ENVHOLD].incr = 0.0f;
                      modenv_data[FLUID_VOICE_ENVHOLD].min = -1.0f;
                      modenv_data[FLUID_VOICE_ENVHOLD].max = 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_frames(GEN_MODENVDECAY, GEN_KEYTOMODENVDECAY, 1); /* 1 for decay */
                      y = 1.0f - 0.001f * GEN(GEN_MODENVSUSTAIN);
                      fluid_clip(y, 0.0f, 1.0f);
                      modenv_data[FLUID_VOICE_ENVDECAY].count = count;
                      modenv_data[FLUID_VOICE_ENVDECAY].coeff = 1.0f;
                      modenv_data[FLUID_VOICE_ENVDECAY].incr = count ? -1.0f / count : 0.0f;
                      modenv_data[FLUID_VOICE_ENVDECAY].min = y;
                      modenv_data[FLUID_VOICE_ENVDECAY].max = 2.0f;
                      break;

                case GEN_MODENVRELEASE:                                  /* SF 2.01 section 8.1.3 # 30 */
                      x = GEN(GEN_MODENVRELEASE);
                      fluid_clip(x, -12000.0f, 8000.0f);
                      count = 1 + NUM_FRAMES_RELEASE(x);
                      modenv_data[FLUID_VOICE_ENVRELEASE].count = count;
                      modenv_data[FLUID_VOICE_ENVRELEASE].coeff = 1.0f;
                      modenv_data[FLUID_VOICE_ENVRELEASE].incr = count ? -1.0f / count : 0.0;
                      modenv_data[FLUID_VOICE_ENVRELEASE].min = 0.0f;
                      modenv_data[FLUID_VOICE_ENVRELEASE].max = 2.0f;
                      break;

                } /* switch gen */
          }

    /**
     * fluid_voice_modulate
     *
     * 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 three steps:
     *
     * - first, we look for all the modulators that have the changed
     * controller as a source. This will yield a list of generators that
     * will be changed because of the controller event.
     *
     * - For every changed generator, calculate its new value. This is the
     * sum of its original value plus the values of al the attached
     * modulators.
     *
     * - For every changed generator, convert its value to the correct
     * unit of the corresponding DSP parameter
     * */

    void Voice::modulate(bool _cc, int _ctrl)
          {
          for (int i = 0; i < mod_count; i++) {
                Mod* m = &mod[i];

                /* step 1: find all the modulators that have the changed controller
                 * as input source.
                 */
                if (m->has_source(_cc, _ctrl)) {
                      int g = m->get_dest();
                      float modval = 0.0;

                      /* step 2: for every changed modulator, calculate the modulation
                       * value of its associated generator
                       */
                      for (int k = 0; k < mod_count; k++) {
                            if (fluid_mod_has_dest(&mod[k], g)) {
                                  modval += mod[k].get_value(channel, this);
                                  }
                            }
                      gen[g].set_mod(modval);

                      /* step 3: now that we have the new value of the generator,
                       * recalculate the parameter values that are derived from the
                       * generator
                       */
                      update_param(g);
                      }
                }
          }

    /**
     * fluid_voice_modulate_all
     *
     * Update all the modulators. This function is called after a
     * ALL_CTRL_OFF MIDI message has been received (CC 121).
     *
     */
    void Voice::modulate_all()
          {
          /* Loop through all the modulators.
            FIXME: we should loop through the set of generators instead of
            the set of modulators. We risk to call 'fluid_voice_update_param'
            several times for the same generator if several modulators have
            that generator as destination. It's not an error, just a wast of
            energy (think polution, global warming, unhappy musicians, ...)
           */

          for (int i = 0; i < mod_count; i++) {
                Mod* m       = &mod[i];
                int g        = m->get_dest();
                float modval = 0.0;

                /* Accumulate the modulation values of all the modulators with
                 * destination generator 'gen'
                 */
                for (int k = 0; k < mod_count; k++) {
                      if (fluid_mod_has_dest(&mod[k], g))
                            modval += mod[k].get_value(channel, this);
                      }

                gen[g].set_mod(modval);

                /* Update the parameter values that are depend on the generator
                 * 'gen'
                 */
                update_param(g);
                }
          }

    /*
     * fluid_voice_noteoff
     */
    void Voice::noteoff()
          {
          if (channel && channel->sustained())
                status = FLUID_VOICE_SUSTAINED;
          else {
                if (volenv_section == 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 equievalent amplitude during the release phase
                      * for seamless volume transition.
                      */
                      if (volenv_val > 0) {
                            float lfo = modlfo_val * -modlfo_to_vol;
                            float amp = volenv_val * pow (10.0, lfo / -200);
                            float env_value = - ((-200 * log (amp) / log (10.0) - lfo) / 960.0 - 1);
                            fluid_clip (env_value, 0.0, 1.0);
                            volenv_val = env_value;
                            }
                      }
                volenv_section = FLUID_VOICE_ENVRELEASE;
                volenv_count = 0;
                modenv_section = FLUID_VOICE_ENVRELEASE;
                modenv_count = 0;
                }
          }

    /*
     * 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.
     */

    void Voice::kill_excl()
          {
          if (!isPlaying())
                return;

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

          /* If the voice is not yet in release state, put it into release state */
          if (volenv_section != FLUID_VOICE_ENVRELEASE) {
                volenv_section = FLUID_VOICE_ENVRELEASE;
                volenv_count = 0;
                modenv_section = FLUID_VOICE_ENVRELEASE;
                modenv_count = 0;
                }

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

          /* Speed up the modulation envelope */
          gen_set(GEN_MODENVRELEASE, -200);
          update_param(GEN_MODENVRELEASE);
          }

    //---------------------------------------------------------
    //   off
    //    Turns off a voice, meaning that it is not processed
    //    anymore by the DSP loop.
    //---------------------------------------------------------

    void Voice::off()
          {
          chan           = NO_CHANNEL;
          volenv_section = FLUID_VOICE_ENVFINISHED;
          volenv_count   = 0;
          modenv_section = FLUID_VOICE_ENVFINISHED;
          modenv_count   = 0;
          status         = FLUID_VOICE_OFF;
          _fluid->freeVoice(this);
          }

    /*
     * fluid_voice_add_mod
     *
     * Adds a modulator to the voice.  "mode" indicates, what to do, if
     * an identical modulator exists already.
     *
     * mode == FLUID_VOICE_ADD: Identical modulators on preset level are added
     * mode == FLUID_VOICE_OVERWRITE: Identical modulators on instrument level are overwritten
     * mode == FLUID_VOICE_DEFAULT: This is a default modulator, there can be no identical modulator.
     *                             Don't check.
     */
    void Voice::add_mod(const Mod* _mod, int mode)
          {
          /*
           * Some soundfonts come with a huge number of non-standard
           * controllers, because they have been designed for one particular
           * sound card.  Discard them, maybe print a warning.
           */

          if (((_mod->flags1 & FLUID_MOD_CC) == 0)
             && ((_mod->src1 != 0)      /* SF2.01 section 8.2.1: Constant value */
    	   && (_mod->src1 != 2)       /* Note-on velocity */
    	   && (_mod->src1 != 3)       /* Note-on key number */
    	   && (_mod->src1 != 10)      /* Poly pressure */
    	   && (_mod->src1 != 13)      /* Channel pressure */
    	   && (_mod->src1 != 14)      /* Pitch wheel */
    	   && (_mod->src1 != 16))) {  /* Pitch wheel sensitivity */
                qDebug("Ignoring invalid controller, using non-CC source %i.", _mod->src1);
                return;
                }

          if (mode == FLUID_VOICE_ADD) {
                /* if identical modulator exists, add them */
                for (int i = 0; i < mod_count; i++) {
                      if (test_identity(&mod[i], _mod)) {
                            //		printf("Adding modulator...\n");
                            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 (int i = 0; i < mod_count; i++) {
                      if (test_identity(&mod[i], _mod)) {
                            //  printf("Replacing modulator...amount is %f\n",mod->amount);
                            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 (mod_count < FLUID_NUM_MOD)
                _mod->clone(&mod[mod_count++]);
          }

    /*
     * 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_init, after
     * all modulators have been run on the voice once.  Also,
     * voice->attenuation has to be initialized.
     */

    float Voice::get_lower_boundary_for_attenuation()
          {
          float possible_att_reduction_cB = 0;

          for (int i = 0; i < mod_count; i++) {
                Mod* m = &mod[i];

                /* Modulator has attenuation as target and can change over time? */
                if ((m->dest == GEN_ATTENUATION) && ((m->flags1 & FLUID_MOD_CC) || (m->flags2 & FLUID_MOD_CC))) {
                      float current_val = m->get_value(channel, this);
                      float v = fabs(m->amount);

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

                      /* For example:
                       * - current_val=100
                       * - min_val=-4000
                       * - possible_att_reduction_cB += 4100
                       */
                      if (current_val > v)
    	                  possible_att_reduction_cB += (current_val - v);
                      }
                }
          float lower_bound = attenuation - possible_att_reduction_cB;

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

    /* Purpose:
     *
     * Make sure, that sample start / end point and loop points are in
     * proper order. When starting up, calculate the initial phase.
     */
    void Voice::check_sample_sanity()
          {
          int min_index_nonloop=(int) sample->start;
          int max_index_nonloop=(int) sample->end;

          /* make sure we have enough samples surrounding the loop */
          int min_index_loop=(int) sample->start + FLUID_MIN_LOOP_PAD;
          int max_index_loop=(int) sample->end - FLUID_MIN_LOOP_PAD;
          fluid_check_fpe("voice_check_sample_sanity start");

          if (!check_sample_sanity_flag)
    	      return;

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

          /* Keep the start point within the sample data */
          if (start < min_index_nonloop)
                start = min_index_nonloop;
          else if (start > max_index_nonloop)
                start = max_index_nonloop;

          /* Keep the end point within the sample data */
          if (end < min_index_nonloop)
                end = min_index_nonloop;
          else if (end > max_index_nonloop)
                end = max_index_nonloop;

          /* Keep start and end point in the right order */
          if (start > end) {
                int temp = start;
                start    = end;
                end      = temp;
                }

          /* Zero length? */
          if (start == end) {
                off();
    	      return;
                }

          if ((SAMPLEMODE() == FLUID_LOOP_UNTIL_RELEASE) || (SAMPLEMODE() == FLUID_LOOP_DURING_RELEASE)) {
                /* Keep the loop start point within the sample data */
    	      if (loopstart < min_index_loop)
    	            loopstart = min_index_loop;
                else if (loopstart > max_index_loop)
    	            loopstart = max_index_loop;

                /* Keep the loop end point within the sample data */
                if (loopend < min_index_loop)
    	            loopend = min_index_loop;
                else if (loopend > max_index_loop)
    	            loopend = max_index_loop;

                /* Keep loop start and end point in the right order */
                if (loopstart > loopend){
    	            int temp  = loopstart;
    	            loopstart = loopend;
    	            loopend   = temp;
                      }

                /* Loop too short? Then don't loop. */
                if (loopend < loopstart + FLUID_MIN_LOOP_SIZE)
    	            gen[GEN_SAMPLEMODE].val = 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)loopstart >= (int)sample->loopstart && (int)loopend <= (int)sample->loopend){
    	            /* Is there a valid peak amplitude available for the loop? */
    	            if (sample->amplitude_that_reaches_noise_floor_is_valid) {
    	                  amplitude_that_reaches_noise_floor_loop = sample->amplitude_that_reaches_noise_floor;
                            }
                      else
    	                  /* Worst case */
    	                  amplitude_that_reaches_noise_floor_loop = amplitude_that_reaches_noise_floor_nonloop;
                      }
                } /* if sample mode is looped */

          /* Run startup specific code (only once, when the voice is started) */
          if (check_sample_sanity_flag & FLUID_SAMPLESANITY_STARTUP) {
                if (max_index_loop - min_index_loop < FLUID_MIN_LOOP_SIZE){
                      if ((SAMPLEMODE() == FLUID_LOOP_UNTIL_RELEASE) || (SAMPLEMODE() == FLUID_LOOP_DURING_RELEASE))
    	                  gen[GEN_SAMPLEMODE].val = FLUID_UNLOOPED;
                      }

                /* Set the initial phase of the voice (using the result from the
    	         start offset modulators).
                 */
                phase.setInt(start);
                } /* if startup */

          /* Is this voice run in loop mode, or does it run straight to the
           end of the waveform data?
           */
          if (((SAMPLEMODE() == FLUID_LOOP_UNTIL_RELEASE) && (volenv_section < FLUID_VOICE_ENVRELEASE)) || (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 = phase.index();
                if (index_in_sample >= loopend) {
         	            /* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Phase after 2nd loop point!"); */
         	            phase.setInt(loopstart);
                      }
                }
    /*    FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Sample from %i to %i, loop from %i to %i", voice->start, voice->end, voice->loopstart, voice->loopend); */

        /* Sample sanity has been assured. Don't check again, until some
           sample parameter is changed by modulation.
         */
          check_sample_sanity_flag = 0;
          fluid_check_fpe("voice_check_sample_sanity");
          }

    //---------------------------------------------------------
    //   set_param
    //---------------------------------------------------------

    void Voice::set_param(int g, float nrpn_value, int abs)
          {
          gen[g].nrpn = nrpn_value;
          gen[g].flags = (abs)? GEN_ABS_NRPN : GEN_SET;
          update_param(g);
          }

      /** 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. In many cases the loop volume is many dB
       * below the maximum volume.  For example, the loop volume for a
       * typical acoustic piano is 20 dB below max.  Taking that into
       * account in the turn-off algorithm we can save 20 dB / 100 dB =>
       * 1/5 of the total release time.
       * So it's a good idea to call fluid_voice_optimize_sample
       * on each sample once.
       */
    /* - Scan the loop
     * - determine the peak level
     * - Calculate, what factor will make the loop inaudible
     * - Store in sample
     */
    void Sample::optimize()
          {
          Sample* s = this;
          signed short peak_max = 0;
          signed short peak_min = 0;
          signed short peak;
          float normalized_amplitude_during_loop;
          double result;
          int i;

          /* ignore ROM and other(?) invalid samples */
          if (!s->valid())
                return;

          if (!s->amplitude_that_reaches_noise_floor_is_valid) { /* Only once */
                /* Scan the loop */
                for (i = (int)s->loopstart; i < (int) s->loopend; i ++) {
                      signed short val = s->data[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 = ((float)peak)/32768.;
                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;
                }
          }
    /* Purpose:
     *
     * - filters (applies a lowpass filter with variable cutoff frequency and quality factor)
     * - mixes the processed sample to left and right output using the pan setting
     * - sends the processed sample to chorus and reverb
     *
     * A couple of variables are used internally, their results are discarded:
     * - dsp_phase_fractional: The fractional part of dsp_phase
     * - dsp_coeff: A table of four coefficients, depending on the fractional phase.
     *              Used to interpolate between samples.
     * - dsp_process_buffer: Holds the processed signal between stages
     * - dsp_centernode: delay line for the IIR filter
     * - dsp_hist1: same
     * - dsp_hist2: same
     *
     */
    void Voice::effects(int count, float* out, float* reverb, float* chorus)
          {
          /* filter (implement the voice filter according to SoundFont standard) */

          /* Check for denormal number (too close to zero). */
          if (fabs (hist1) < 1e-20)
                hist1 = 0.0f;             /* FIXME JMG - Is this even needed? */

          /* Two versions of the filter loop. One, while the filter is
           * changing towards its new setting. The other, if the filter
           * doesn't change.
           */

          if (filter_coeff_incr_count > 0) {
                /* Increment is added to each filter coefficient filter_coeff_incr_count times. */
                for (int i = 0; i < count; i++) {
                      /* The filter is implemented in Direct-II form. */
                      float dsp_centernode = dsp_buf[i] - a1 * hist1 - a2 * hist2;
                      dsp_buf[i] = b02 * (dsp_centernode + hist2) + b1 * hist1;
                      hist2 = hist1;
                      hist1 = dsp_centernode;

                      if (filter_coeff_incr_count-- > 0) {
                            a1  += a1_incr;
                            a2  += a2_incr;
                            b02 += b02_incr;
                            b1  += b1_incr;
                            }
                      }
                }
          else { /* The filter parameters are constant.  This is duplicated to save time. */
                for (int i = 0; i < count; i++) {   // The filter is implemented in Direct-II form.
                      float dsp_centernode = dsp_buf[i] - a1 * hist1 - a2 * hist2;
                      dsp_buf[i]     = b02 * (dsp_centernode + hist2) + b1 * hist1;
                      hist2          = hist1;
                      hist1          = dsp_centernode;
                      }
                }

          for (int i = 0; i < count; i++) {
                float v    = dsp_buf[i];

                float vv   = v  * amp_left;
                *out++    += vv;
                *reverb++ += vv * amp_reverb;
                *chorus++ += vv * amp_chorus;

                vv         = v  * amp_right;
                *out++    += vv;
                *reverb++ += vv * amp_reverb;
                *chorus++ += vv * amp_chorus;
                }
          }
    }

↑ V507 Pointer to local array 'l_dsp_buf' is stored outside the scope of this array. Such a pointer will become invalid.

↑ V595 The 'sample' pointer was utilized before it was verified against nullptr. Check lines: 926, 929.