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

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

/**
 * @return -1 if voice has finished, 0 if it's currently quiet, 1 otherwise
 */
static inline int
fluid_rvoice_calc_amp(fluid_rvoice_t* voice)
{
  fluid_real_t target_amp;	/* target amplitude */

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

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

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

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

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

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

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

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

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

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

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

  fluid_check_fpe ("voice_write amplitude calculation");

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

  return 1;
}


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    } /* if sample mode is looped */

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

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

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

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


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

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

  if (!voice->dsp.sample)
    return 0;
  if (voice->dsp.check_sample_sanity_flag)
    fluid_rvoice_check_sample_sanity(voice);

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

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

  voice->envlfo.ticks += FLUID_BUFSIZE;

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

  fluid_adsr_env_calc(&voice->envlfo.volenv, 1);
  fluid_check_fpe ("voice_write vol env");
  if (fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVFINISHED)
    return 0;

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

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

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

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

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

  count = fluid_rvoice_calc_amp(voice);
  if (count <= 0) 
    return count;

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

  /* Calculate the number of samples, that the DSP loop advances
   * through the original waveform with each step in the output
   * buffer. It is the ratio between the frequencies of original
   * waveform and output waveform.*/
  voice->dsp.phase_incr = fluid_ct2hz_real(voice->dsp.pitch + 
     fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch
     + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch
     + fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_pitch) 
     / voice->dsp.root_pitch_hz;

  fluid_check_fpe ("voice_write phase calculation");

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

  voice->dsp.is_looping = voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE
    || (voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE
	&& fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE);

  /*********************** 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. */
  voice->dsp.dsp_buf = dsp_buf; 

  switch (voice->dsp.interp_method)
  {
    case FLUID_INTERP_NONE:
      count = fluid_rvoice_dsp_interpolate_none (&voice->dsp);
      break;
    case FLUID_INTERP_LINEAR:
      count = fluid_rvoice_dsp_interpolate_linear (&voice->dsp);
      break;
    case FLUID_INTERP_4THORDER:
    default:
      count = fluid_rvoice_dsp_interpolate_4th_order (&voice->dsp);
      break;
    case FLUID_INTERP_7THORDER:
      count = fluid_rvoice_dsp_interpolate_7th_order (&voice->dsp);
      break;
  }
  fluid_check_fpe ("voice_write interpolation");
  if (count == 0)
    return count;

  /*************** resonant filter ******************/
  fluid_iir_filter_calc(&voice->resonant_filter, voice->dsp.output_rate,
  		        fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc +
 		        fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_fc);

  fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count);

  return count;
}


static inline fluid_real_t* 
get_dest_buf(fluid_rvoice_buffers_t* buffers, int index,
             fluid_real_t** dest_bufs, int dest_bufcount)
{
  int j = buffers->bufs[index].mapping;
  if (j >= dest_bufcount || j < 0) return NULL;
  return dest_bufs[j];
}

/**
 * Mix data down to buffers
 *
 * @param buffers Destination buffer(s)
 * @param dsp_buf Mono sample source
 * @param samplecount Number of samples to process (no FLUID_BUFSIZE restriction)
 * @param dest_bufs Array of buffers to mixdown to
 * @param dest_bufcount Length of dest_bufs
 */
void 
fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t* buffers, 
                         fluid_real_t* dsp_buf, int samplecount, 
                         fluid_real_t** dest_bufs, int dest_bufcount)
{
  int bufcount = buffers->count;
  int i, dsp_i;
  if (!samplecount || !bufcount || !dest_bufcount) 
    return;

  for (i=0; i < bufcount; i++) {
    fluid_real_t* buf = get_dest_buf(buffers, i, dest_bufs, dest_bufcount);
    fluid_real_t* next_buf;
    fluid_real_t amp = buffers->bufs[i].amp;
    if (buf == NULL || amp == 0.0f)
      continue;

    /* Optimization for centered stereo samples - we can save one 
       multiplication per sample */
    next_buf = (i+1 >= bufcount ? NULL : get_dest_buf(buffers, i+1, dest_bufs, dest_bufcount));
    if (next_buf && buffers->bufs[i+1].amp == amp) {
      for (dsp_i = 0; dsp_i < samplecount; dsp_i++) {
        fluid_real_t samp = amp * dsp_buf[dsp_i]; 
        buf[dsp_i] += samp;
        next_buf[dsp_i] += samp;
      }
      i++;
    }
    else {
      for (dsp_i = 0; dsp_i < samplecount; dsp_i++)
        buf[dsp_i] += amp * dsp_buf[dsp_i];
    }
  }
}

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

  if (bufnum < buffers->count) return FLUID_OK;
  if (bufnum >= FLUID_RVOICE_MAX_BUFS) return FLUID_FAILED;

  for (i = buffers->count; i <= bufnum; i++) {
    buffers->bufs[bufnum].amp = 0.0f;  
    buffers->bufs[bufnum].mapping = i;  
  }
  buffers->count = bufnum+1;
  return FLUID_OK;
}


void 
fluid_rvoice_buffers_set_amp(fluid_rvoice_buffers_t* buffers, 
                             unsigned int bufnum, fluid_real_t value)
{
  if (fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK)
    return;
  buffers->bufs[bufnum].amp = value;
}

void 
fluid_rvoice_buffers_set_mapping(fluid_rvoice_buffers_t* buffers, 
                                 unsigned int bufnum, int mapping)
{
  if (fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK)
    return;
  buffers->bufs[bufnum].mapping = mapping;
}


void
fluid_rvoice_reset(fluid_rvoice_t* voice)
{
  voice->dsp.has_looped = 0;
  voice->envlfo.ticks = 0;
  voice->envlfo.noteoff_ticks = 0;
  voice->dsp.amp = 0.0f; /* The last value of the volume envelope, used to
                            calculate the volume increment during
                            processing */

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

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

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

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

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


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

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


void 
fluid_rvoice_set_output_rate(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->dsp.output_rate = value;
}

void 
fluid_rvoice_set_interp_method(fluid_rvoice_t* voice, int value)
{
  voice->dsp.interp_method = value;
}

void 
fluid_rvoice_set_root_pitch_hz(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->dsp.root_pitch_hz = value;
}

void 
fluid_rvoice_set_pitch(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->dsp.pitch = value;
}


void 
fluid_rvoice_set_attenuation(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->dsp.attenuation = value;
}

void 
fluid_rvoice_set_min_attenuation_cB(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->dsp.min_attenuation_cB = value;
}

void 
fluid_rvoice_set_viblfo_to_pitch(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->envlfo.viblfo_to_pitch = value;
}

void fluid_rvoice_set_modlfo_to_pitch(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->envlfo.modlfo_to_pitch = value;
}

void 
fluid_rvoice_set_modlfo_to_vol(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->envlfo.modlfo_to_vol = value;
}

void 
fluid_rvoice_set_modlfo_to_fc(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->envlfo.modlfo_to_fc = value;
}

void 
fluid_rvoice_set_modenv_to_fc(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->envlfo.modenv_to_fc = value;
}

void 
fluid_rvoice_set_modenv_to_pitch(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->envlfo.modenv_to_pitch = value;
}

void 
fluid_rvoice_set_synth_gain(fluid_rvoice_t* voice, fluid_real_t value)
{
  voice->dsp.synth_gain = value;

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

void 
fluid_rvoice_set_start(fluid_rvoice_t* voice, int value)
{
  voice->dsp.start = value;
  voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

void 
fluid_rvoice_set_end(fluid_rvoice_t* voice, int value)
{
  voice->dsp.end = value;
  voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

void 
fluid_rvoice_set_loopstart(fluid_rvoice_t* voice, int value)
{
  voice->dsp.loopstart = value;
  voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

void fluid_rvoice_set_loopend(fluid_rvoice_t* voice, int value)
{
  voice->dsp.loopend = value;
  voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}

void fluid_rvoice_set_samplemode(fluid_rvoice_t* voice, enum fluid_loop value)
{
  voice->dsp.samplemode = value;
  voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK;
}


void 
fluid_rvoice_set_sample(fluid_rvoice_t* voice, fluid_sample_t* value)
{
  voice->dsp.sample = value;
  if (value) {
    voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP;
  }
}

void 
fluid_rvoice_voiceoff(fluid_rvoice_t* voice)
{
  fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVFINISHED);
  fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVFINISHED);
}


