// ----
// ---- file   : StSampleVoice.cpp
// ---- author : Bastian Spiegel <bs@tkscript.de>
// ---- legal  : (c) 2009-2019 by Bastian Spiegel. 
// ----          Distributed under terms of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL). See 
// ----          http://www.gnu.org/licenses/licenses.html#LGPL or COPYING for further information.
// ----
// ---- info   : This is part of the "syntracker" midi sequencer.
// ----
// ---- created: 23Sep2009
// ----          
// ---- changed: 26Dec2009, 27Dec2009, 29Dec2009, 02Jan2010, 05Jan2010, 07Jan2010, 15Jan2010
// ----          19Jan2010, 21Jan2010, 27Jan2010, 28Feb2010, 01Mar2010, 12Apr2010, 29Apr2010
// ----          30Apr2010, 02May2010, 04May2010, 11May2010, 14May2010, 28Jun2010, 29Jun2010
// ----          07Jul2010, 08Jul2010, 04Sep2010, 05Sep2010, 09Sep2010, 05Mar2011, 10Mar2011
// ----          20Mar2011, 28Mar2011, 29Mar2011, 08May2013, 09May2013, 13Sep2015, 19Dec2018
// ----          29Dec2018, 04Jan2019, 07Jan2019, 08Jan2019, 10Jan2019, 17Jan2019, 24Mar2019
// ----          30Mar2019, 31Mar2019, 23Apr2019, 11May2019, 12May2019, 13May2019, 15May2019
// ----          16May2019, 17May2019
// ----
// ----
// ----

#define YAC_EPSILONCOMPARE_ABS defined
#define YAC_EPSILONCOMPARE_ABS_DEFAULT defined

#include "tksampler.h"

//   #define DP if(0);else yac_host->printf
 #define DP if(1);else yac_host->printf

#define USE_STATS defined


#ifdef USE_STATS
static sUI stat_opt_innermix = 0;
static sUI stat_opt_lastmix  = 0;
static sUI stat_opt_generic  = 0;
#endif // USE_STATS


#define VOLRAMP_NUMSTEPS STSAMPLEVOICE_VOLRAMP_NUMSTEPS

#define NUM_ZERO_SAMPLES 256
static sF32 zero_samples[2 * NUM_ZERO_SAMPLES];
static sSI b_fill_zero_samples = 1;


//#define ANTICLICK_FADEOUT_TIME 512 //4096
#define ANTICLICK_FADEOUT_TIME 64

// # of samples to interpolate when modulating sample offset in granular mode
#define ANTICLICK_GRANULAR_SMPOFFINTERPOL_TIME 512


#define Dsoftstopped() (volramp_fadeout_countdown >= 0)


static sF32 loc_bipolar_to_scale(const sF32 _t, const sF32 _div, const sF32 _mul) {
   // t (-1..1) => /_div .. *_mul
   
   sF32 s;

   if(_t < 0.0f)
   {
      s = (1.0f / _div);
      s = 1.0f + (s - 1.0f) * -_t;
   }
   else
   {
      s = _mul;
      s = 1.0f + (s - 1.0f) * _t;
   }
   
   return s;
}


StSampleVoice::StSampleVoice(void) {
   freq_table    = NULL;
   sample_player = NULL;
   override_sample_loops = NULL;

   _resetVoice();
}

StSampleVoice::~StSampleVoice() {
   YAC_DELETE_SAFE(override_sample_loops);
}

void StSampleVoice::_resetVoice(void) {
   ////printf("xxx StSampleVoice::_resetVoice\n");
   sample                      = NULL;
   b_used                      = false;
   b_playing                   = false;
   b_release                   = YAC_TRUE;
   b_alloc                     = YAC_FALSE;
   b_glide                     = YAC_FALSE;
   replay_ticks                = 0;
   voice_key                   = 0;
   b_fading_out                = false;
   queued_noteon.b_valid       = 0;
   volramp_fade_vol            = 1.0f;
   volramp_fadein_countdown    = -1;
   volramp_fadeout_countdown   = -1;
   anticlick_fadeout_countdown = 0;
   last_sample_l               = 0.0f;
   last_sample_r               = 0.0f;
   anticlick_granular_smpoffinterpol_countdown = 0;
#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF
   sampleshift_eol_xfade_loopstart_frameidx = 0u;
   // (note) clearing the buffer should not be necessary (just a precaution)
   memset((void*)sampleshift_eol_xfade_buf_l, 0, sizeof(sampleshift_eol_xfade_buf_l));
   memset((void*)sampleshift_eol_xfade_buf_r, 0, sizeof(sampleshift_eol_xfade_buf_r));
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF
   
   float_block_off = 0.0f;
   float_block_size = 0.0f;
   current_block_size = 0;
   current_block_offset = 0;
   last_relsmpoff = 0;
   current_sample_offset = 0;
   current_sample_len = 0;
   samples_until_end_of_loopstep = 0;
   fadein_countdown = 0;
   fadeout_countdown = 0;
   b_fading_out = 0;
   current_ts_offset = 0.0f;

   mod_vol                = 1.0f;
   mod_vol2               = 1.0f;
   mod_pan                = 0.0f;
   mod_pan2               = 0.0f;
   mod_freq               = 0.0f;
   mod_freq2              = 0.0f;
   mod_mod                = 0.0f;
   mod_timestretch        = 0.0f;
   mmdst_timestretch      = 0.0f;
   mod_timestretch_bend   = 0.0f;
   mmdst_timestretch_bend = 0.0f;
   mod_sampleoff          = 0.0f;
   mod_sampleoff2         = 0.0f;  // internal (rand, vel to off)
   mod_cyclelen           = 0.0f;
   mod_sampleshift        = 0.0f;
   mod_sampleshift_endofloop = -1.0f;
   mod_altvolume          = 1.0f;
   mod_jumptoloop         = -1;

   samples_since_cycle = 0;
   fragment_interpol = 0.0f;
   current_vol = 0.0f;
   next_vol = 0.0f;
   cached_vel_vol = 0.0f;
   current_pan = 0.0f;
   next_pan = 0.0f;
   current_freq = 0.0f;
   next_freq = 0.0f;
   current_aux_adsr = 0.0f;
   next_aux_adsr = 0.0f;
   current_aux_lfo = 0.0f;
   next_aux_lfo = 0.0f;

   waveguide_idx        = 0;
   next_waveguide_idx   = 0;
   queued_waveguide_idx = 0;

   ////current_loop_idx      = -1;

   filter_type = STSAMPLE_FLT_TYPE_NONE;
   mod_filter_cutoff    = 0.0f;
   mod_filter_pan       = 0.0f;
   mod_filter_offset    = 0.0f;
   mod_filter_resonance = 0.0f;

   mod_glide_switch = YAC_TRUE;
   mod_glide_speed  = 1.0f;
   mmdst_glide_speed = 1.0f;

   perfctl_poly_pressure = -1.0f;
   perfctl_pressure_max = 0.0f;

   resetBiquad();

   glide_src_note = glide_src_note_orig = 60.0f;
   glide_time = 0.0f;

   active_note_idx = 0;
}

sF32 StSampleVoice::getCurrentGlideNote(void) {
   sF32 r;
   sF32 t;

   if(mod_glide_switch)
   {
      switch(sample->glide_type)
      {
         default:
         case STSAMPLE_GLIDE_NONE:
            r = sF32(note);
            break;

         case STSAMPLE_GLIDE_TIME_LIN:
            r = glide_src_note + (note - glide_src_note) * glide_time;
            break;

         case STSAMPLE_GLIDE_TIME_EXP2:
            t = 1.0f - glide_time;
            t = t * t;
            t = 1.0f - t;
            r = glide_src_note + (note - glide_src_note) * t;
            break;

         case STSAMPLE_GLIDE_TIME_EXP3:
            t = 1.0f - glide_time;
            t = t * t * t;
            t = 1.0f - t;
            r = glide_src_note + (note - glide_src_note) * t;
            break;

         case STSAMPLE_GLIDE_TIME_EXP4:
            t = 1.0f - glide_time;
            t = t * t;
            t = t * t;
            t = 1.0f - t;
            r = glide_src_note + (note - glide_src_note) * t;
            break;

         case STSAMPLE_GLIDE_FREQ_LIN:
         case STSAMPLE_GLIDE_FREQ_EXP2:
         case STSAMPLE_GLIDE_FREQ_EXP3:
         case STSAMPLE_GLIDE_FREQ_EXP4:
            r = glide_src_note;
            break;
      }

      if(sample->b_glide_glissando)
      {
         r = sF32(sSI(r + 0.5f));
      }
   }
   else
   {
      r = sF32(note);
   }

   return r;
}

void StSampleVoice::tickGlide(void) {

   switch(sample->glide_type)
   {
      default:
      case STSAMPLE_GLIDE_NONE:
         break;

      case STSAMPLE_GLIDE_TIME_LIN:
      case STSAMPLE_GLIDE_TIME_EXP2:
      case STSAMPLE_GLIDE_TIME_EXP3:
      case STSAMPLE_GLIDE_TIME_EXP4:
         if(glide_src_note > note)
         {
            // Glide down
            glide_time += sample->glide_speed_time_down * mod_glide_speed * mmdst_glide_speed;
         }
         else
         {
            // Glide up
            glide_time += sample->glide_speed_time_up * mod_glide_speed * mmdst_glide_speed;
         }
         if(glide_time > 1.0f)
            glide_time = 1.0f;
         break;

      case STSAMPLE_GLIDE_FREQ_LIN:
         if(glide_src_note > note)
         {
            // Glide down
            glide_src_note -= sample->glide_speed_freq_down * mod_glide_speed * mmdst_glide_speed;
            if(glide_src_note < sF32(note))
               glide_src_note = sF32(note);
         }
         else
         {
            // Glide up
            glide_src_note += sample->glide_speed_freq_up * mod_glide_speed * mmdst_glide_speed;
            if(glide_src_note > sF32(note))
               glide_src_note = sF32(note);
         }
         break;

      case STSAMPLE_GLIDE_FREQ_EXP2:
         if(glide_src_note > note)
         {
            // Glide down
            sF64 t = glide_src_note - sF32(note);
            t = t / (glide_src_note_orig - sF32(note));
            t = 1.0 - t;
            t = t * t;
            t = 1.0 - t;
            t *= mod_glide_speed * mmdst_glide_speed;
            glide_src_note = sF32(glide_src_note - 2.0f * sample->glide_speed_freq_down * t);
            if(glide_src_note < sF32(note))
               glide_src_note = sF32(note);
         }
         else if(glide_src_note < note)
         {
            // Glide up
            sF64 t = sF32(note) - glide_src_note;
            t = t / (sF32(note) - glide_src_note_orig);
            t = 1.0 - t;
            t = t * t;
            t = 1.0 - t;
            t *= mod_glide_speed * mmdst_glide_speed;
            glide_src_note = sF32(glide_src_note + 2.0f * sample->glide_speed_freq_up * t);
            if(glide_src_note > sF32(note))
               glide_src_note = sF32(note);
         }
         break;

      case STSAMPLE_GLIDE_FREQ_EXP3:
         if(glide_src_note > note)
         {
            // Glide down
            sF64 t = glide_src_note - sF32(note);
            t = t / (glide_src_note_orig - sF32(note));
            t = 1.0 - t;
            t = t * t * t;
            t = 1.0 - t;
            t *= mod_glide_speed * mmdst_glide_speed;
            glide_src_note = sF32(glide_src_note - 2.0f * sample->glide_speed_freq_down * t);
            if(glide_src_note < sF32(note))
               glide_src_note = sF32(note);
         }
         else if(glide_src_note < note)
         {
            // Glide up
            sF64 t = sF32(note) - glide_src_note;
            t = t / (sF32(note) - glide_src_note_orig);
            t = 1.0 - t;
            t = t * t * t;
            t = 1.0 - t;
            t *= mod_glide_speed * mmdst_glide_speed;
            glide_src_note = sF32(glide_src_note + 2.0f * sample->glide_speed_freq_up * t);
            if(glide_src_note > sF32(note))
               glide_src_note = sF32(note);
         }
         break;

      case STSAMPLE_GLIDE_FREQ_EXP4:
         if(glide_src_note > note)
         {
            // Glide down
            sF64 t = glide_src_note - sF32(note);
            t = t / (glide_src_note_orig - sF32(note));
            t = 1.0 - t;
            t = t * t;
            t = t * t;
            t = 1.0 - t;
            t *= mod_glide_speed * mmdst_glide_speed;
            glide_src_note = sF32(glide_src_note - 2.0f * sample->glide_speed_freq_down * t);
            if(glide_src_note < sF32(note))
               glide_src_note = sF32(note);
         }
         else if(glide_src_note < note)
         {
            // Glide up
            sF64 t = sF32(note) - glide_src_note;
            t = t / (sF32(note) - glide_src_note_orig);
            t = 1.0 - t;
            t = t * t;
            t = t * t;
            t = 1.0 - t;
            t *= mod_glide_speed * mmdst_glide_speed;
            glide_src_note = sF32(glide_src_note + 2.0f * sample->glide_speed_freq_up * t);
            if(glide_src_note > sF32(note))
               glide_src_note = sF32(note);
         }
         break;
   }
}

void StSampleVoice::resetBiquad(void) {
   biquad_a_1[0].reset();
   biquad_a_1[1].reset();
   biquad_a_2[0].reset();
   biquad_a_2[1].reset();

   biquad_b_1[0].reset();
   biquad_b_1[1].reset();
   biquad_b_2[0].reset();
   biquad_b_2[1].reset();
}

sUI StSampleVoice::getVoiceIndex(void) {
   return voice_idx;
}

sBool StSampleVoice::isAllocated(void) {
   //printf("xxx isAllocated: this=0x%p volramp_fadeout_countdown=%d\n", this, volramp_fadeout_countdown);
   return (b_playing || queued_noteon.b_valid) && (volramp_fadeout_countdown < 0);
}

sBool StSampleVoice::canAllocate(void) {
   return !queued_noteon.b_valid;// && !b_used;
}

sBool StSampleVoice::isSoftStopped(void) {
   return Dsoftstopped();
}

void StSampleVoice::reallyStartVoice(const StSampleVoiceNoteOnParams *_params,
                                     sBool _bDelayed)
{
   // Note: if _bDelayed is 1, the voice was delayed because of volramp fadeout. 
   //       -> do not reset block size since this fxn is called within the render() loop
   //       -> assumes that _mixRate has not changed

   ////printf("xxx StSampleVoice::reallyStartVoice: this=0x%p current_vol=%f next_vol=%f\n", this, current_vol, next_vol);
   
   queued_noteon.b_valid = 0;

   // printf("xxx StSampleVoice::reallyStartVoice: note=%d vel=%f mod=%f vol=%f pan=%f freq=%f ts=%f so=%f cl=%f\n",
   //        _params->_note, 
   //        _params->_vel, 
   //        _params->_mod, 
   //        _params->_vol, 
   //        _params->_pan, 
   //        _params->_freq, 
   //        _params->_timestretch, 
   //        _params->_sampleoff, 
   //        _params->_cyclelen
   //        );


   sample       = _params->_sample;
   freq_table   = _params->_freqTable;
   b_playing    = false;
   mix_rate     = _params->_mixRate;
   note         = _params->_note;
   replay_ticks = 0;

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      anticlick_fadeout_countdown = 0;
      anticlick_granular_smpoffinterpol_countdown = 0;
      sample->waveform->ui_last_played_offset = 0.0f;
      current_delay_countdown = (sUI) ((sample->sample_delay * _params->_mixRate) / 1000);
   }

   // Patch that prevents the samplerengine from crashing in case a waveform could not be loaded
   if(NULL == sample->waveform->sample_data)
   {
      b_used    = 0;
      b_playing = 0;
      b_release = 0;
      return;
   }

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      current_orig_sample_len = current_sample_len = sample->sample_len;
      current_loop_idx      = -1;
      current_loop_countdown = getCurrentSampleLoops() ? 1 : 0;
      samples_until_end_of_loopstep = 0;
   }

   // Set initial controllers/params
   mod_vol                = _params->_vol;
   mod_vol2               = _params->_vol2;
   mod_pan                = _params->_pan;
   mod_pan2               = _params->_pan2;
   mod_freq               = _params->_freq;
   mod_freq2              = _params->_freq2;
   mod_mod                = _params->_mod;
   mod_timestretch        = _params->_timestretch;
   mmdst_timestretch      = 0.0f;
   mod_timestretch_bend   = _params->_timestretch_bend;
   mmdst_timestretch_bend = 0.0f;
   mod_sampleoff          = _params->_sampleoff;
   mod_sampleoff2         = _params->_sampleoff2;
   mmdst_sampleoff        = 0.0f;

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      if(sample->sampleoffset_rand_amount > 0.0f)
      {
         mod_sampleoff2 += ((sF32) ((rand()*(0.999999999999f/((sF32)RAND_MAX)))*sample->sampleoffset_rand_amount));
      }

      if(sample->b_invert_sampleoffset_velocity_amount)
      {
         mod_sampleoff2 += sample->sampleoffset_velocity_amount - (sample->sampleoffset_velocity_amount * _params->_vel);
      }
      else
      {
         mod_sampleoff2 += (sample->sampleoffset_velocity_amount * _params->_vel);
      }
   }

   mod_cyclelen      = _params->_cyclelen;
   mmdst_cyclelen    = 1.0f;
   mod_sampleshift   = _params->_sampleshift;
   mmdst_sampleshift = 0.0f;
   mod_sampleshift_endofloop   = _params->_sampleshift_endofloop;
   mmdst_sampleshift_endofloop_last = -1.0f;
   mod_altvolume     = _params->_altvolume;
   mod_jumptoloop    = _params->_jumptoloop;

   last_sample_sampleshift_eol_l = 0.0f;
   last_sample_sampleshift_eol_r = 0.0f;;
   sampleshift_eol_xfade_frames_left = 0u;

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      waveguide_idx = queued_waveguide_idx = next_waveguide_idx = _params->_waveguideidx;

      // Randomize sample offset ? (test)
      ///mod_sampleoff += ((rand()*(0.999999999999f/((sF32)RAND_MAX)) )*0.0001) - (0.0001*0.5);

      // Reset loop vars/registers
      {
         for(int i=0; i<16; i++)
         {
            loop_vars[i] = 0;
         }
      }

      // Determine number of sample loops
      if(getCurrentSampleLoops())
      {
         num_sample_loops = (sSI) getCurrentSampleLoops()->num_elements / 3; // div 3 because of sampleoff,len,repeats
      }
      else
      {
         num_sample_loops = 0;
      }

      b_timedloop = 0;
      fadein_countdown = -1;
      fadeout_countdown = -1;

      b_fading_out = 0;
      b_zero = 0;
   }

   // printf("\n\nxxx reallyStartVoice: this=0x%p\n", this);
   // printf("xxx reallyStartVoice: this=0x%p num_sample_loops=%d sample=0x%p sample->sample_loops=0x%p\n", this, num_sample_loops, sample, sample->sample_loops);
   
   b_release = 0;

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      // Handle loop-only replay
      //
      if(0 == current_sample_len)
      {
         sF32 *dummy;
         b_timedloop = sample->b_timedloop && (num_sample_loops > 0);
         timedloop_base = sample->timedloop_base;
         if(!handleEndOfLoop(&current_play_offset, &dummy))
         {
            // no loops, nothing to play
            return;
         }
      }
      else
      {
         // Absolute sample offset
         current_sample_offset = sample->sample_offset;
         ////printf("xxx current_sample_offset = %u\n", current_sample_offset);
      }

      // Calculate initial (relative) play offset
      last_relsmpoff = -1; // Force calculation of current_play_offset
      calcCurrentSampleLen();

      current_ts_offset   = 0.0f;
      samples_since_cycle = 0;
      fragment_interpol   = 0.0f;

      if(sample->b_timestretch)
      {
         sF32 ts;
         calcCurrentCycleLen(ts);
         calcCurrentSampleLen();

         if(sample->timestretch_startphase_rand_amount > 0.0f)
         { 
            current_ts_offset = (rand() % (sUI)current_cyclelen ) * sample->timestretch_startphase_rand_amount;
            while(current_ts_offset >= current_cyclelen)
               current_ts_offset -= current_cyclelen;
         }

      }

      calcCurrentOffset();

      if(sample->b_waveguide)
      {
         current_play_offset = (sF64) waveguide_next_play_offset;
      }

      // Clip play offset
      ////printf("xxx clip play offset: relSmpOff=%f off=%f currentLen=%u\n", relSmpOff, current_play_offset, current_sample_len);
      if( ((sUI)current_play_offset) >= current_sample_len)
      {
         if(0 != current_sample_len)
         {
            current_play_offset = ((sF64) current_sample_len)-1.0f;
         }
         else
         {
            current_play_offset = 0.0f;
         }
      }

      if(sample->b_waveguide)
      {
         // Copy back clipped offset
         waveguide_next_play_offset = (sUI) current_play_offset;
      }

      ////yac_host->printf("xxx reallyStartVoice: current_play_offset=%f\n", current_play_offset);

      ////printf("xxx reallyStartVoice: off=%u len=%u\n", current_sample_offset, current_sample_len);
   

  
      // Lazy fill zero samples array
      if(b_fill_zero_samples)
      {
         b_fill_zero_samples = 0;
         sUI i;
         for(i=0; i < (2*NUM_ZERO_SAMPLES); i++)
         {
            zero_samples[i] = 0.0f;
         }
      }
   }
   
 
   //yac_host->printf("xxx startvoice: sample has %d loops. b_timedloop=%d\n", num_sample_loops, b_timedloop);

   sUI retrigMask = _params->_retrigmask;

   if(b_glide)
   {
      if(!sample->b_glide_retrig_env_freq)
         retrigMask &= ~1u;

      if(!sample->b_glide_retrig_env_vol)
         retrigMask &= ~2u;

      if(!sample->b_glide_retrig_env_pan)
         retrigMask &= ~4u;

      if(!sample->b_glide_retrig_env_aux)
         retrigMask &= ~8u;

      if(!sample->b_glide_retrig_lfo_freq)
         retrigMask &= ~16u;

      if(!sample->b_glide_retrig_lfo_vol)
         retrigMask &= ~32u;

      if(!sample->b_glide_retrig_lfo_pan)
         retrigMask &= ~64u;

      if(!sample->b_glide_retrig_lfo_aux)
         retrigMask &= ~128u;
   }

   
   // Start ADSR envelopes and LFOs
   startADSRAndLFO(retrigMask, true/*bNoteOn/bResetMod*/ && !b_glide);
   // yac_host->printf("xxx reallystartvoice: after startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   lfo_freq.mod_speed = _params->_lfofreqspd;
   lfo_freq.mod_level = _params->_lfofreqlvl;
   lfo_freq.mmdst_speed = 1.0f;
   lfo_freq.mmdst_level = 1.0f;

   sample->lfo_freq_global.mod_speed = _params->_lfofreqspd;
   sample->lfo_freq_global.mod_level = _params->_lfofreqlvl;
   sample->lfo_freq_global.mmdst_speed = 1.0f;
   sample->lfo_freq_global.mmdst_level = 1.0f;

   lfo_vol.mod_speed = _params->_lfovolspd;
   lfo_vol.mod_level = _params->_lfovollvl;
   lfo_vol.mmdst_speed = 1.0f;
   lfo_vol.mmdst_level = 1.0f;

   sample->lfo_vol_global.mod_speed = _params->_lfovolspd;
   sample->lfo_vol_global.mod_level = _params->_lfovollvl;
   sample->lfo_vol_global.mmdst_speed = 1.0f;
   sample->lfo_vol_global.mmdst_level = 1.0f;

   lfo_pan.mod_speed = _params->_lfopanspd;
   lfo_pan.mod_level = _params->_lfopanlvl;
   lfo_pan.mmdst_speed = 1.0f;
   lfo_pan.mmdst_level = 1.0f;

   sample->lfo_pan_global.mod_speed = _params->_lfopanspd;
   sample->lfo_pan_global.mod_level = _params->_lfopanlvl;
   sample->lfo_pan_global.mmdst_speed = 1.0f;
   sample->lfo_pan_global.mmdst_level = 1.0f;

   lfo_aux.mod_speed = _params->_lfoauxspd;
   lfo_aux.mod_level = _params->_lfoauxlvl;
   lfo_aux.mmdst_speed = 1.0f;
   lfo_aux.mmdst_level = 1.0f;

   sample->lfo_aux_global.mod_speed = _params->_lfoauxspd;
   sample->lfo_aux_global.mod_level = _params->_lfoauxlvl;
   sample->lfo_aux_global.mmdst_speed = 1.0f;
   sample->lfo_aux_global.mmdst_level = 1.0f;

   adsr_freq.mod_speed     = _params->_adsrfreqspd;
   adsr_freq.mod_level     = _params->_adsrfreqlvl;
   adsr_freq.mod_intensity = _params->_adsrfreqint;
   adsr_freq.mod_aspeed    = _params->_afreqspd;
   adsr_freq.mod_sspeed    = _params->_sfreqspd;
   adsr_freq.mod_rspeed    = _params->_rfreqspd;

   adsr_vol.mod_speed     = _params->_adsrvolspd;
   adsr_vol.mod_level     = _params->_adsrvollvl;
   adsr_vol.mod_intensity = _params->_adsrvolint;
   adsr_vol.mod_aspeed    = _params->_avolspd;
   adsr_vol.mod_sspeed    = _params->_svolspd;
   adsr_vol.mod_rspeed    = _params->_rvolspd;

   adsr_pan.mod_speed     = _params->_adsrpanspd;
   adsr_pan.mod_level     = _params->_adsrpanlvl;
   adsr_pan.mod_intensity = _params->_adsrpanint;
   adsr_pan.mod_aspeed    = _params->_apanspd;
   adsr_pan.mod_sspeed    = _params->_spanspd;
   adsr_pan.mod_rspeed    = _params->_rpanspd;

   adsr_aux.mod_speed     = _params->_adsrauxspd;
   adsr_aux.mod_level     = _params->_adsrauxlvl;
   adsr_aux.mod_intensity = _params->_adsrauxint;
   adsr_aux.mod_aspeed    = _params->_aauxspd;
   adsr_aux.mod_sspeed    = _params->_sauxspd;
   adsr_aux.mod_rspeed    = _params->_rauxspd;

   if(!b_glide)
      filter_type          = sample->filter_type;
   mod_filter_cutoff    = _params->_filter_cutoff;
   mod_filter_pan       = _params->_filter_pan;
   mod_filter_offset    = _params->_filter_offset;
   mod_filter_resonance = _params->_filter_resonance;
   if(!b_glide)
      resetBiquad();

   mod_glide_switch = _params->_glide_switch;
   mod_glide_speed  = _params->_glide_speed;
   mmdst_glide_speed = 1.0f;

   perfctl_poly_pressure = _params->_perfctl_poly_pressure;
   ::memcpy((void*)perf_ctl_on, (void*)sample_player->perf_ctl, sizeof(perf_ctl_on));

   perfctl_pressure_max = 0.0f;
   
   // Determine "max" volume according to velocity zone map
   cached_vel_vol = sample->calcVelZoneVolume(_params->_vel);

   if(!_bDelayed)
   {
      float_block_size     = STSAMPLEVOICE_BLOCK_LEN * _params->_mixRate;
      float_block_off      = 0.0f;
      current_block_size   = (sUI) float_block_size;
      current_block_offset = 0;
   }

   // yac_host->printf("xxx reallystartvoice: 2 startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      // (block-based) volume ramping
      volramp_fadeout_countdown = -1;
      if(sample->num_volramp_steps_in > 0)
      {
         volramp_fadein_countdown = sample->num_volramp_steps_in;// + 1; // +1 since calcNextBlockState is called twice
         volramp_fade_vol = 0.0f;
      }
      else
      {
         volramp_fadein_countdown = -1;
         volramp_fade_vol = 1.0f;
      }
   }

   // yac_host->printf("xxx reallystartvoice: 3 startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   calcNextBlockState(); // Calc initial state

   // yac_host->printf("xxx reallystartvoice: 4 startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   calcNextBlockState(); // Calc "next" state

   // yac_host->printf("xxx reallystartvoice: 5 startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   if(!b_glide || sample->b_glide_retrig_sample)
   {
      if((sample->b_timedloop && sample->b_timedloop_fade))///// || b_restartfadein)
      {
         // Fadein first "n" samples to avoid clicks
         fadein_countdown = VOLRAMP_NUMSTEPS;
      }
   }

   ////printf("xxx reallyStartVoice: cvol=%f nvol=%f\n", current_vol, next_vol);

   // // printf("[---] reallyStartVoice: current_sample_len is %u\n", current_sample_len);
   // // if(0 == current_sample_len)
   // // {
   // //    printf("[---] reallyStartVoice: current_sample_len is 0 !!\n");
   // //    stopVoice();
   // //    return;
   // // }
   
   b_playing = true;

   // yac_host->printf("xxx reallystartvoice: LEAVE adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);
}

void StSampleVoice::stopVoice(void) {
   // stop *immediately*

   ////yac_host->printf("xxx StSampleVoice::stopVoice: this=0x%p\n", this);

   if(b_playing)
   {
      if(current_delay_countdown > 0)
      {
         // Sample still delaying, do not fade out
         current_delay_countdown = 0;
         anticlick_fadeout_countdown = 0;
      }
      else
      {
         // fade out last played sample to avoid clicks
         anticlick_fadeout_countdown = ANTICLICK_FADEOUT_TIME; 
      }
      // Note: voice_key is unset as soon as sample has been faded out

      b_playing = false;
   }

   if(NULL != sample)
   {
      sample->waveform->ui_last_played_offset = -1.0f;
   }
}

void StSampleVoice::softStopVoice(void) {

   // Already fading out ?
   if(volramp_fadeout_countdown < 0)
   {
      //printf("xxx StSampleVoice::softStopVoice: this=0x%p b_playing=%d volramp_fadeout_countdown=%d\n", this, b_playing, volramp_fadeout_countdown);

      queued_noteon.b_valid = 0;

      b_used = 0; // => can allocate

      // //xxx workaround to prevent clicks in "14Sep2010" testcase (bassline)
      //    ==> should be fixed now
      // //stopVoice();

      // stop after volume ramp fadeout
      if(b_playing)
      {
         volramp_fadeout_countdown = (sSI)sample->num_volramp_steps_out;
      }

      if(volramp_fadeout_countdown < 0)
         volramp_fadeout_countdown = 0;

      if(NULL != sample)
      {
         sample->waveform->ui_last_played_offset = -1.0f;
      }
   }
}


void StSampleVoice::prepareToPlay(StSamplePlayer *_samplePlayer, StSample *_sample, sSI _voiceKey) {
   // called directly after voice allocation
   //  (todo) this will not work with softStopVoice() / fadeout since sampleptr is already set here
   //          move this to reallyStartVoice ?
   if(NULL == sample)
   {
      sample = _sample; // normally done in startVoice(), but "sample" must always point to a valid sample
   }
   sample_player = _samplePlayer; // (todo) should be only done once in init
   voice_key     = _voiceKey;
   b_used        = 1;

   for(sUI mmIdx = 0u; mmIdx < STSAMPLE_NUM_MODMATRIX_ENTRIES; mmIdx++)
   {
      modmatrix_amt[mmIdx] = 0.0f;
   }

   // b_release     = 0; // moved to reallyStartVoice();

   // mod_vol         = 1.0f;
   // mod_pan         = 0.0f;
   // mod_freq        = 0.0f;
   // mod_mod         = 0.0f;
   // mod_timestretch = 0.0f;
   // mod_sampleoff   = 0.0f;
   // mod_cyclelen    = 0.0f;
   // mod_sampleshift = 0.0f;
   // mod_altvolume   = 1.0f;
   // mod_jumptoloop  = -1;

   current_delay_countdown = 0;

   for(sUI randIdx = 0u; randIdx < 4u; randIdx++)
   {
      noteon_rand_p[randIdx] = (sF32) ((::rand()*(0.999999999999f/((sF32)RAND_MAX)))*1.0f);
      noteon_rand_pm[randIdx] = noteon_rand_p[randIdx] * 2.0f - 1.0f;
   }

   active_note_idx = sample_player->getNumActiveNotes();

   release_velocity = 0.0f;
}

void StSampleVoice::initStartedVoice(void) {
   // called when voice should actually start playing (after ctl/param have been set)

   // yac_host->printf("xxx initStartedVoice: queued_noteon.b_valid=%d\n", queued_noteon.b_valid);

   b_alloc = YAC_FALSE;

   if(queued_noteon.b_valid)
   {
      b_restartfadein = 0;

      if(b_playing && (sample->num_volramp_steps_out>0)) // fadeout current sample?
      {
         // Delay actual note-on until sample has been faded out
         // TODO: make ctl. modification attempts block until new sample has really been started?
         volramp_fadeout_countdown = (sSI)sample->num_volramp_steps_out;
         // printf("xxx StSampleVoice::initStartedVoice: fadeout current voice, cd=%d\n", volramp_fadeout_countdown);
      }
      else
      {
         // Initialize voice immediately
         // printf("xxx StSampleVoice::initStartedVoice: start immediately. b_playing=%d\n", b_playing);
         volramp_fadeout_countdown = -1;
         queued_noteon.b_valid     = 0;
         reallyStartVoice(&queued_noteon, 0);
         // printf("xxx 2 StSampleVoice::initStartedVoice: start immediately. b_playing=%d\n", b_playing);
      }
   }
   else
   {
      b_playing = false;
      b_used    = false;
      voice_key = 0;
   }
}

void StSampleVoice::_restartVoice(void) {

   // (note) this causes problems with voice-allocation:
   //         the method is called, when an arpeggio is played (noteOnRetrig())
   //         but during note-on, the next (mono) voice has not been allocated, yet.
   //         setting b_valid to true again will retrigger the previous voice when
   //         the volramp fadeout has finished (softStopVoice())
   //         ==> must not be called when a voice is going to be allocated on the same replay tick

   // ///////////////if(b_playing)
   // printf("xxx StSampleVoice::restartVoice: this=0x%p volramp_fadeout_countdown=%d\n", this, volramp_fadeout_countdown);
   if(!Dsoftstopped())
   {
      // Re-Initialize voice immediately
      b_used                    = true;
      queued_noteon.b_valid     = true;
      volramp_fadeout_countdown = (sSI)(sample->num_volramp_steps_out);
      b_playing                 = true;
      b_restartfadein           = true;
   }
}


void StSampleVoice::_startVoice(YAC_Object *_sample, YAC_Object *_freqTable, sF32 _mixRate, 
                                sSI _note, sF32 _vel, sF32 _mod, sF32 _vol, sF32 _pan, sF32 _freq) 
{
   if(YAC_CHK(_sample, clid_StSample))
   {
      if(YAC_BCHK(_freqTable, YAC_CLID_FLOATARRAY))
      {
         replay_ticks = 0; // Reset ticks so multi-sample voice is not reallocated in same tick

         queued_noteon._sample            = (StSample*) _sample;
         queued_noteon._freqTable         = (YAC_FloatArray *) _freqTable;
         queued_noteon._mixRate           = _mixRate;
         queued_noteon._note              = _note;
         queued_noteon._vel               = _vel;
         queued_noteon._mod               = _mod;
         queued_noteon._vol               = _vol;
         queued_noteon._vol2              = 1.0f;
         queued_noteon._pan               = _pan;
         queued_noteon._pan2              = 0.0f;
         queued_noteon._freq              = _freq;
         queued_noteon._freq2             = 0.0f;
         queued_noteon._timestretch       = 0.0f;
         queued_noteon._timestretch_bend  = 0.0f;
         queued_noteon._sampleoff         = 0.0f;
         queued_noteon._sampleoff2        = 0.0f;
         queued_noteon._cyclelen          = 1.0f;
         queued_noteon._sampleshift       = 0.0f;
         queued_noteon._sampleshift_endofloop  = -1.0f;
         queued_noteon._altvolume         = 1.0f;
         queued_noteon._retrigmask        = 0xff; // start all env+LFOs
         queued_noteon._lfofreqspd        = 1.0f;
         queued_noteon._lfofreqlvl        = 1.0f;
         queued_noteon._lfovolspd         = 1.0f;
         queued_noteon._lfovollvl         = 1.0f;
         queued_noteon._lfopanspd         = 1.0f;
         queued_noteon._lfopanlvl         = 1.0f;
         queued_noteon._lfoauxspd         = 1.0f;
         queued_noteon._lfoauxlvl         = 1.0f;
         queued_noteon._adsrfreqspd       = 1.0f;
         queued_noteon._adsrfreqlvl       = 1.0f;
         queued_noteon._adsrfreqint       = 1.0f;
         queued_noteon._adsrvolspd        = 1.0f;
         queued_noteon._adsrvollvl        = 1.0f;
         queued_noteon._adsrvolint        = 1.0f;
         queued_noteon._adsrpanspd        = 1.0f;
         queued_noteon._adsrpanlvl        = 1.0f;
         queued_noteon._adsrpanint        = 1.0f;
         queued_noteon._adsrauxspd        = 1.0f;
         queued_noteon._adsrauxlvl        = 1.0f;
         queued_noteon._adsrauxint        = 1.0f;
         queued_noteon._jumptoloop        = -1;
         queued_noteon._waveguideidx      = queued_noteon._sample->waveguide_idx;
         queued_noteon._afreqspd          = 1.0f;
         queued_noteon._sfreqspd          = 1.0f;
         queued_noteon._rfreqspd          = 1.0f;
         queued_noteon._avolspd           = 1.0f;
         queued_noteon._svolspd           = 1.0f;
         queued_noteon._rvolspd           = 1.0f;
         queued_noteon._apanspd           = 1.0f;
         queued_noteon._spanspd           = 1.0f;
         queued_noteon._rpanspd           = 1.0f;
         queued_noteon._aauxspd           = 1.0f;
         queued_noteon._sauxspd           = 1.0f;
         queued_noteon._rauxspd           = 1.0f;
         queued_noteon._filter_cutoff    = 0.0f;
         queued_noteon._filter_pan       = 0.0f;
         queued_noteon._filter_offset    = 0.0f;
         queued_noteon._filter_resonance = 0.0f;
         queued_noteon._glide_switch     = YAC_TRUE;
         queued_noteon._glide_speed      = 1.0f;
         queued_noteon._perfctl_poly_pressure = -1.0f;

         queued_noteon.b_valid       = 1;

         //printf("xxx StSampleVoice::startVoice: this=0x%p note=%d vel=%f\n", this, _note, _vel);

         // Note: these queued params can be overwritten by setMod(), setVol(), ..
         //       before the voice is actually started.
      }
      else
      {
         Dyac_throw_def(InvalidPointer, "_freqTable is not a valid FloatArray object");
      }
   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_sample is not a valid StSample object");
   }
}

void StSampleVoice::noteOff(sF32 _vel) {

   ////printf("xxx noteOff: vel=%f cached_vel_vol=%f\n", _vel, cached_vel_vol);

   // cached_vel_vol = 
   //    (cached_vel_vol * (1.0f - sample->volume_velocity_amount)) + 
   //    (cached_vel_vol * _vel * sample->volume_velocity_amount);
   // if(cached_vel_vol > 1.0f)
   // {
   //    cached_vel_vol = 1.0f;
   // }
   // else if(cached_vel_vol < 0.00001f)
   // {
   //    cached_vel_vol = 0.0f;
   // }
   
   /////printf("xxx noteOff b_release=%d delay_countdown=%d\n", b_release, current_delay_countdown);

   // // // queued_noteon.b_valid = 0;

   release_velocity = _vel;

   if(current_delay_countdown > 0)
   {
      // Has not started to play, yet
      ////printf("xxx stopVoice 1\n");
      stopVoice();
   }
   else if(!b_release)
   {
      b_release = YAC_TRUE;

      if(adsr_vol.adsr)
      {
         adsr_vol.noteOff();

         if(!adsr_vol.isFinished())
         {
            adsr_pan.noteOff();
            adsr_freq.noteOff();
            adsr_aux.noteOff();
      
            // jump to release loop step
            if( ((sUI)sample->noteoff_loop_index) < (sUI)num_sample_loops)
            {
               current_loop_idx = sample->noteoff_loop_index;

               // // if(!b_timedloop && sample->b_timedloop)
               if(sample->b_timedloop)
               {
                  // [04Jan2019] Re-Enable timedloop after infinite loop
                  samples_until_end_of_loopstep = (VOLRAMP_NUMSTEPS + 1);  // fade-out + start next loop after volume ramp
                  b_fading_out = YAC_FALSE;
                  b_timedloop = YAC_TRUE;
                  current_loop_idx--;  // inc'd in handleEndOfLoop()
               }
               else if(sample->b_noteoff_immediate_loop_jump)  //  || b_timedloop
               {
                  // may cause clicks, especially with "synthetic" waveforms
                  current_loop_countdown = 1;
                  current_loop_idx--;
                  sF32 *dummy;
                  handleEndOfLoop(&current_play_offset, &dummy);
               }
            }
         }
         else
         {
            // No Release phase, "immediately" stop voice (after volume ramp down)
            softStopVoice();
            ////printf("xxx StSampleVoice::noteOff: no release phase, softStopVoice 2\n");
         }
      }
      else
      {
         // No Release phase, "immediately" stop voice (after volume ramp down)
         softStopVoice();
         ////printf("xxx StSampleVoice::noteOff: no release phase, softStopVoice\n");
      }
   }
}

void StSampleVoice::calcNextBlockState(void) {
   // Copy state
   current_vol  = next_vol;
   current_pan  = next_pan;
   current_freq = next_freq;
   current_aux_adsr  = next_aux_adsr;
   current_aux_lfo  = next_aux_lfo;

   //printf("xxx calcNextBlockState: this=0x%p b_playing=%d volramp_fadeout_countdown=%d\n", this, b_playing, volramp_fadeout_countdown);

   // Do sample fadeout
   if(volramp_fadeout_countdown >= 0)
   {
      
      // Continue fade out
      volramp_fadeout_countdown--;
      if(-1 == volramp_fadeout_countdown)
      {
         ////printf("xxx StSampleVoice: fade-out finished this=0x%p b_valid=%d\n", this, queued_noteon.b_valid);
         
         // Fadeout finished
         if(queued_noteon.b_valid) // xx paranoia check, should never be 0 ATP
         {
            // Start queued sample
            volramp_fade_vol = 0.0f;
            queued_noteon.b_valid = 0;
            reallyStartVoice(&queued_noteon, 1);
            // Note: reallyStartVoice re-enters calcNextBlockState()
            return;
         }
         else
         {
            ////printf("xxx stopVoice 2\n");
            stopVoice();

            // Allow re-allocation of voice
            voice_key = 0;
         }
      }
      else
      {
         volramp_fade_vol = (((sF32)volramp_fadeout_countdown) / sample->num_volramp_steps_out);
         ////yac_host->printf("xxx volramp_fade_vol=%f countdown=%d\n", volramp_fade_vol, volramp_fadeout_countdown);
      }
   }
   else if(volramp_fadein_countdown >= 0)
   {
      // Do sample fade in
      volramp_fade_vol = 1.0f - ( ((sF32)volramp_fadein_countdown) / sample->num_volramp_steps_in);
      volramp_fadein_countdown--;
      // yac_host->printf("xxx volramp_fade_vol=%f cd=%d/%d\n", volramp_fade_vol, volramp_fadein_countdown, sample->num_volramp_steps_in);
   }



   // Update modifiers to values set by the application
   LOCK_MTX_MOD;

   // Calc new state
   ////printf("xxx sample.vol=%f cached_vel_vol=%f mod_vol=%f sp.vol=%f\n", sample->volume, cached_vel_vol, mod_vol, sample_player->mod_vol);

   next_freq = getCurrentGlideNote();
   next_freq = next_freq + sample->transpose + mod_freq + mod_freq2 + sample_player->mod_freq + sample_player->mod_freq2;
   // printf("xxx next_freq=%f\n", next_freq);
   if(NULL != sample->parent_samplebank)
   {
      next_freq += sample->parent_samplebank->transpose;
   }

   next_vol  = 
      sample->volume               * 
      cached_vel_vol               * 
      mod_vol                      * 
      mod_vol2                     * 
      sample_player->mod_vol       * 
      mod_altvolume                * 
      sample_player->mod_altvolume * 
      sample->calcKeyModZoneVolume (sF32(note), getEffectiveMod());////mod_mod + sample_player->mod_mod);

#if 0
   static int xxxVol = 0;
   if(0 == (xxxVol++&127))
   {
      yac_host->printf("xxx next_vol=%f sample->volume=%f cached_vel_vol=%f mod_vol=%f mod_vol2=%f sample_player->mod_vol=%f mod_altvolume=%f sample_player->mod_altvolume=%f\n", next_vol, sample->volume, cached_vel_vol, mod_vol, mod_vol2, sample_player->mod_vol, mod_altvolume, sample_player->mod_altvolume);
   }
#endif

   if(NULL != sample->parent_samplebank)
   {
      next_vol *= sample->parent_samplebank->volume;
   }

   ////yac_host->printf("xxx calcNextBlockState: next_vol=%f sample->volume=%f cached_vel_vol=%f mod_vol=%f sample_player->mod_vol=%f mod_altvolume=%f sample_player->mod_altvolume=%f\n", next_vol, sample->volume, cached_vel_vol, mod_vol, sample_player->mod_vol, mod_altvolume, sample_player->mod_altvolume);

   next_pan  = sample->pan + mod_pan + mod_pan2 + sample_player->mod_pan;

   if(NULL != sample->parent_samplebank)
   {
      next_pan += sample->parent_samplebank->pan;
   }

   next_aux_adsr = 0.0f;
   next_aux_lfo = 0.0f;

   ////printf("xxx next_pan=%f smp=%f mod=%f sp=%f\n", next_pan, sample->pan, mod_pan, sample_player->mod_pan);

   UNLOCK_MTX_MOD;

   // yac_host->printf("xxx nextblockstate: 1 startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   struct {
      struct {
         sF32 freq;
         sF32 vol;
         sF32 pan;
         sF32 aux;
      } env;
      struct {
         sF32 freq;
         sF32 vol;
         sF32 pan;
         sF32 aux;
      } lfo;
   } mmsrc;

   sBool bFreqLFOGlobal = YAC_FALSE;
   sBool bVolLFOGlobal  = YAC_FALSE;
   sBool bPanLFOGlobal  = YAC_FALSE;
   sBool bAuxLFOGlobal  = YAC_FALSE;

   // Freq LFO
   if((NULL != sample->lfo_freq) && (sample->lfo_freq->b_global))
   {
      if(sample->global_lfo_tick_nr != sample_player->global_tick_nr)
      {
         mmsrc.lfo.freq = sample->lfo_freq_global.tick();
      }
      else
      {
         mmsrc.lfo.freq = sample->lfo_freq_global.last_level;
      }
      bFreqLFOGlobal = YAC_TRUE;
   }
   else
   {
      mmsrc.lfo.freq = lfo_freq.tick();
   }

   // Vol LFO
   if((NULL != sample->lfo_vol) && (sample->lfo_vol->b_global))
   {
      if(sample->global_lfo_tick_nr != sample_player->global_tick_nr)
      {
         mmsrc.lfo.vol = sample->lfo_vol_global.tick();
      }
      else
      {
         mmsrc.lfo.vol = sample->lfo_vol_global.last_level;
      }
      bVolLFOGlobal = YAC_TRUE;
   }
   else
   {
      mmsrc.lfo.vol = lfo_vol.tick();
   }

   // Pan LFO
   if((NULL != sample->lfo_pan) && (sample->lfo_pan->b_global))
   {
      if(sample->global_lfo_tick_nr != sample_player->global_tick_nr)
      {
         mmsrc.lfo.pan = sample->lfo_pan_global.tick();
      }
      else
      {
         mmsrc.lfo.pan = sample->lfo_pan_global.last_level;
      }
      bPanLFOGlobal = YAC_TRUE;
   }
   else
   {
      mmsrc.lfo.pan = lfo_pan.tick();
   }

   // Aux LFO
   if((NULL != sample->lfo_aux) && (sample->lfo_aux->b_global))
   {
      if(sample->global_lfo_tick_nr != sample_player->global_tick_nr)
      {
         mmsrc.lfo.aux = sample->lfo_aux_global.tick();
      }
      else
      {
         mmsrc.lfo.aux = sample->lfo_aux_global.last_level;
      }
      bAuxLFOGlobal = YAC_TRUE;
   }
   else
   {
      mmsrc.lfo.aux = lfo_aux.tick();
   }

   sample->global_lfo_tick_nr = sample_player->global_tick_nr;

   mmsrc.env.freq = adsr_freq.adsr ? adsr_freq.tick() : 0.0f;
   mmsrc.env.vol  = adsr_vol.adsr  ? adsr_vol.tick()  : 0.0f;
   mmsrc.env.pan  = adsr_pan.adsr  ? adsr_pan.tick()  : 0.0f;
   mmsrc.env.aux  = adsr_aux.adsr  ? adsr_aux.tick()  : 0.0f;

   if(perfctl_poly_pressure >= perfctl_pressure_max)
   {
      perfctl_pressure_max = perfctl_poly_pressure;
   }
   else if(sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PRESSURE] >= perfctl_pressure_max)
   {
      perfctl_pressure_max = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PRESSURE];
   }

   // (note) vol/pan destinations are next_vol and next_pan
   struct {
      sF32 freq;
      sF32 freq_oct;
      sF32 freq_fifth;
      sF32 freq_fourth;
      sF32 freq_semi;
      sF32 freq_fine;
      sF32 freq_beat;
      sF32 flt_cutoff;
      sF32 flt_pan;
      sF32 flt_offset;
      sF32 flt_res;
      sF32 jumptoloop;
      sBool b_jumptoloop_valid;
      sF32 sampleshift_endofloop;
      sBool b_sampleshift_endofloop_valid;
   } mmdst;

   mmdst.freq        = 0.0f;
   mmdst.freq_oct    = 0.0f;
   mmdst.freq_fifth  = 0.0f;
   mmdst.freq_fourth = 0.0f;
   mmdst.freq_semi   = 0.0f;
   mmdst.freq_fine   = 0.0f;
   mmdst.freq_beat   = sample->beat_frequency;
   mmdst.flt_cutoff  = sample->filter_cutoff + mod_filter_cutoff;
   mmdst.flt_pan     = (sample->filter_pan + mod_filter_pan);
   mmdst.flt_offset  = (sample->filter_offset + mod_filter_offset);
   mmdst.flt_res     = sample->filter_resonance + mod_filter_resonance;
   mmdst_sampleoff   = 0.0f;
   mmdst_sampleshift = 0.0f;
   mmdst.sampleshift_endofloop = 0.0f;
   mmdst.b_sampleshift_endofloop_valid = YAC_FALSE;
   mmdst_cyclelen    = 1.0f;
   mmdst.jumptoloop  = 0.0f;
   mmdst.b_jumptoloop_valid = YAC_FALSE;
   mmdst_glide_speed = 1.0f;
   mmdst_timestretch = 0.0f;
   mmdst_timestretch_bend = 0.0f;

   lfo_freq.mmdst_speed = 1.0f;
   lfo_freq.mmdst_level = 1.0f;
   lfo_vol.mmdst_speed = 1.0f;
   lfo_vol.mmdst_level = 1.0f;
   lfo_pan.mmdst_speed = 1.0f;
   lfo_pan.mmdst_level = 1.0f;
   lfo_aux.mmdst_speed = 1.0f;
   lfo_aux.mmdst_level = 1.0f;

   adsr_freq.mmdst_speed     = 1.0f;
   adsr_freq.mmdst_level     = 1.0f;
   adsr_freq.mmdst_intensity = 1.0f;
   adsr_freq.mmdst_aspeed    = 1.0f;
   adsr_freq.mmdst_sspeed    = 1.0f;
   adsr_freq.mmdst_rspeed    = 1.0f;

   adsr_vol.mmdst_speed     = 1.0f;
   adsr_vol.mmdst_level     = 1.0f;
   adsr_vol.mmdst_intensity = 1.0f;
   adsr_vol.mmdst_aspeed    = 1.0f;
   adsr_vol.mmdst_sspeed    = 1.0f;
   adsr_vol.mmdst_rspeed    = 1.0f;

   adsr_pan.mmdst_speed     = 1.0f;
   adsr_pan.mmdst_level     = 1.0f;
   adsr_pan.mmdst_intensity = 1.0f;
   adsr_pan.mmdst_aspeed    = 1.0f;
   adsr_pan.mmdst_sspeed    = 1.0f;
   adsr_pan.mmdst_rspeed    = 1.0f;

   adsr_aux.mmdst_speed     = 1.0f;
   adsr_aux.mmdst_level     = 1.0f;
   adsr_aux.mmdst_intensity = 1.0f;
   adsr_aux.mmdst_aspeed    = 1.0f;
   adsr_aux.mmdst_sspeed    = 1.0f;
   adsr_aux.mmdst_rspeed    = 1.0f;

   sBool mmWriteMask[STSAMPLE_NUM_MODMATRIX_ENTRIES] = { 0,0,0,0, 0,0,0,0, 0,0,0,0 };

   for(sUI mmIdx = 0u; mmIdx < STSAMPLE_NUM_MODMATRIX_ENTRIES; mmIdx++)
   {
      const StSampleMMEntry *mm = &sample->modmatrix[mmIdx];
      if(mm->b_enable)
      {
         sF32 srcVal = 0.0f;
         switch(mm->src)
         {
            default:
            case STSAMPLE_MM_SRC_NONE:
               srcVal = 0.0f;
               break;

            case STSAMPLE_MM_SRC_CONST_1:
               srcVal = 1.0f;
               break;

            case STSAMPLE_MM_SRC_CONST_M1:
               srcVal = -1.0f;
               break;

            case STSAMPLE_MM_SRC_CONST_0:
               srcVal = 0.0f;
               break;

            case STSAMPLE_MM_SRC_CONST_255:
               // e.g. for 'timestretch' dest
               srcVal = 255.0f;
               break;

            case STSAMPLE_MM_SRC_RAND_1_PM:
               srcVal = noteon_rand_pm[0];
               break;

            case STSAMPLE_MM_SRC_RAND_1_P:
               srcVal = noteon_rand_p[0];
               break;

            case STSAMPLE_MM_SRC_RAND_2_PM:
               srcVal = noteon_rand_pm[1];
               break;

            case STSAMPLE_MM_SRC_RAND_2_P:
               srcVal = noteon_rand_p[1];
               break;

            case STSAMPLE_MM_SRC_RAND_3_PM:
               srcVal = noteon_rand_pm[2];
               break;

            case STSAMPLE_MM_SRC_RAND_3_P:
               srcVal = noteon_rand_p[2];
               break;

            case STSAMPLE_MM_SRC_RAND_4_PM:
               srcVal = noteon_rand_pm[3];
               break;

            case STSAMPLE_MM_SRC_RAND_4_P:
               srcVal = noteon_rand_p[3];
               break;

            case STSAMPLE_MM_SRC_KEYBOARD_PM:
               srcVal = (note - 60.0f);
               srcVal = (srcVal >= 0.0) ? (srcVal * (1.0f / 67.0f)) : (srcVal * (1.0f / 60.0f));
               break;

            case STSAMPLE_MM_SRC_KEYBOARD_P:
               srcVal = (note - 60) * (1.0f / 67.0f);
               if(srcVal < 0.0f)
                  srcVal = 0.0f;
               break;

            case STSAMPLE_MM_SRC_KEYBOARD_M:
               srcVal = (note - 60) * (-1.0f / 60.0f);
               if(srcVal < 0.0f)
                  srcVal = 0.0f;
               break;

            case STSAMPLE_MM_SRC_KEYBOARD_ABS:
               srcVal = note * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_VOICE_NR:
               srcVal = sF32(voice_key % sample_player->num_voices);
               break;

            case STSAMPLE_MM_SRC_VOICE_NR_EVEN:
               srcVal = (voice_key & 1u) ? 0.0f : 1.0f;
               break;

            case STSAMPLE_MM_SRC_VOICE_NR_ODD:
               srcVal = (voice_key & 1u) ? 1.0f : 0.0f;
               break;

            case STSAMPLE_MM_SRC_VOICE_NR_EVENODD:
               srcVal = (voice_key & 1u) ? -1.0f : 1.0f;
               break;

            case STSAMPLE_MM_SRC_VOICE_NR_ODDEVEN:
               srcVal = (voice_key & 1u) ? 1.0f : -1.0f;
               break;

            case STSAMPLE_MM_SRC_ACTIVE_NOTES:
               srcVal = sF32(active_note_idx + 1u) / sF32(sample->max_voices);
               if(srcVal > 1.0f)
                  srcVal = 1.0f;
               break;

            case STSAMPLE_MM_SRC_ACTIVE_NOTE_EVEN:
               srcVal = (0u != (active_note_idx & 1u)) ? 0.0f : 1.0f;
               break;

            case STSAMPLE_MM_SRC_ACTIVE_NOTE_ODD:
               srcVal = (0u != (active_note_idx & 1u)) ? 1.0f : 0.0f;
               break;

            case STSAMPLE_MM_SRC_ACTIVE_NOTE_EVENODD:
               srcVal = (0u != (active_note_idx & 1u)) ? -1.0f : 1.0f;
               break;

            case STSAMPLE_MM_SRC_ACTIVE_NOTE_ODDEVEN:
               srcVal = (0u != (active_note_idx & 1u)) ? 1.0f : -1.0f;
               break;

            case STSAMPLE_MM_SRC_VELOCITY:
               srcVal = queued_noteon._vel;
               break;

            case STSAMPLE_MM_SRC_VELOCITY_HI:
               srcVal = queued_noteon._vel;
               if(srcVal >= (96.0f / 127.0f))
               {
                  srcVal -= (96.0f / 127.0f);
                  srcVal *= 1.0f / ((127.0f - 96.0f)/127.0f);  // => 0..1
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_RELEASE_VELOCITY:
               srcVal = release_velocity;
               break;

            case STSAMPLE_MM_SRC_RELEASE_VELOCITY_HI:
               srcVal = release_velocity;
               if(srcVal >= (96.0f / 127.0f))
               {
                  srcVal -= (96.0f / 127.0f);
                  srcVal *= 1.0f / ((127.0f - 96.0f)/127.0f);  // => 0..1
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PITCHBEND_PM:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PITCHBEND];
               if(srcVal >= 8192.0f)
               {
                  srcVal = (srcVal - 8192.0f) * (1.0f / 8191.0f);
               }
               else
               {
                  srcVal = -1.0f + (srcVal * (1.0f / 8192.0f));
               }
               break;

            case STSAMPLE_MM_SRC_PITCHBEND_PM_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_PITCHBEND];
               if(srcVal >= 8192.0f)
               {
                  srcVal = (srcVal - 8192.0f) * (1.0f / 8191.0f);
               }
               else
               {
                  srcVal = -1.0f + (srcVal * (1.0f / 8192.0f));
               }
               break;

            case STSAMPLE_MM_SRC_PITCHBEND_P:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PITCHBEND];
               if(srcVal >= 8192.0f)
               {
                  srcVal = (srcVal - 8192.0f) * (1.0f / 8191.0f);
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PITCHBEND_P_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_PITCHBEND];
               if(srcVal >= 8192.0f)
               {
                  srcVal = (srcVal - 8192.0f) * (1.0f / 8191.0f);
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PITCHBEND_M:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PITCHBEND];
               if(srcVal <= 8192.0f)
               {
                  srcVal = 1.0f - (srcVal * (1.0f / 8192.0f));
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PITCHBEND_M_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_PITCHBEND];
               if(srcVal <= 8192.0f)
               {
                  srcVal = 1.0f - (srcVal * (1.0f / 8192.0f));
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PRESSURE:
               if(perfctl_poly_pressure >= 0.0f)
               {
                  // Use per-note poly pressure
                  srcVal = perfctl_poly_pressure * (1.0f / 127.0f);
               }
               else
               {
                  // Use channel pressure
                  srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PRESSURE] * (1.0f / 127.0f);
               }
               break;

            case STSAMPLE_MM_SRC_PRESSURE_ON:
               if(perfctl_poly_pressure >= 0.0f)
               {
                  // Use per-note poly pressure
                  srcVal = perfctl_poly_pressure * (1.0f / 127.0f);
               }
               else
               {
                  // Use channel pressure
                  srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_PRESSURE] * (1.0f / 127.0f);
               }
               break;

            case STSAMPLE_MM_SRC_PRESSURE_HI:
               if(perfctl_poly_pressure >= 0.0f)
               {
                  // Use per-note poly pressure
                  srcVal = perfctl_poly_pressure * (1.0f / 127.0f);
               }
               else
               {
                  // Use channel pressure
                  srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_PRESSURE] * (1.0f / 127.0f);
               }
               if(srcVal >= (96.0f / 127.0f))
               {
                  srcVal -= (96.0f / 127.0f);
                  srcVal *= 1.0f / ((127.0f - 96.0f)/127.0f);  // => 0..1
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PRESSURE_HI_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_PRESSURE] * (1.0f / 127.0f);
               if(srcVal >= (96.0f / 127.0f))
               {
                  srcVal -= (96.0f / 127.0f);
                  srcVal *= 1.0f / ((127.0f - 96.0f)/127.0f);  // => 0..1
               }
               else
               {
                  srcVal = 0.0f;
               }
               break;

            case STSAMPLE_MM_SRC_PRESSURE_MAX:
               srcVal = perfctl_pressure_max * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC1_MODWHEEL:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC1_MODWHEEL] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC1_MODWHEEL_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC1_MODWHEEL] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC2_BREATHCTL:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC2_BREATHCTL] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC2_BREATHCTL_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC2_BREATHCTL] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC4_FOOTCTL:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC4_FOOTCTL] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC4_FOOTCTL_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC4_FOOTCTL] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC7_VOLUME:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC7_VOLUME] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC7_VOLUME_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC7_VOLUME] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC8_BALANCE:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC8_BALANCE] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC8_BALANCE_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC8_BALANCE] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC10_PAN:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC10_PAN] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC10_PAN_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC10_PAN] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC11_EXPRESSION:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC11_EXPRESSION] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC11_EXPRESSION_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC11_EXPRESSION] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC16_GENERAL_1:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC16_GENERAL_1_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC17_GENERAL_2:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC17_GENERAL_2_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC18_GENERAL_3:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC18_GENERAL_3_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC19_GENERAL_4:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC19_GENERAL_4_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC16_GENERAL_1] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC74_BRIGHT:
               srcVal = sample_player->perf_ctl[STSAMPLEPLAYER_PERFCTL_CC74_BRIGHTNESS] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_CC74_BRIGHT_ON:
               srcVal = perf_ctl_on[STSAMPLEPLAYER_PERFCTL_CC74_BRIGHTNESS] * (1.0f / 127.0f);
               break;

            case STSAMPLE_MM_SRC_LFO_FREQ:
               srcVal = mmsrc.lfo.freq;
               break;

            case STSAMPLE_MM_SRC_LFO_VOL:
               srcVal = mmsrc.lfo.vol;
               break;

            case STSAMPLE_MM_SRC_LFO_PAN:
               srcVal = mmsrc.lfo.pan;
               break;

            case STSAMPLE_MM_SRC_LFO_AUX:
               srcVal = mmsrc.lfo.aux;
               break;

            case STSAMPLE_MM_SRC_ENV_FREQ:
               srcVal = mmsrc.env.freq;
               break;

            case STSAMPLE_MM_SRC_ENV_VOL:
               srcVal = mmsrc.env.vol;
               break;

            case STSAMPLE_MM_SRC_ENV_PAN:
               srcVal = mmsrc.env.pan;
               break;

            case STSAMPLE_MM_SRC_ENV_AUX:
               srcVal = mmsrc.env.aux;
               break;
         }

         sF32 mmAmt = mm->amt + modmatrix_amt[mmIdx];
         sF32 srcValDef = srcVal * mmAmt;

         sBool bAutoAdd = ((STSAMPLE_MM_OP_AUTO == mm->op) || (STSAMPLE_MM_OP_ADD == mm->op));
         sBool bAutoMul = ((STSAMPLE_MM_OP_AUTO == mm->op) || (STSAMPLE_MM_OP_MUL == mm->op));

#define Delse_mm_lerp(d)                                                \
         else if(STSAMPLE_MM_OP_BLEND_SRC == mm->op)                    \
            d = (d) + (srcVal - (d)) * mmAmt;                           \
         else if(STSAMPLE_MM_OP_BLEND_AMT == mm->op)                    \
            d = (d) + (mmAmt - (d)) * srcVal

#define Delse_mm_lerp_bipolar8(d)                                       \
         else if(STSAMPLE_MM_OP_BLEND_SRC == mm->op)                    \
            d = (d) + (loc_bipolar_to_scale(srcVal, 8.0f/*div*/, 8.0f/*mul*/) - (d)) * mmAmt; \
         else if(STSAMPLE_MM_OP_BLEND_AMT == mm->op)                    \
            d = (d) + (loc_bipolar_to_scale(mmAmt, 8.0f/*div*/, 8.0f/*mul*/) - (d)) * srcVal

#define Delse_mm_lerp_scl(d, s)                                         \
         else if(STSAMPLE_MM_OP_BLEND_SRC == mm->op)                    \
            d = (d) + (srcVal*(s) - (d)) * mmAmt;                       \
         else if(STSAMPLE_MM_OP_BLEND_AMT == mm->op)                    \
            d = (d) + (mmAmt*(s) - (d)) * srcVal

         switch(mm->dst)
         {
            default:
            case STSAMPLE_MM_DST_NONE:
               // (todo) mm dst
               break;

            case STSAMPLE_MM_DST_FREQ:
               if(bAutoAdd)
                  mmdst.freq += srcValDef;
               else if(bAutoMul)
                  mmdst.freq *= srcValDef;
               Delse_mm_lerp(mmdst.freq);
               break;

            case STSAMPLE_MM_DST_FREQ_OCT:
               if(bAutoAdd)
                  mmdst.freq_oct += srcValDef;
               else if(bAutoMul)
                  mmdst.freq_oct *= srcValDef;
               Delse_mm_lerp(mmdst.freq_oct);
               break;

            case STSAMPLE_MM_DST_FREQ_FIFTH:
               if(bAutoAdd)
                  mmdst.freq_fifth += srcValDef;
               else if(bAutoMul)
                  mmdst.freq_fifth *= srcValDef;
               Delse_mm_lerp(mmdst.freq_fifth);
               break;

            case STSAMPLE_MM_DST_FREQ_FOURTH:
               if(bAutoAdd)
                  mmdst.freq_fourth += srcValDef;
               else if(bAutoMul)
                  mmdst.freq_fourth *= srcValDef;
               Delse_mm_lerp(mmdst.freq_fourth);
               break;

            case STSAMPLE_MM_DST_FREQ_SEMI:
               if(bAutoAdd)
                  mmdst.freq_semi += srcValDef;
               else if(bAutoMul)
                  mmdst.freq_semi *= srcValDef;
               Delse_mm_lerp(mmdst.freq_semi);
               break;

            case STSAMPLE_MM_DST_FREQ_FINE:
               if(bAutoAdd)
                  mmdst.freq_fine += srcValDef;
               else if(bAutoMul)
                  mmdst.freq_fine *= srcValDef;
               Delse_mm_lerp(mmdst.freq_fine);
               break;

            case STSAMPLE_MM_DST_FREQ_BEAT:
               if(bAutoAdd)
                  mmdst.freq_beat += srcValDef;
               else if(bAutoMul)
                  mmdst.freq_beat *= srcValDef;
               Delse_mm_lerp(mmdst.freq_beat);
               break;

            case STSAMPLE_MM_DST_VOLUME:
               if(bAutoMul)
                  next_vol *= srcValDef;
               else if(bAutoAdd)
                  next_vol += srcValDef;
               Delse_mm_lerp(next_vol);
               break;

            case STSAMPLE_MM_DST_PAN:
               if(bAutoAdd)
                  next_pan += srcValDef;
               else if(bAutoMul)
                  next_pan *= srcValDef;
               Delse_mm_lerp(next_pan);
               break;

            case STSAMPLE_MM_DST_FILTER_CUTOFF:
               if(bAutoAdd)
                  mmdst.flt_cutoff += srcValDef;
               else if(bAutoMul)
                  mmdst.flt_cutoff *= srcValDef;
               Delse_mm_lerp(mmdst.flt_cutoff);
               break;

            case STSAMPLE_MM_DST_FILTER_PAN:
               if(bAutoAdd)
                  mmdst.flt_pan += srcValDef;
               else if(bAutoMul)
                  mmdst.flt_pan *= srcValDef;
               Delse_mm_lerp(mmdst.flt_pan);
               break;

            case STSAMPLE_MM_DST_FILTER_OFFSET:
               if(bAutoAdd)
                  mmdst.flt_offset += srcValDef;
               else if(bAutoMul)
                  mmdst.flt_offset *= srcValDef;
               Delse_mm_lerp(mmdst.flt_offset);
               break;

            case STSAMPLE_MM_DST_FILTER_RESONANCE:
               if(bAutoAdd)
                  mmdst.flt_res += srcValDef;
               else if(bAutoMul)
                  mmdst.flt_res *= srcValDef;
               Delse_mm_lerp(mmdst.flt_res);
               break;

            case STSAMPLE_MM_DST_SAMPLE_OFFSET:
               if(bAutoAdd)
                  mmdst_sampleoff += srcValDef;
               else if(bAutoMul)
                  mmdst_sampleoff *= srcValDef;
               Delse_mm_lerp(mmdst_sampleoff);
               break;

            case STSAMPLE_MM_DST_SAMPLE_SHIFT:
               if(bAutoAdd)
                  mmdst_sampleshift += srcValDef;
               else if(bAutoMul)
                  mmdst_sampleshift *= srcValDef;
               Delse_mm_lerp(mmdst_sampleshift);
               break;

            case STSAMPLE_MM_DST_SAMPLE_SHIFT_EOL:
               if(bAutoAdd)
                  mmdst.sampleshift_endofloop += srcValDef;
               else if(bAutoMul)
                  mmdst.sampleshift_endofloop *= srcValDef;
               Delse_mm_lerp(mmdst.sampleshift_endofloop);
               mmdst.b_sampleshift_endofloop_valid = YAC_TRUE;
               break;

            case STSAMPLE_MM_DST_CYCLE_LEN:
               if(bAutoMul)
                  mmdst_cyclelen *= srcValDef;
               else if(bAutoAdd)
                  mmdst_cyclelen += srcValDef;
               Delse_mm_lerp(mmdst_cyclelen);
               break;

            case STSAMPLE_MM_DST_JUMPTOLOOP:
               if(bAutoAdd)
                  mmdst.jumptoloop += srcValDef;
               else if(bAutoMul)
                  mmdst.jumptoloop *= srcValDef;
               Delse_mm_lerp(mmdst.jumptoloop);
               mmdst.b_jumptoloop_valid = YAC_TRUE;
               break;

            case STSAMPLE_MM_DST_FREQ_LFO_SPEED:
               if(bFreqLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_freq_global.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     sample->lfo_freq_global.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(sample->lfo_freq_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_freq.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     lfo_freq.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(lfo_freq.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_FREQ_LFO_SPEED_ABS:
               if(bFreqLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_freq_global.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     sample->lfo_freq_global.mmdst_speed += srcValDef;
                  Delse_mm_lerp(sample->lfo_freq_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_freq.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     lfo_freq.mmdst_speed += srcValDef;
                  Delse_mm_lerp(lfo_freq.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_FREQ_LFO_LEVEL:
               if(bFreqLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_freq_global.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     sample->lfo_freq_global.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(sample->lfo_freq_global.mmdst_level, 8.0f);
               }
               else
               {
                  if(bAutoMul)
                     lfo_freq.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     lfo_freq.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(lfo_freq.mmdst_level, 8.0f);
               }
               break;

            case STSAMPLE_MM_DST_VOL_LFO_SPEED:
               if(bVolLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_vol_global.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     sample->lfo_vol_global.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(sample->lfo_vol_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_vol.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     lfo_vol.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(lfo_vol.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_VOL_LFO_SPEED_ABS:
               if(bVolLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_vol_global.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     sample->lfo_vol_global.mmdst_speed += srcValDef;
                  Delse_mm_lerp(sample->lfo_vol_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_vol.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     lfo_vol.mmdst_speed += srcValDef;
                  Delse_mm_lerp(lfo_vol.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_VOL_LFO_LEVEL:
               if(bVolLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_vol_global.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     sample->lfo_vol_global.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(sample->lfo_vol_global.mmdst_level, 8.0f);
               }
               else
               {
                  if(bAutoMul)
                     lfo_vol.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     lfo_vol.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(lfo_vol.mmdst_level, 8.0f);
               }
               break;

            case STSAMPLE_MM_DST_PAN_LFO_SPEED:
               if(bPanLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_pan_global.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     sample->lfo_pan_global.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(sample->lfo_pan_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_pan.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     lfo_pan.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(lfo_pan.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_PAN_LFO_SPEED_ABS:
               if(bPanLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_pan_global.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     sample->lfo_pan_global.mmdst_speed += srcValDef;
                  Delse_mm_lerp(sample->lfo_pan_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_pan.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     lfo_pan.mmdst_speed += srcValDef;
                  Delse_mm_lerp(lfo_pan.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_PAN_LFO_LEVEL:
               if(bPanLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_pan_global.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     sample->lfo_pan_global.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(sample->lfo_pan_global.mmdst_level, 8.0f);
               }
               else
               {
                  if(bAutoMul)
                     lfo_pan.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     lfo_pan.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(lfo_pan.mmdst_level, 8.0f);
               }
               break;

            case STSAMPLE_MM_DST_AUX_LFO_SPEED:
               if(bAuxLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_aux_global.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     sample->lfo_aux_global.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(sample->lfo_aux_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_aux.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  else if(bAutoAdd)
                     lfo_aux.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
                  Delse_mm_lerp_bipolar8(lfo_aux.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_AUX_LFO_SPEED_ABS:
               if(bAuxLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_aux_global.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     sample->lfo_aux_global.mmdst_speed += srcValDef;
                  Delse_mm_lerp(sample->lfo_aux_global.mmdst_speed);
               }
               else
               {
                  if(bAutoMul)
                     lfo_aux.mmdst_speed *= srcValDef;
                  else if(bAutoAdd)
                     lfo_aux.mmdst_speed += srcValDef;
                  Delse_mm_lerp(lfo_aux.mmdst_speed);
               }
               break;

            case STSAMPLE_MM_DST_AUX_LFO_LEVEL:
               if(bAuxLFOGlobal)
               {
                  if(bAutoMul)
                     sample->lfo_aux_global.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     sample->lfo_aux_global.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(sample->lfo_aux_global.mmdst_level, 8.0f);
               }
               else
               {
                  if(bAutoMul)
                     lfo_aux.mmdst_level *= srcValDef * 8.0f;
                  else if(bAutoAdd)
                     lfo_aux.mmdst_level += srcValDef * 8.0f;
                  Delse_mm_lerp_scl(lfo_aux.mmdst_level, 8.0f);
               }
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_SPEED:
               if(bAutoMul)
                  adsr_freq.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_freq.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_freq.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_SPEED_ABS:
               if(bAutoMul)
                  adsr_freq.mmdst_speed *= srcValDef;
               else if(bAutoAdd)
                  adsr_freq.mmdst_speed += srcValDef;
               Delse_mm_lerp_bipolar8(adsr_freq.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_LEVEL:
               if(bAutoMul)
                  adsr_freq.mmdst_level *= srcValDef * 8.0f;
               else if(bAutoAdd)
                  adsr_freq.mmdst_level += srcValDef * 8.0f;
               Delse_mm_lerp_scl(adsr_freq.mmdst_level, 8.0f);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_INTENSITY:
               if(bAutoMul)
                  adsr_freq.mmdst_intensity *= srcValDef;
               else if(bAutoAdd)
                  adsr_freq.mmdst_intensity += srcValDef;
               Delse_mm_lerp(adsr_freq.mmdst_intensity);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_ATTACK_SPEED:
               if(bAutoMul)
                  adsr_freq.mmdst_aspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_freq.mmdst_aspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_freq.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_ATTACK_SPEED_ABS:
               if(bAutoMul)
                  adsr_freq.mmdst_aspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_freq.mmdst_aspeed += srcValDef;
               Delse_mm_lerp(adsr_freq.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_SUSTAIN_SPEED:
               if(bAutoMul)
                  adsr_freq.mmdst_sspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_freq.mmdst_sspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_freq.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_SUSTAIN_SPEED_ABS:
               if(bAutoMul)
                  adsr_freq.mmdst_sspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_freq.mmdst_sspeed += srcValDef;
               Delse_mm_lerp(adsr_freq.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_RELEASE_SPEED:
               if(bAutoMul)
                  adsr_freq.mmdst_rspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_freq.mmdst_rspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_freq.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_FREQ_ENV_RELEASE_SPEED_ABS:
               if(bAutoMul)
                  adsr_freq.mmdst_rspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_freq.mmdst_rspeed += srcValDef;
               Delse_mm_lerp(adsr_freq.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_SPEED:
               if(bAutoMul)
                  adsr_vol.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_vol.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_vol.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_SPEED_ABS:
               if(bAutoMul)
                  adsr_vol.mmdst_speed *= srcValDef;
               else if(bAutoAdd)
                  adsr_vol.mmdst_speed += srcValDef;
               Delse_mm_lerp(adsr_vol.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_LEVEL:
               if(bAutoMul)
                  adsr_vol.mmdst_level *= srcValDef * 8.0f;
               else if(bAutoAdd)
                  adsr_vol.mmdst_level += srcValDef * 8.0f;
               Delse_mm_lerp_scl(adsr_vol.mmdst_level, 8.0f);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_INTENSITY:
               if(bAutoMul)
                  adsr_vol.mmdst_intensity *= srcValDef;
               else if(bAutoAdd)
                  adsr_vol.mmdst_intensity += srcValDef;
               Delse_mm_lerp(adsr_vol.mmdst_intensity);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_ATTACK_SPEED:
               if(bAutoMul)
                  adsr_vol.mmdst_aspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_vol.mmdst_aspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_vol.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_ATTACK_SPEED_ABS:
               if(bAutoMul)
                  adsr_vol.mmdst_aspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_vol.mmdst_aspeed += srcValDef;
               Delse_mm_lerp(adsr_vol.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_SUSTAIN_SPEED:
               if(bAutoMul)
                  adsr_vol.mmdst_sspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_vol.mmdst_sspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_vol.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_SUSTAIN_SPEED_ABS:
               if(bAutoMul)
                  adsr_vol.mmdst_sspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_vol.mmdst_sspeed += srcValDef;
               Delse_mm_lerp(adsr_vol.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_RELEASE_SPEED:
               if(bAutoMul)
                  adsr_vol.mmdst_rspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_vol.mmdst_rspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_vol.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_VOL_ENV_RELEASE_SPEED_ABS:
               if(bAutoMul)
                  adsr_vol.mmdst_rspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_vol.mmdst_rspeed += srcValDef;
               Delse_mm_lerp(adsr_vol.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_SPEED:
               if(bAutoMul)
                  adsr_pan.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_pan.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_pan.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_SPEED_ABS:
               if(bAutoMul)
                  adsr_pan.mmdst_speed *= srcValDef;
               else if(bAutoAdd)
                  adsr_pan.mmdst_speed += srcValDef;
               Delse_mm_lerp(adsr_pan.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_LEVEL:
               if(bAutoMul)
                  adsr_pan.mmdst_level *= srcValDef * 8.0f;
               else if(bAutoAdd)
                  adsr_pan.mmdst_level += srcValDef * 8.0f;
               Delse_mm_lerp_scl(adsr_pan.mmdst_level, 8.0f);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_INTENSITY:
               if(bAutoMul)
                  adsr_pan.mmdst_intensity *= srcValDef;
               else if(bAutoAdd)
                  adsr_pan.mmdst_intensity += srcValDef;
               Delse_mm_lerp(adsr_pan.mmdst_intensity);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_ATTACK_SPEED:
               if(bAutoMul)
                  adsr_pan.mmdst_aspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_pan.mmdst_aspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_pan.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_ATTACK_SPEED_ABS:
               if(bAutoMul)
                  adsr_pan.mmdst_aspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_pan.mmdst_aspeed += srcValDef;
               Delse_mm_lerp(adsr_pan.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_SUSTAIN_SPEED:
               if(bAutoMul)
                  adsr_pan.mmdst_sspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_pan.mmdst_sspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_pan.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_SUSTAIN_SPEED_ABS:
               if(bAutoMul)
                  adsr_pan.mmdst_sspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_pan.mmdst_sspeed += srcValDef;
               Delse_mm_lerp(adsr_pan.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_RELEASE_SPEED:
               if(bAutoMul)
                  adsr_pan.mmdst_rspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_pan.mmdst_rspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_pan.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_PAN_ENV_RELEASE_SPEED_ABS:
               if(bAutoMul)
                  adsr_pan.mmdst_rspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_pan.mmdst_rspeed += srcValDef;
               Delse_mm_lerp(adsr_pan.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_SPEED:
               if(bAutoMul)
                  adsr_aux.mmdst_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_aux.mmdst_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_aux.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_SPEED_ABS:
               if(bAutoMul)
                  adsr_aux.mmdst_speed *= srcValDef;
               else if(bAutoAdd)
                  adsr_aux.mmdst_speed += srcValDef;
               Delse_mm_lerp(adsr_aux.mmdst_speed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_LEVEL:
               if(bAutoMul)
                  adsr_aux.mmdst_level *= srcValDef * 8.0f;
               else if(bAutoAdd)
                  adsr_aux.mmdst_level += srcValDef * 8.0f;
               Delse_mm_lerp_scl(adsr_aux.mmdst_level, 8.0f);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_INTENSITY:
               if(bAutoMul)
                  adsr_aux.mmdst_intensity *= srcValDef;
               else if(bAutoAdd)
                  adsr_aux.mmdst_intensity += srcValDef;
               Delse_mm_lerp(adsr_aux.mmdst_intensity);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_ATTACK_SPEED:
               if(bAutoMul)
                  adsr_aux.mmdst_aspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_aux.mmdst_aspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_aux.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_ATTACK_SPEED_ABS:
               if(bAutoMul)
                  adsr_aux.mmdst_aspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_aux.mmdst_aspeed += srcValDef;
               Delse_mm_lerp(adsr_aux.mmdst_aspeed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_SUSTAIN_SPEED:
               if(bAutoMul)
                  adsr_aux.mmdst_sspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_aux.mmdst_sspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_aux.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_SUSTAIN_SPEED_ABS:
               if(bAutoMul)
                  adsr_aux.mmdst_sspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_aux.mmdst_sspeed += srcValDef;
               Delse_mm_lerp(adsr_aux.mmdst_sspeed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_RELEASE_SPEED:
               if(bAutoMul)
                  adsr_aux.mmdst_rspeed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  adsr_aux.mmdst_rspeed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(adsr_aux.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_AUX_ENV_RELEASE_SPEED_ABS:
               if(bAutoMul)
                  adsr_aux.mmdst_rspeed *= srcValDef;
               else if(bAutoAdd)
                  adsr_aux.mmdst_rspeed += srcValDef;
               Delse_mm_lerp(adsr_aux.mmdst_rspeed);
               break;

            case STSAMPLE_MM_DST_MOD_1_AMOUNT:
            case STSAMPLE_MM_DST_MOD_2_AMOUNT:
            case STSAMPLE_MM_DST_MOD_3_AMOUNT:
            case STSAMPLE_MM_DST_MOD_4_AMOUNT:
            case STSAMPLE_MM_DST_MOD_5_AMOUNT:
            case STSAMPLE_MM_DST_MOD_6_AMOUNT:
            case STSAMPLE_MM_DST_MOD_7_AMOUNT:
            case STSAMPLE_MM_DST_MOD_8_AMOUNT:
            case STSAMPLE_MM_DST_MOD_9_AMOUNT:
            case STSAMPLE_MM_DST_MOD_10_AMOUNT:
            case STSAMPLE_MM_DST_MOD_11_AMOUNT:
            case STSAMPLE_MM_DST_MOD_12_AMOUNT:
               {
                  sUI mmDestIdx = (mm->dst - STSAMPLE_MM_DST_MOD_1_AMOUNT);
                  if(mmIdx != mmDestIdx)
                  {
                     if(mmWriteMask[mmDestIdx])
                     {
                        // Mod Matrix amount has already been written by another mod matrix entry
                        if(bAutoAdd)
                           modmatrix_amt[mmDestIdx] += srcValDef;
                        else if(bAutoMul)
                           modmatrix_amt[mmDestIdx] *= srcValDef;
                        Delse_mm_lerp(modmatrix_amt[mmDestIdx]);
                     }
                     else
                     {
                        // Mod Matrix amount has not been written, yet
                        if(bAutoAdd)
                           modmatrix_amt[mmDestIdx] = srcValDef;
                        else if(bAutoMul)
                           modmatrix_amt[mmDestIdx] = 0.0f;
                        Delse_mm_lerp(modmatrix_amt[mmDestIdx]);

                        mmWriteMask[mmDestIdx] = YAC_TRUE;
                     }
                  }
               }
               break;

            case STSAMPLE_MM_DST_GLIDE_SPEED:
               if(bAutoMul)
                  mmdst_glide_speed *= loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               else if(bAutoAdd)
                  mmdst_glide_speed += loc_bipolar_to_scale(srcValDef, 8.0f/*div*/, 8.0f/*mul*/);
               Delse_mm_lerp_bipolar8(mmdst_glide_speed);
               break;

            case STSAMPLE_MM_DST_TIMESTRETCH:
               if(bAutoAdd)
                  mmdst_timestretch += srcValDef;
               else if(bAutoMul)
                  mmdst_timestretch *= srcValDef;
               Delse_mm_lerp(mmdst_timestretch);
               break;

            case STSAMPLE_MM_DST_TIMESTRETCH_BEND:
               if(bAutoAdd)
                  mmdst_timestretch_bend += srcValDef;
               else if(bAutoMul)
                  mmdst_timestretch_bend *= srcValDef;
               Delse_mm_lerp(mmdst_timestretch_bend);
               break;
         }
      }
   }

   for(sUI mmIdx = 0u; mmIdx < STSAMPLE_NUM_MODMATRIX_ENTRIES; mmIdx++)
   {
      if(!mmWriteMask[mmIdx])
         modmatrix_amt[mmIdx] = 0.0f;  // e.g. after modmatrix was edited
      // else: keep for next tick()
   }

   if(mmdst.b_jumptoloop_valid)
   {
      if(num_sample_loops > 1)
      {
         sSI mmJumpToLoopIdx = sSI((num_sample_loops -1) * mmdst.jumptoloop + 0.5f);
         if(mmJumpToLoopIdx != current_loop_idx)
         {
            mod_jumptoloop = mmJumpToLoopIdx;  // Queue (overwrites param modulation, if any)
            if(!b_playing)
            {
               // Set initial loop (during reallyStartVoice())
               sF32 *dummy;
               (void)handleEndOfLoop(&current_play_offset, &dummy);
            }
            // else: update when current loop ends
         }
      }
   }

   if(mmdst.b_sampleshift_endofloop_valid)
   {
      // Queue
      if(mmdst.sampleshift_endofloop != mmdst_sampleshift_endofloop_last)
      {
         mod_sampleshift_endofloop = mmdst.sampleshift_endofloop;
         mmdst_sampleshift_endofloop_last = mmdst.sampleshift_endofloop;
      }
   }

   next_freq += mmdst.freq * 48.0f;
   next_freq += sF32(12.0f * ((mmdst.freq_oct >= 0) ? sSI(mmdst.freq_oct * 4.0f + 0.5f) : sSI(mmdst.freq_oct * 4.0f - 0.5f)));
   next_freq += sF32(7.0f * ((mmdst.freq_fifth >= 0) ? sSI(mmdst.freq_fifth * 4.0f + 0.5f) : sSI(mmdst.freq_fifth * 4.0f - 0.5f)));
   next_freq += sF32(5.0f * ((mmdst.freq_fourth >= 0) ? sSI(mmdst.freq_fourth * 4.0f + 0.5f) : sSI(mmdst.freq_fourth * 4.0f - 0.5f)));
   next_freq += sF32( ((mmdst.freq_semi >= 0) ? sSI(mmdst.freq_semi * 12.0f + 0.5f) : sSI(mmdst.freq_semi * 12.0f - 0.5f)) );
   next_freq += mmdst.freq_fine;

   
   // Freq ADSR
   if(adsr_freq.adsr)
   {
      sF32 a = adsr_freq.adsr->intensity;
      next_freq += a * mmsrc.env.freq * sample->freq_env_amt;
   }

   // Volume ADSR
   if(adsr_vol.adsr)
   {
      sF32 a = adsr_vol.adsr->intensity;
      sF32 av = mmsrc.env.vol;
      // yac_host->printf("xxx nextblockstate: 1a startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

      if(av < 0.0f)
         av = 0.0f;
      else if(av > 1.0f)
         av = 1.0f;

      ////printf("xxx next_vol=%f cached_vel_vol=%f adsrintensity=%f\n", next_vol, cached_vel_vol, a);
      next_vol = ((1.0f - a)*next_vol) + (next_vol * a * av); // blend
      if(adsr_vol.b_finished)
      {
         if(!queued_noteon.b_valid)
         {
            // yac_host->printf("xxx stopVoice 3 queued_noteon.b_valid=%d\n", queued_noteon.b_valid);
            softStopVoice();
         }
      }
   }

   // yac_host->printf("xxx nextblockstate: 2 startadsr adsr_vol.current_env=%p elements=%p\n", adsr_vol.current_env, adsr_vol.current_env->elements);

   // Pan ADSR
   if(adsr_pan.adsr)
   {
      sF32 a = adsr_pan.adsr->intensity;
      sF32 av = mmsrc.env.pan;
      ////printf("xxx next_pan=%f a=%f av=%f\n", next_pan, a, av);
      next_pan += a * av * sample->pan_env_amt;
   }

   // Aux (Filter) ADSR
   if(adsr_aux.adsr)
   {
      sF32 a = adsr_aux.adsr->intensity;
      next_aux_adsr += a * mmsrc.env.aux;

#if 0
      static int xxxAux = 0;
      if(0 == (xxxAux++&127))
      {
         yac_host->printf("xxx next_aux_adsr=%f\n", next_aux_adsr);
      }
#endif

   }

   // Freq LFO
   next_freq += mmsrc.lfo.freq * sample->freq_lfo_amt;

   // Volume LFO
   next_vol += next_vol * mmsrc.lfo.vol * sample->vol_lfo_amt;

   // Pan LFO
   next_pan += mmsrc.lfo.pan * sample->pan_lfo_amt;

   // Aux LFO
   next_aux_lfo += mmsrc.lfo.aux;


   // Volume block ramping
   next_vol *= volramp_fade_vol;

   // Clip volume
   if(!sample->b_enabled)
   {
      next_vol = 0.0f; // sample is muted
   }
   else if(next_vol < 0.0f)
   {
      next_vol = 0.0f;
   }
   else if(next_vol > 1.0f)
   {
      next_vol = 1.0f;
   }

   if(Dfltnonzero(mmdst.freq_beat))
   {
      sF32 beatDetune = mmdst.freq_beat / powf(2.0f, (next_freq - 60.0f) / 12.0f);
      next_freq += (beatDetune * 0.01f);
   }

   // Clip frequency
   if(next_freq < 0.00001f)
   {
      next_freq = 0.00001f;
   }

   // // if(current_freq != next_freq)
   // // {
       // // yac_host->printf("xxx next_freq=%f\n", next_freq);
   // // }

   // Clip panning
   if(next_pan > 1.0f)
   {
      next_pan = 1.0f;
   }
   else if(next_pan < -1.0f)
   {
      next_pan = -1.0f;
   }

   // Calc filter params
   sF32 fltCutOff = mmdst.flt_cutoff;
   fltCutOff += next_aux_adsr * (sample->filter_aux_env_amount + (sample->filter_aux_env_velocity_amount * queued_noteon._vel));
   fltCutOff += next_aux_lfo * sample->filter_aux_lfo_amount;

   fltCutOff += ((next_freq - 64.0f) / 127.0f) * sample->filter_keyboard_amount;

   sF32 fltCutOffL = fltCutOff - mmdst.flt_pan;
   sF32 fltCutOffR = fltCutOff + mmdst.flt_pan;
   sF32 fltRes = mmdst.flt_res;
   fltRes = sMIN(1.0f, sMAX(fltRes, 0.0f));
   sF32 fltGainDb = sample->filter_eq_gain;
#define Dcutoff(f) f = normToCutOffFreq(f)

   sF32 fltOffset = mmdst.flt_offset;  // for dual filter LPF/HPF/BPF/BRF

   switch(filter_type)
   {
      default:
      case STSAMPLE_FLT_TYPE_NONE:
         break;

         // Single Filter Configurations:
      case STSAMPLE_FLT_TYPE_LP:
      case STSAMPLE_FLT_TYPE_HP:
      case STSAMPLE_FLT_TYPE_BP:
      case STSAMPLE_FLT_TYPE_BR:
      case STSAMPLE_FLT_TYPE_PEQ:
      case STSAMPLE_FLT_TYPE_LSH:
      case STSAMPLE_FLT_TYPE_HSH:
      {
         sSI type;

         switch(filter_type)
         {
            default:
            case STSAMPLE_FLT_TYPE_LP:
               type = StSampleVoiceBiquad2::LPF;
               break;

            case STSAMPLE_FLT_TYPE_HP:
               type = StSampleVoiceBiquad2::HPF;
               break;

            case STSAMPLE_FLT_TYPE_BP:
               type = StSampleVoiceBiquad2::BPF;
               break;

            case STSAMPLE_FLT_TYPE_BR:
               type = StSampleVoiceBiquad2::BRF;
               break;

            case STSAMPLE_FLT_TYPE_PEQ:
               type = StSampleVoiceBiquad2::PEQ;
               break;

            case STSAMPLE_FLT_TYPE_LSH:
               type = StSampleVoiceBiquad2::LSH;
               break;

            case STSAMPLE_FLT_TYPE_HSH:
               type = StSampleVoiceBiquad2::HSH;
               break;
         }               

         Dcutoff(fltCutOffL);
         Dcutoff(fltCutOffR);

         biquad_b_1[0].calcParams(float_block_size,
                                  type,
                                  fltGainDb,
                                  fltCutOffL,
                                  fltRes
                                  );
         biquad_b_1[1].calcParams(float_block_size,
                                  type,
                                  fltGainDb,
                                  fltCutOffR,
                                  fltRes
                                  );
      }
      break;

         // Dual Filter Configurations:
      case STSAMPLE_FLT_TYPE_LPLP:
      case STSAMPLE_FLT_TYPE_LPHP:
      case STSAMPLE_FLT_TYPE_LPBP:
      case STSAMPLE_FLT_TYPE_LPBR:
      case STSAMPLE_FLT_TYPE_HPLP:
      case STSAMPLE_FLT_TYPE_HPHP:
      case STSAMPLE_FLT_TYPE_HPBP:
      case STSAMPLE_FLT_TYPE_HPBR:
      case STSAMPLE_FLT_TYPE_BPLP:
      case STSAMPLE_FLT_TYPE_BPHP:
      case STSAMPLE_FLT_TYPE_BPBP:
      case STSAMPLE_FLT_TYPE_BPBR:
      case STSAMPLE_FLT_TYPE_BRLP:
      case STSAMPLE_FLT_TYPE_BRHP:
      case STSAMPLE_FLT_TYPE_BRBP:
      case STSAMPLE_FLT_TYPE_BRBR:
      case STSAMPLE_FLT_TYPE_PEQ2:
      case STSAMPLE_FLT_TYPE_LSH2:
      case STSAMPLE_FLT_TYPE_HSH2:
      {
         sSI type1;
         sSI type2;
         switch(filter_type)
         {
            default:
            case STSAMPLE_FLT_TYPE_LPLP:
               type1 = StSampleVoiceBiquad2::LPF;
               type2 = StSampleVoiceBiquad2::LPF;
               break;
            case STSAMPLE_FLT_TYPE_LPHP:
               type1 = StSampleVoiceBiquad2::LPF;
               type2 = StSampleVoiceBiquad2::HPF;
               break;
            case STSAMPLE_FLT_TYPE_LPBP:
               type1 = StSampleVoiceBiquad2::LPF;
               type2 = StSampleVoiceBiquad2::BPF;
               break;
            case STSAMPLE_FLT_TYPE_LPBR:
               type1 = StSampleVoiceBiquad2::LPF;
               type2 = StSampleVoiceBiquad2::BRF;
               break;

            case STSAMPLE_FLT_TYPE_HPLP:
               type1 = StSampleVoiceBiquad2::HPF;
               type2 = StSampleVoiceBiquad2::LPF;
               break;
            case STSAMPLE_FLT_TYPE_HPHP:
               type1 = StSampleVoiceBiquad2::HPF;
               type2 = StSampleVoiceBiquad2::HPF;
               break;
            case STSAMPLE_FLT_TYPE_HPBP:
               type1 = StSampleVoiceBiquad2::HPF;
               type2 = StSampleVoiceBiquad2::BPF;
               break;
            case STSAMPLE_FLT_TYPE_HPBR:
               type1 = StSampleVoiceBiquad2::HPF;
               type2 = StSampleVoiceBiquad2::BRF;
               break;

            case STSAMPLE_FLT_TYPE_BPLP:
               type1 = StSampleVoiceBiquad2::BPF;
               type2 = StSampleVoiceBiquad2::LPF;
               break;
            case STSAMPLE_FLT_TYPE_BPHP:
               type1 = StSampleVoiceBiquad2::BPF;
               type2 = StSampleVoiceBiquad2::HPF;
               break;
            case STSAMPLE_FLT_TYPE_BPBP:
               type1 = StSampleVoiceBiquad2::BPF;
               type2 = StSampleVoiceBiquad2::BPF;
               break;
            case STSAMPLE_FLT_TYPE_BPBR:
               type1 = StSampleVoiceBiquad2::BPF;
               type2 = StSampleVoiceBiquad2::BRF;
               break;

            case STSAMPLE_FLT_TYPE_BRLP:
               type1 = StSampleVoiceBiquad2::BRF;
               type2 = StSampleVoiceBiquad2::LPF;
               break;
            case STSAMPLE_FLT_TYPE_BRHP:
               type1 = StSampleVoiceBiquad2::BRF;
               type2 = StSampleVoiceBiquad2::HPF;
               break;
            case STSAMPLE_FLT_TYPE_BRBP:
               type1 = StSampleVoiceBiquad2::BRF;
               type2 = StSampleVoiceBiquad2::BPF;
               break;
            case STSAMPLE_FLT_TYPE_BRBR:
               type1 = StSampleVoiceBiquad2::BRF;
               type2 = StSampleVoiceBiquad2::BRF;
               break;

            case STSAMPLE_FLT_TYPE_PEQ2:
               type1 = StSampleVoiceBiquad2::PEQ;
               type2 = StSampleVoiceBiquad2::PEQ;
               fltOffset = 0.0f;
               break;
            case STSAMPLE_FLT_TYPE_LSH2:
               type1 = StSampleVoiceBiquad2::LSH;
               type2 = StSampleVoiceBiquad2::LSH;
               fltOffset = 0.0f;
               break;
            case STSAMPLE_FLT_TYPE_HSH2:
               type1 = StSampleVoiceBiquad2::HSH;
               type2 = StSampleVoiceBiquad2::HSH;
               fltOffset = 0.0f;
               break;
         }
         sF32 fltCutOffL_1 = fltCutOffL + fltOffset;
         sF32 fltCutOffL_2 = fltCutOffL - fltOffset;

         sF32 fltCutOffR_1 = fltCutOffR + fltOffset;
         sF32 fltCutOffR_2 = fltCutOffR - fltOffset;

         Dcutoff(fltCutOffL_1);
         Dcutoff(fltCutOffL_2);

         Dcutoff(fltCutOffR_1);
         Dcutoff(fltCutOffR_2);

         // yac_host->printf("xxx cutoff l=(%f; %f) r=(%f; %f)  bw=%f\n",
         //                  fltCutOffL_hp,
         //                  fltCutOffL_lp,
         //                  fltCutOffR_hp,
         //                  fltCutOffR_lp,
         //                  fltBandwidth
         //                  );

         biquad_b_1[0].calcParams(float_block_size,
                                  type1,
                                  fltGainDb,
                                  fltCutOffL_1,
                                  fltRes
                                  );

         biquad_b_2[0].calcParams(float_block_size,
                                  type2,
                                  fltGainDb,
                                  fltCutOffL_2,
                                  fltRes
                                  );

         biquad_b_1[1].calcParams(float_block_size,
                                  type1,
                                  fltGainDb,
                                  fltCutOffR_1,
                                  fltRes
                                  );

         biquad_b_2[1].calcParams(float_block_size,
                                  type2,
                                  fltGainDb,
                                  fltCutOffR_2,
                                  fltRes
                                  );
      }
      break;
   } // switch filter_type

   ////yac_host->printf("xxx calcNextBlockState: (final) next_vol=%f\n", next_vol);

   ////yac_host->printf("xxx calcNextBlockState this=0x%p freq=%f vol=%f pan=%f\n", this, next_freq, next_vol, next_pan);
}


sF32 StSampleVoice::FreqToNote(sF32 _freq, YAC_Object *_freqTable) {
   if(YAC_BCHK(_freqTable, YAC_CLID_FLOATARRAY))
   {
      YAC_CAST_ARG(YAC_FloatArray, ft, _freqTable);

      if(ft->num_elements >= 128)
      {
         sUI i;
         for(i=0; i<127; i++)
         {
            sF32 fa = ft->elements[i];
            sF32 fb = ft->elements[i+1];
            if((_freq >= fa)&&(_freq <fb))
            {
               return i + ((_freq-fa) / (fb-fa));
            }
         }
         return 126.99f;
      }
   }
   return 0.0f;
}


sF32 StSampleVoice::noteToFreq(sF32 _note) {
   if(NULL != freq_table)
   {
      if(_note >= 0)
      {
         if(_note >= 127)
         {
            _note = 126.9999f;
         }
         sUI intNote = (sUI) _note;
         sF32 frac = _note - (sF32)intNote;
         return freq_table->elements[intNote] * (1.0f - frac) + freq_table->elements[intNote + 1] * frac;
      }
      else
      {
         _note = - _note;
         if(_note >= 127)
         {
            _note = 126.9999f;
         }
         sUI intNote = (sUI) _note;
         sF32 frac = _note - (sF32)intNote;
         return - ( freq_table->elements[intNote] * (1.0f - frac) + freq_table->elements[intNote + 1] * frac );
      }
   }
   return 440.0f;
}

sF32 StSampleVoice::noteToSampleRate(sF32 _note) {
   sF32 hz = noteToFreq(_note);
   
   return (hz / sample->waveform->base_frequency) * (sample->waveform->sample_rate / mix_rate);
}

sF32 StSampleVoice::normToCutOffFreq(sF32 _norm) {
   if(NULL != freq_table)
   {
      _norm *= 127.0f;
      _norm = sMIN(127.0f, sMAX(_norm, 0.0f));

      sUI intNote = (sUI) _norm;
      sF32 frac = _norm - (sF32)intNote;
      sF32 freq = freq_table->elements[intNote] * (1.0f - frac) + freq_table->elements[intNote + 1] * frac;
      freq /= mix_rate;
      return freq;
   }
   return 0.0f;
}

void StSampleVoice::_setVol(sF32 _vol) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._vol = _vol;
   }
   else
   {
      // Modify voice
      mod_vol = _vol;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getVol(void) {
   return mod_vol;
}

void StSampleVoice::_setVol2(sF32 _vol) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._vol2 = _vol;
   }
   else
   {
      // Modify voice
      mod_vol2 = _vol;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getVol2(void) {
   return mod_vol2;
}

void StSampleVoice::_setPan(sF32 _pan) {
   LOCK_MTX_MOD;
   ////printf("xxx StSampleVoice::setPan: pan=%f\n", _pan);

   if(_pan > 1.0f)
      _pan = 1.0f;
   else if(_pan < -1.0f)
      _pan = -1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._pan = _pan;
   }
   else
   {
      // Modify voice
      mod_pan = _pan;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getPan(void) {
   return mod_pan;
}

void StSampleVoice::_setPan2(sF32 _pan) {
   LOCK_MTX_MOD;

   if(_pan > 1.0f)
      _pan = 1.0f;
   else if(_pan < -1.0f)
      _pan = -1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._pan2 = _pan;
   }
   else
   {
      // Modify voice
      mod_pan2 = _pan;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getPan2(void) {
   return mod_pan2;
}

void StSampleVoice::_setFreq(sF32 _freq) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._freq = _freq;
   }
   else
   {
      // Modify voice
      mod_freq = _freq;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getFreq(void) {
   return mod_freq;
}

void StSampleVoice::_setFreq2(sF32 _freq) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._freq2 = _freq;
   }
   else
   {
      // Modify voice
      mod_freq2 = _freq;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getFreq2(void) {
   return mod_freq2;
}

void StSampleVoice::_setMod(sF32 _mod) {
   LOCK_MTX_MOD;

   if(_mod < 0.0f)
      _mod = 0.0f;
   else if(_mod > 1.0f)
      _mod = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._mod = _mod;
   }
   else
   {
      // Modify voice
      mod_mod = _mod;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getMod(void) {
   return mod_mod;
}

void StSampleVoice::_setTimestretch(sF32 _ts) {
   LOCK_MTX_MOD;

   ////printf("xxx setTimestretch: ts=%f\n", _ts);

   // // if(_ts < 0.0f)
   // //    _ts = 0.0f;
   // // else if(_ts > 1.0f)
   // //    _ts = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._timestretch = _ts;
   }
   else
   {
      // Modify voice
      mod_timestretch = _ts;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getTimestretch(void) {
   return mod_timestretch;
}

void StSampleVoice::_setTimestretchBend(sF32 _bend) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._timestretch_bend = _bend;
   }
   else
   {
      // Modify voice
      mod_timestretch_bend = _bend;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getTimestretchBend(void) {
   return mod_timestretch_bend;
}

void StSampleVoice::_setSampleOff(sF32 _so) {
   LOCK_MTX_MOD;

   // if(_so < 0.0f)
   //    _so = 0.0f;
   // else if(_so > 1.0f)
   //    _so = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._sampleoff = _so;
   }
   else
   {
      // Modify voice
      if(!sample->b_sampleoffnoteon)
      {
         mod_sampleoff = _so;
      }
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getSampleOff(void) {
   return mod_sampleoff;
}

void StSampleVoice::_setSampleOff2(sF32 _so) {
   LOCK_MTX_MOD;

   // if(_so < 0.0f)
   //    _so = 0.0f;
   // else if(_so > 1.0f)
   //    _so = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._sampleoff2 = _so;
   }
   else
   {
      // Modify voice
      mod_sampleoff2 = _so;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getSampleOff2(void) {
   return mod_sampleoff2;
}

void StSampleVoice::_setCycleLen(sF32 _cl) {
   LOCK_MTX_MOD;

   if(_cl < 0.0f)
      _cl = 0.0f;
   else if(_cl > 1.0f)
      _cl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._cyclelen = _cl;
   }
   else
   {
      // Modify voice
      mod_cyclelen = _cl;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getCycleLen(void) {
   return mod_cyclelen;
}

void StSampleVoice::_setSampleShift(sF32 _sh) {
   LOCK_MTX_MOD;

   if(_sh < 0.0f)
      _sh = 0.0f;
   else if(_sh > 1.0f)
      _sh = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._sampleshift = _sh;
   }
   else
   {
      // Modify voice
      mod_sampleshift = _sh;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getSampleShift(void) {
   return mod_sampleshift;
}

void StSampleVoice::_setSampleShiftEndOfLoop(sF32 _sh) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._sampleshift_endofloop = _sh;
   }
   else
   {
      // Modify voice
      mod_sampleshift_endofloop = _sh;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getSampleShiftEndOfLoop(void) {
   return mod_sampleshift_endofloop;
}

void StSampleVoice::_setAltVolume(sF32 _av) {
   LOCK_MTX_MOD;

   if(_av < 0.0f)
      _av = 0.0f;
   else if(_av > 1.0f)
      _av = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._altvolume = _av;
   }
   else
   {
      // Modify voice
      mod_altvolume = _av;
   }
   UNLOCK_MTX_MOD;
}

sF32 StSampleVoice::_getAltVolume(void) {
   return mod_altvolume;
}

void StSampleVoice::_setRetrigMask(sUI _mask) {
   // (Re-)trigger envelopes and LFOs:
   //   - 0x01: reset freq envelope
   //   - 0x02: reset volume envelope
   //   - 0x04: reset pan envelope
   //   - 0x08: reset aux envelope
   //   - 0x10: reset freq LFO
   //   - 0x20: reset volume LFO
   //   - 0x40: reset pan LFO
   //   - 0x80: reset aux LFO
   
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      ////printf("xxx b_valid: setRetrigMask=%02x\n", _mask);
      queued_noteon._retrigmask = _mask;
   }
   else
   {
      // yac_host->printf("xxx voice running setRetrigMask=%02x\n", _mask);
      startADSRAndLFO(_mask, false);
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOFreqSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfofreqspd = _spd;
   }
   else
   {
      // Modify voice
      lfo_freq.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOFreqLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfofreqlvl = _lvl;
   }
   else
   {
      // Modify voice
      lfo_freq.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOVolSpd(sF32 _spd) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfovolspd = _spd;
   }
   else
   {
      // Modify voice
      lfo_vol.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOVolLvl(sF32 _lvl) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfovollvl = _lvl;
   }
   else
   {
      // Modify voice
      lfo_vol.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOPanSpd(sF32 _spd) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfopanspd = _spd;
   }
   else
   {
      // Modify voice
      lfo_pan.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOPanLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfopanlvl = _lvl;
   }
   else
   {
      // Modify voice
      lfo_pan.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOAuxSpd(sF32 _spd) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfoauxspd = _spd;
   }
   else
   {
      // Modify voice
      lfo_aux.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setLFOAuxLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._lfoauxlvl = _lvl;
   }
   else
   {
      // Modify voice
      lfo_aux.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRFreqSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrfreqspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_freq.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRFreqLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrfreqlvl = _lvl;
   }
   else
   {
      // Modify voice
      adsr_freq.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRFreqInt(sF32 _int) {
   LOCK_MTX_MOD;

   if(_int < 0.0f)
      _int = 0.0f;
   else if(_int > 1.0f)
      _int = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrfreqint = _int;
   }
   else
   {
      // Modify voice
      adsr_freq.mod_intensity = _int;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRVolSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrvolspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_vol.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRVolLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrvollvl = _lvl;
   }
   else
   {
      // Modify voice
      adsr_vol.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRVolInt(sF32 _int) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrvolint = _int;
   }
   else
   {
      // Modify voice
      adsr_vol.mod_intensity = _int;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRPanSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrpanspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_pan.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRPanLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrpanlvl = _lvl;
   }
   else
   {
      // Modify voice
      adsr_pan.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRPanInt(sF32 _int) {
   LOCK_MTX_MOD;

   if(_int < 0.0f)
      _int = 0.0f;
   else if(_int > 1.0f)
      _int = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrpanint = _int;
   }
   else
   {
      // Modify voice
      adsr_pan.mod_intensity = _int;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRAuxSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrauxspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_aux.mod_speed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRAuxLvl(sF32 _lvl) {
   LOCK_MTX_MOD;

   if(_lvl < 0.0f)
      _lvl = 0.0f;
   else if(_lvl > 1.0f)
      _lvl = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrauxlvl = _lvl;
   }
   else
   {
      // Modify voice
      adsr_aux.mod_level = _lvl;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setADSRAuxInt(sF32 _int) {
   LOCK_MTX_MOD;

   if(_int < 0.0f)
      _int = 0.0f;
   else if(_int > 1.0f)
      _int = 1.0f;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._adsrauxint = _int;
   }
   else
   {
      // Modify voice
      adsr_aux.mod_intensity = _int;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setAFreqSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._afreqspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_freq.mod_aspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setSFreqSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._sfreqspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_freq.mod_sspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setRFreqSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._rfreqspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_freq.mod_rspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setAVolSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._avolspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_vol.mod_aspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setSVolSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._svolspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_vol.mod_sspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setRVolSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._rvolspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_vol.mod_rspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setAPanSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._apanspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_pan.mod_aspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setSPanSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._spanspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_pan.mod_sspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setRPanSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._rpanspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_pan.mod_rspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setAAuxSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._aauxspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_aux.mod_aspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setSAuxSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._sauxspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_aux.mod_sspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setRAuxSpd(sF32 _spd) {
   LOCK_MTX_MOD;

   if(_spd < 0.00001f)
   {
      _spd  = 0.00001f;
   }

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._rauxspd = _spd;
   }
   else
   {
      // Modify voice
      adsr_aux.mod_rspeed = _spd;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::startADSRAndLFO(sUI _mask, sBool _bNoteOn) {

   // yac_host->printf("xxx startADSRAndLFO: mask=0x%08x bNoteOn=%d b_release=%d\n", _mask, _bNoteOn, b_release);

   // // if(!b_release)
   // // {
      if(_mask & 0x01u)
      {
         adsr_freq.startADSR(sample->adsr_freq, 0.0f, _bNoteOn/*bResetMod*/, b_release);
      }
      else if(_bNoteOn)
      {
         adsr_freq .stopADSR();
      }

      if(_mask & 0x02u)
      {
         adsr_vol .startADSR(sample->adsr_vol, 1.0f, _bNoteOn/*bResetMod*/, b_release);
      }
      else if(_bNoteOn)
      {
         adsr_vol .stopADSR();
      }

      if(_mask & 0x04u)
      {
         adsr_pan .startADSR(sample->adsr_pan, 0.0f, _bNoteOn/*bResetMod*/, b_release);
      }
      else if(_bNoteOn)
      {
         adsr_pan .stopADSR();
      }

      if(_mask & 0x08u)
      {
         adsr_aux .startADSR(sample->adsr_aux, 0.0f, _bNoteOn/*bResetMod*/, b_release);
      }
      else if(_bNoteOn)
      {
         adsr_aux .stopADSR();
      }

   
      if(_mask & 0x10)
      {
         lfo_freq.startLFO(sample->lfo_freq, _bNoteOn/*bResetMod*/);
         sample->lfo_freq_global.startLFO(sample->lfo_freq, _bNoteOn/*bResetMod*/);
      }
      else if(_bNoteOn)
      {
         lfo_freq .stopLFO();
         sample->lfo_freq_global.stopLFO();
      }

      if(_mask & 0x20)
      {
         lfo_vol.startLFO(sample->lfo_vol, _bNoteOn/*bResetMod*/);
         sample->lfo_vol_global.startLFO(sample->lfo_vol, _bNoteOn/*bResetMod*/);
      }
      else if(_bNoteOn)
      {
         lfo_vol.stopLFO();
         sample->lfo_vol_global.stopLFO();
      }

      if(_mask & 0x40)
      {
         lfo_pan.startLFO(sample->lfo_pan, _bNoteOn/*bResetMod*/);
         sample->lfo_pan_global.startLFO(sample->lfo_pan, _bNoteOn/*bResetMod*/);
      }
      else if(_bNoteOn)
      {
         lfo_pan.stopLFO();
         sample->lfo_pan_global.stopLFO();
      }

      if(_mask & 0x80)
      {
         lfo_aux.startLFO(sample->lfo_aux, _bNoteOn/*bResetMod*/);
         sample->lfo_aux_global.startLFO(sample->lfo_aux, _bNoteOn/*bResetMod*/);
      }
      else if(_bNoteOn)
      {
         lfo_aux.stopLFO();
         sample->lfo_aux_global.stopLFO();
      }

   // // }

}

void StSampleVoice::_setJumpToLoop(sSI _idx) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._jumptoloop = _idx;
   }
   else
   {
      // Modify voice
      mod_jumptoloop = _idx;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setJumpToLoopRel(sF32 _relIdx) {
   LOCK_MTX_MOD;
   if(num_sample_loops > 0u)
   {
      sSI idx = sSI(_relIdx * (num_sample_loops-1) + 0.5f);

      if(queued_noteon.b_valid)
      {
         // Voice has not been started, yet
         queued_noteon._jumptoloop = idx;
      }
      else
      {
         // Modify voice
         mod_jumptoloop = idx;
      }
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setWaveguideIndex(sSI _idx) {
   LOCK_MTX_MOD;
   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._waveguideidx = _idx;
   }
   else
   {
      // Modify voice
      queued_waveguide_idx = _idx;
   }
   UNLOCK_MTX_MOD;
}

sBool StSampleVoice::handleEndOfLoop(sF64 *cOff, sF32**smpDat) {
   //
   // end of sample, begin next loop
   //

   ////yac_host->printf("xxx 1 handleEndOfLoop: cOff=%f current_sample_len=%u mod_jumptoloop=%d\n", *cOff, current_sample_len, mod_jumptoloop);
   // printf("xxx smpDat=0x%p\n", smpDat);
   // printf("xxx sample=0x%p\n", sample);

   sBool bNextStep = false;

#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF
   sampleshift_eol_xfade_loopstart_frameidx = 0u;
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF

   if(mod_sampleshift_endofloop >= 0.0f)
   {
      last_sample_sampleshift_eol_l = last_sample_l;
      last_sample_sampleshift_eol_r = last_sample_r;
      sampleshift_eol_xfade_frames_left = VOLRAMP_NUMSTEPS;

      mod_sampleshift = mod_sampleshift_endofloop;
      mod_sampleshift_endofloop = -1.0f;
   }

   if(1 == b_timedloop)
   {
      // Timeout loop mode
      //printf("xxx handleEndOfLoop: timedloop, samples_until_end_of_loopstep=%d mod_jumptoloop=%d\n", samples_until_end_of_loopstep, mod_jumptoloop);
      if((samples_until_end_of_loopstep > 0) && (-1 == mod_jumptoloop))
      {
         // Repeat same loop again
         calcSmpDat(*smpDat, current_sample_offset, current_orig_sample_len, current_sample_len);
         *cOff = 0;
         return 1;
      }
      else
      {
         bNextStep = 1;
      }
   }
   else
   {
      // Normal loop mode (loop cycle boundary)

      if(current_sample_len)
      {
         while( (*cOff) >=  ((sF64)current_sample_len) )
            (*cOff) -= (sF64) current_sample_len;
      }

      if(current_loop_countdown)
      {
         bNextStep = (0 == --current_loop_countdown);
      }
      else if(0 == num_sample_loops)
      {
         // End of sample
         if(!queued_noteon.b_valid)
         {
            ////printf("xxx stopVoice 4\n");
            stopVoice();
         }
         return 0;
      }
      else
      {
         // printf("xxx 2c1 this=0x%p\n", this);
         // printf("xxx 2c1 sample=0x%p num_sample_loops=%d\n", sample, num_sample_loops);
         // printf("xxx 2c1 sample->sample_loops=0x%p\n", sample->sample_loops);
         //
         // Loop last loop forever
         //
         sUI sloff = 3 * current_loop_idx;
         YAC_IntArray *sampleLoops = getCurrentSampleLoops();
         current_sample_offset   = *(sUI*)&sampleLoops->elements[sloff + 0];
         current_orig_sample_len = *(sUI*)&sampleLoops->elements[sloff + 1];
         current_loop_countdown  =         sampleLoops->elements[sloff + 2];
         if(sample->ui_sync_first_loop_index)
         {
            current_loop_countdown = 0; // forever
         }
         calcCurrentSampleLen();

        
         // yac_host->printf("xxx restart inf. loop idx=%i off=%u len=%u count=%u\n", 
         //                  current_loop_idx,
         //                  current_sample_offset,
         //                  current_sample_len,
         //                  current_loop_countdown
         //                  );
         
         // keep phase, e.g. for chiptune-like osc waveform sequences
         while( ((sUI)*cOff) >= current_sample_len )
         {
            *cOff -= (sF64)current_sample_len;
         }
         ////yac_host->printf("xxx loop last loop cOff=%u\n", (sUI)*cOff);
         
         ////////*smpDat = sample->waveform->sample_data->elements + current_sample_offset * sample->waveform->num_channels;
         calcSmpDat(*smpDat, current_sample_offset, current_orig_sample_len, current_sample_len);
         ////printf("xxx loop smpDat=%p off=%d len=%d\n", *smpDat, current_sample_offset, current_sample_len);

         bNextStep = 0;
         b_zero = 0;
      }

     
   } // else if b_timedloop
   

   // yac_host->printf("xxx 2 handleEndOfLoop: bNextStep=%d cOff=%f current_sample_len=%u\n", bNextStep, *cOff, current_sample_len);


   if(-1 != mod_jumptoloop)
   {
      ////printf("xxx handleEndOfLoop: mod_jumptoloop=%d\n", mod_jumptoloop);
      
      if( ((sUI)mod_jumptoloop) < (sUI)num_sample_loops )
      {
         current_loop_idx = (sUI) (mod_jumptoloop -1); // will be inc'd in if(bNextStep) block
         bNextStep = 1;
      }
      mod_jumptoloop = -1;
   }
   
   
   if(bNextStep)
   {
      // Start next loop
      if(num_sample_loops > 0)
      {
         ////yac_host->printf("xxx start next loop current_idx=%d\n", current_loop_idx);
         if(-1 == current_loop_idx)
         {
            // First loop
            current_loop_idx = sample->first_loop_index;
         }
         else
         {
            // Next loop
            current_loop_idx++;
         }
         ////printf("xxx handleEndOfLoop b_timedloop=%d loopIdx=%d/%d\n", b_timedloop, current_loop_idx, num_sample_loops);

         if(current_loop_idx >= num_sample_loops)
         {
            // End of sample
            ////printf("xxx stopVoice 5\n");
            stopVoice();
            return 0;
         }
         else
         {
            // Play next loop
            sUI sloff = 3 * current_loop_idx;
            sUI times = *(sUI*)&getCurrentSampleLoops()->elements[sloff + 2];
            sUI target;
            
            ////printf("xxx playnextloop idx=%d\n", current_loop_idx);
            
            while(times & StSample::LOOP_OP_MASK)
            {
               if(StSample::LOOP_OP_SET == (times & StSample::LOOP_OP_MASK))
               {
                  // Copy immediate value to loop register
                  sUI varIdx = (times & StSample::LOOP_VAR_MASK) >> StSample::LOOP_VAR_SHIFT;
                  loop_vars[varIdx] = times & StSample::LOOP_VAL_MASK;
                  // Continue with next loop/command
                  target = (current_loop_idx + 1); // new jmp target (next instruction)
                  ////yac_host->printf("xxx LOOP_OP_SET: %d = #%d\n", varIdx, loop_vars[varIdx]);
               }
               else if(StSample::LOOP_OP_JNZ == (times & StSample::LOOP_OP_MASK))
               {
                  // Decrement and branch if not zero
                  sUI varIdx = (times & StSample::LOOP_VAR_MASK) >> StSample::LOOP_VAR_SHIFT;
                  if(loop_vars[varIdx])
                  {
                     loop_vars[varIdx]--;
                  }
                  // set new jmp target
                  if(loop_vars[varIdx])
                  {
                     target = (times & StSample::LOOP_VAL_MASK);
                  }
                  else
                  {
                     // Continue with next loop/command
                     target = (current_loop_idx + 1);
                  }
                  ////yac_host->printf("xxx LOOP_OP_JNZ: varIdx=%d val=%d target=%u\n", varIdx, loop_vars[varIdx], target);
               }
               else if(StSample::LOOP_OP_MOV == (times & StSample::LOOP_OP_MASK))
               {
                  // Move register values
                  sUI varIdxSrc = (times & StSample::LOOP_VAL_MASK) & 15;
                  sUI varIdxDst = (times & StSample::LOOP_VAR_MASK) >> StSample::LOOP_VAR_SHIFT;
                  loop_vars[varIdxDst] = loop_vars[varIdxSrc];
                  // Continue with next loop/command
                  target = (current_loop_idx + 1); // new jmp target (next instruction)
                  ////yac_host->printf("xxx LOOP_OP_MOV: %d = %d\n", varIdxDst, varIdxSrc);
               }
               else
               {
                  // Unconditional jump
                  target = (times & StSample::LOOP_VAL_MASK);
                  ////yac_host->printf("xxx LOOP_OP_JMP: target=%d\n", target);
               }
               
               if(StSample::LOOP_OP_JMP == (target & StSample::LOOP_OP_MASK))
               {
                  // avoid infinite loops
                  ////printf("xxx stopVoice 6\n");
                  stopVoice();
                  return 0;
               }
               if( target < ((sUI)num_sample_loops) )
               {
                  current_loop_idx = target;
                  sloff = 3 * current_loop_idx;
                  times = *(sUI*)&getCurrentSampleLoops()->elements[sloff + 2];
               }
               else
               {
                  // illegal loop index
                  ////printf("xxx stopVoice 7\n");
                  stopVoice();
                  return 0;
               }
            } // while parse loop ops
            
            // Found a non-op loop entry
            if(sample->ui_sync_first_loop_index)
            {
               times = 0; // forever
            }
            current_loop_countdown  = (sSI) times;
            current_sample_offset   = *(sUI*)&getCurrentSampleLoops()->elements[sloff + 0];
            current_orig_sample_len = *(sUI*)&getCurrentSampleLoops()->elements[sloff + 1];
            calcCurrentSampleLen();
            
//                yac_host->printf("xxx start loop idx=%d off=%u len=%u count=%u\n",
//                                 current_loop_idx,
//                                 current_sample_offset,
//                                 current_sample_len,
//                                 current_loop_countdown
//                                 );
            
            if(0 == current_sample_len)
            {
               // End of sample
               ////printf("xxx stopVoice 8\n");
               stopVoice();
               return 0;
            }
            else
            {
               ////yac_host->printf("xxx *cOff=%f current_sample_len=%d\n", *cOff, current_sample_len);

              
               if(b_timedloop)
               {
                  if(0 == times)
                  {
                     // Disable timedloop if step is looped forever
                     //  (note) not, do not! otherwise the infinitely looped sample part is not correctly faded in, causing clicks.
                     b_timedloop = 0;

                     if(sample->b_timedloop_fade)
                     {
                        fadein_countdown = VOLRAMP_NUMSTEPS;
                     }
                     else
                     {
                        fadein_countdown = -1;
                     }
                     fadeout_countdown = -1;
                     b_fading_out = 0;

                     while( ((sUI)*cOff) >= current_sample_len )
                     {
                        *cOff -= (sF64)current_sample_len;
                     }
                  }
                  else
                  {
                     samples_until_end_of_loopstep = (sSI) (times * mix_rate / timedloop_base);
                     ////printf("xxx handleEndOfLoop: samples_until_end_of_loopstep=%d\n", samples_until_end_of_loopstep);
                     if(sample->b_timedloop_fade)
                     {
                        fadein_countdown = VOLRAMP_NUMSTEPS;
                     }
                     else
                     {
                        fadein_countdown = -1;
                     }
                     fadeout_countdown = -1;
                     b_fading_out = 0;
                     *cOff = 0.0f;
                  }
               }
               else
               {
                  // keep phase, e.g. for chiptune-like osc waveform sequences
                  while( ((sUI)*cOff) >= current_sample_len )
                  {
                     *cOff -= (sF64)current_sample_len;
                  }
               }

               ////yac_host->printf("xxx 2 *cOff=%f current_sample_len=%d\n", *cOff, current_sample_len);
               ////yac_host->printf("xxx StSampleVoice: start loop off=%d len=%d\n", current_sample_offset, current_sample_len);
               ////////*smpDat = sample->waveform->sample_data->elements + current_sample_offset * sample->waveform->num_channels;
               calcSmpDat(*smpDat, current_sample_offset, current_orig_sample_len, current_sample_len);
               b_zero = 0;
            }
         }
      }
   } // if bNextStep   

   return 1;
}

void StSampleVoice::readWindowedCycleSample(const sF32 *smpDat, sUI _off, sF32 _tsOff, sF32 *_l, sF32 *_r, const sUI _curSampleLen) {

   // sUI tsIntOff = (sUI) _tsOff;
   // if(tsIntOff == ( ((sUI)current_cyclelen) - 1))
   // {
   //    sF32 al,ar,bl,br;
   //    sF32 tsFrac = _tsOff - tsIntOff;

   //    // printf("xxx readcyclesmp: LAST off=%u tsOff=%f tsFrac=%f\n", _off, _tsOff, tsFrac);

   //    // read last cycle sample
   //    readWindowedSample(smpDat, _off + _tsOff, &al, &ar, false);

   //    // lerp to start of cycle
   //    readWindowedSample(smpDat, _off + tsFrac, &bl, &br, false);

   //    *_l = (1.0f - tsFrac) * al + tsFrac * bl;
   //    *_r = (1.0f - tsFrac) * ar + tsFrac * br;

   // //    // *_l = 0;
   // //    // *_r = 0;

   // //    // *_l = 1.0f;
   // //    // *_r = 1.0f;

   // }
   // else
   {
      // printf("xxx readcyclesmp: off=%u tsOff=%f\n", _off, _tsOff);
      // if(_tsOff >= current_cyclelen)
      // {
      //    // printf("xxx readcyclesmp: CLIP tsoff=%f cyclelen=%f\n", _tsOff, current_cyclelen);
      //    _tsOff = current_cyclelen - 1;
      // }
      readWindowedSample(smpDat, _off + _tsOff, _l, _r, true/*bAllowInterpol*/, _curSampleLen);

      // *_l = 0;
      // *_r = 0;
   }
}

void StSampleVoice::readWindowedSample(const sF32 *smpDat, sF32 _off, sF32 *_l, sF32 *_r, sBool _bAllowInterpol, const sUI _curSampleLen) {

   ////printf("readWindowedSample _off=%f\n", _off);

   sUI numCh = sample->waveform->num_channels;

   sUI intOff = (sUI) _off;
   sF32 smpFrac = _off - intOff;
   ////printf("readWIndowedSample smpFrac=%f\n", smpFrac);
   while(intOff < 0)
   {
      intOff += _curSampleLen/*current_sample_len*/;
   }
   if(intOff >= _curSampleLen/*current_sample_len*/)
   {
      intOff -= _curSampleLen/*current_sample_len*/;
   }
   sUI smpOff = intOff * numCh;
   if((STSAMPLE_INTERPOL_LINEAR == sample->interpol_type) && _bAllowInterpol)
   {
      // Linear interpolation
      sUI smpOffR;
      if((intOff + 1) >= _curSampleLen/*current_sample_len*/)
      {
         // interpolate to current loop start (ideally: start of next loop..)
         smpOffR = 0;
      }
      else
      {
         // interpolate to next neighbour sample
         smpOffR = smpOff + numCh;
      }
      
      ////printf("xxx _off=%f smpOff=%u len=%u\n", _off, smpOff, current_sample_len);
      
      // // *_l = smpDat[smpOff + 0] * (1-smpFrac) + smpDat[smpOffR + 0] * smpFrac;
      *_l = smpDat[smpOff + 0] + (smpDat[smpOffR + 0] - smpDat[smpOff + 0]) * smpFrac;
      if(numCh > 1)
      {
         // // *_r = smpDat[smpOff + 1] * (1-smpFrac) + smpDat[smpOffR + 1] * smpFrac;
         *_r = smpDat[smpOff + 1] + (smpDat[smpOffR + 1] - smpDat[smpOff + 1]) * smpFrac;
      }
      else
      {
         *_r = *_l; // mono to stereo
      }
   }
   else
   {
      // No interpolation
      *_l = smpDat[smpOff + 0];
      if(numCh > 1)
      {
         *_r = smpDat[smpOff + 1];
      }
      else
      {
         *_r = *_l; // mono to stereo
      }
   }
}

void StSampleVoice::calcCurrentSampleLen(void) {
   if(!b_zero)
   {
      if(sample->b_timestretch)
      {
         current_sample_len = current_orig_sample_len;
      }
      else
      {
         current_sample_len = (sUI) (current_orig_sample_len * mod_cyclelen * sample_player->mod_cyclelen * mmdst_cyclelen);
         if(current_sample_len < 2)
         {
            current_sample_len = 2;
         }
      }
   }
}

void StSampleVoice::alignSampleLenToCyclelen(sUI &_sampleLen) {
   sUI r = sUI(sUI(_sampleLen / current_cyclelen) * current_cyclelen + 0.5f);
   if(r < _sampleLen)
      _sampleLen = r;
}

void StSampleVoice::calcCurrentOffset(void) {
   // (note) offset calculation is forced in reallyStartVoice() by setting last_relsmpoff to -1
   //
   // (note) current_play_offset is relative to current sample/loop start
   //

   // // sF32 relSmpOff = (sample->sampleoffset_velocity_amount * queued_noteon._vel) + mod_sampleoff + mod_sampleoff2 + sample_player->mod_sampleoff;
   sF32 relSmpOff = mod_sampleoff + mod_sampleoff2 + mmdst_sampleoff + sample_player->mod_sampleoff + sample->sampleoffset_rel;

   if(relSmpOff > 0.999f)
   {
      relSmpOff = 0.999f;
   }
   else if(relSmpOff < 0.0f)
   {
      relSmpOff = 0.0f;
   }

   if(Dfltnotequal(relSmpOff, last_relsmpoff))
   {
      // yac_host->printf("xxx recalc off relSmpOff=%f last_relsmpoff=%f\n", relSmpOff, last_relsmpoff);
      if((last_relsmpoff >= 0) && sample->b_timestretch)
      {
         // voice already started, avoid clicks by interpolating with last played cycle
         anticlick_granular_smpoffinterpol_countdown = ANTICLICK_GRANULAR_SMPOFFINTERPOL_TIME;
         ////printf("xxx current_play_offset=%f cyclen=%f\n", current_play_offset, current_cyclelen);
         anticlick_granular_smpoffinterpol_winoff    = (sUI) ( ((sUI)(current_play_offset / current_cyclelen)) * current_cyclelen );
         if(sample->b_waveguide)
         {
            anticlick_granular_smpoffinterpol_nextwinoff = (sUI)waveguide_next_play_offset;
         }
         else
         {
            anticlick_granular_smpoffinterpol_nextwinoff = (sUI)(anticlick_granular_smpoffinterpol_winoff + current_cyclelen);
         }
         anticlick_granular_smpoffinterpol_tsoff     = current_ts_offset;
         anticlick_granular_smpoffinterpol_fragoff   = sF32((current_play_offset - anticlick_granular_smpoffinterpol_winoff) / current_cyclelen);
      }
      current_play_offset  = (sF64) (relSmpOff * current_sample_len);
      // printf("xxx recalc current_play_offset=%f relSmpOff=%f\n", current_play_offset, relSmpOff);

      if(sample->b_waveguide)
      {
         if(NULL != sample->waveguide_table)
         {
            // Calc current cycle offset
            if(sample->waveguide_table->num_elements > 0)
            {
               // Use relSmpOff to modulate waveguide_idx
               if(last_relsmpoff >= 0.0f)
               {
                  waveguide_idx = (sSI) (sample->waveguide_table->num_elements * last_relsmpoff);
                  if(waveguide_idx < 0)
                  {
                     waveguide_idx = 0;
                  }
                  else if(waveguide_idx >= sample->waveguide_table->num_elements)
                  {
                     // should never be reached
                     waveguide_idx = sample->waveguide_table->num_elements - 1;
                  }
               }

               sSI intWinOff = (sSI) sample->waveguide_table->elements[waveguide_idx];
               sUI winOff = (sUI) (intWinOff * current_cyclelen);
               
               sUI winOffNext;
               
               if( (waveguide_idx + 1) < sample->waveguide_table->num_elements)
               {
                  winOffNext = (sUI) (((sUI)sample->waveguide_table->elements[waveguide_idx + 1]) * current_cyclelen);
               }
               else
               {
                  winOffNext = (sUI) (((sUI)sample->waveguide_table->elements[0]) * current_cyclelen);
               }

               waveguide_next_play_offset = winOffNext;
               current_play_offset = (sF64) winOff;

               ////printf("xxx winOff=%u winOffNext=%u idx=%u\n", winOff, winOffNext, waveguide_idx);
               
            }
            else
            {
               waveguide_next_play_offset = (sUI) current_play_offset;
            }
         }
         else
         {
            // Do not have waveguide_table, treat waveguide_idx as absolute cycle index
            ////waveguide_next_play_offset = (sUI) current_play_offset;

            current_play_offset = (sF64) (waveguide_idx * current_cyclelen);
            waveguide_next_play_offset = (sUI) (next_waveguide_idx * current_cyclelen);

            // [14May2019] clip to prevent crash while changing (increasing) cyclelen in UI
            if((current_play_offset >= current_sample_len) || (waveguide_next_play_offset >= current_sample_len))
            {
               current_play_offset = 0;
               waveguide_next_play_offset = 0;
            }
         }
      } // if sample->b_waveguide

      last_relsmpoff = relSmpOff;
   }
   // else: continue playing at current_play_offset
}

void StSampleVoice::calcCurrentCycleLen(sF32 &ts) {

   LOCK_MTX_MOD;

   ts = (sample->timestretch + mod_timestretch + sample_player->mod_timestretch + mmdst_timestretch);

   current_cyclelen  = (sF32) (
      sample->timestretch_granularity * (1 - sample->timestretch_granularity_amount) + 
      (sample->timestretch_granularity * sample->timestretch_granularity_amount * (1.0f - fabs(ts)))
                            );

   current_cyclelen *= mod_cyclelen * sample_player->mod_cyclelen * mmdst_cyclelen;

   ////printf("xxx StSampleVoice::calcCurrentCyclelen: len=%f\n", current_cyclelen);
   ////printf("xxx mod_cyclelen=%f\n", mod_cyclelen);

   UNLOCK_MTX_MOD;
  
   if(current_cyclelen < 2)
      current_cyclelen = 2;
}

void StSampleVoice::calcFragmentInterpolPhase(sF32 _phase, sF32 _width) {
   if(_width <= 0.0f)
   {
      fragment_interpol = _phase;
   }
   else if(fragment_interpol < _width)
   {
      fragment_interpol = (fragment_interpol / _width) * _phase;
   }
   else if(fragment_interpol > (1.0f - _width))
   {
      sF32 t = ((1.0f - fragment_interpol) / _width);
      fragment_interpol = (1.0f - t) + (t * _phase);
   }
   else
   {
      fragment_interpol = _phase;
   }
}

sF32 StSampleVoice::getEffectiveMod(void) {
   sF32 m = mod_mod + sample_player->mod_mod;
   if(m < 0.0f) m = 0.0f; else if(m>1.0f) m = 1.0f;
   return m;
}

void StSampleVoice::applyFragmentInterpol(void) {
   switch(sample->timestretch_interpol_type)
   {
      default:
      case STSAMPLE_TIMESTRETCH_INTERPOL_NONE:
         fragment_interpol = 0.0f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_LINEAR:
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_SMOOTHSTEP:
         fragment_interpol = (fragment_interpol * fragment_interpol) * (3.0f - 2.0f * fragment_interpol);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_CLIP25:
         if(fragment_interpol < 0.25f)
         {
            fragment_interpol = 0.0f;
         }
         else
         {
            fragment_interpol = (fragment_interpol - 0.25f) / 0.75f;
         }
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_CLIP50:
         if(fragment_interpol < 0.5f)
         {
            fragment_interpol = 0.0f;
         }
         else
         {
            fragment_interpol = (fragment_interpol - 0.5f) / 0.5f;
         }
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_CLIP75:
         if(fragment_interpol < 0.75f)
         {
            fragment_interpol = 0.0f;
         }
         else
         {
            fragment_interpol = (fragment_interpol - 0.75f) / 0.25f;
         }
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_CLIPMOD:
      {
         sF32 m = mod_mod + sample_player->mod_mod;
         if(m < 0.0f) m = 0.0f; else if(m>0.9999f) m = 0.9999f;
         if(fragment_interpol < m)
         {
            fragment_interpol = 0.0f;
         }
         else
         {
            fragment_interpol = (fragment_interpol - m) / (1.0f - m);
         }
      }
      break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_CLIPVEL:
      {
         sF32 m = queued_noteon._vel;
         if(m < 0.0f) m = 0.0f; else if(m>0.9999f) m = 0.9999f;
         if(fragment_interpol < m)
         {
            fragment_interpol = 0.0f;
         }
         else
         {
            fragment_interpol = (fragment_interpol - m) / (1.0f - m);
         }
      }
      break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_OFF25:
         fragment_interpol = 0.25f + fragment_interpol*0.75f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_OFF50:
         fragment_interpol = 0.5f + fragment_interpol*0.5f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_OFF75:
         fragment_interpol = 0.75f + fragment_interpol*0.25f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_OFFMOD:
      {
         sF32 m = getEffectiveMod();
         fragment_interpol = m + fragment_interpol*(1.0f - m);
      }
      break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_OFFVEL:
      {
         sF32 m = queued_noteon._vel;
         fragment_interpol = m + fragment_interpol*(1.0f - m);
      }
      break;
               
      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE25W00:
         fragment_interpol =  0.25f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE25W25:
         calcFragmentInterpolPhase(0.25f, 0.25f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE25W50:
         calcFragmentInterpolPhase(0.25f, 0.50f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE25W75:
         calcFragmentInterpolPhase(0.25f, 0.75f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE50W00:
         fragment_interpol =  0.50f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE50W25:
         calcFragmentInterpolPhase(0.50f, 0.25f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE50W50:
         calcFragmentInterpolPhase(0.50f, 0.50f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE50W75:
         calcFragmentInterpolPhase(0.50f, 0.75f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE75W00:
         fragment_interpol = 0.75f;
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE75W25:
         calcFragmentInterpolPhase(0.75f, 0.25f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE75W50:
         calcFragmentInterpolPhase(0.75f, 0.50f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASE75W75:
         calcFragmentInterpolPhase(0.75f, 0.75f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASEMODW00:
         fragment_interpol = getEffectiveMod();
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASEMODW25:
         calcFragmentInterpolPhase(getEffectiveMod(), 0.25f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASEMODW50:
         calcFragmentInterpolPhase(getEffectiveMod(), 0.50f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASEMODW75:
         calcFragmentInterpolPhase(getEffectiveMod(), 0.75f);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASEMODVEL:
         calcFragmentInterpolPhase(getEffectiveMod(), queued_noteon._vel);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_PHASEVELMOD:
         calcFragmentInterpolPhase(queued_noteon._vel, getEffectiveMod());
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_ORDER2:
         fragment_interpol = (fragment_interpol*fragment_interpol);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_ORDER3:
         fragment_interpol = (fragment_interpol*fragment_interpol*fragment_interpol);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_ORDER4:
         fragment_interpol = (fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_ORDER5:
         fragment_interpol = (fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_ORDER6:
         fragment_interpol = (fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol);
         break;

      case STSAMPLE_TIMESTRETCH_INTERPOL_ORDER7:
         fragment_interpol = (fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol*fragment_interpol);
         break;
   }
}

void StSampleVoice::renderBlockTimestretch(sF32 *     buf,
                                           sUI        blkSz,
                                           sF32 *     smpDat,
                                           const sF32 a,
                                           const sF32 b,
                                           sF32       cVolL,
                                           sF32       cVolR,
                                           const sF32 sVolL,
                                           const sF32 sVolR
                                           )
{
   ////printf("xxx renderBlockTimestretch\n");

   // Calculate timestretch and cyclelen
   sF32 ts;
   calcCurrentCycleLen(ts);

   // [15May2019] align to cyclelen
   alignSampleLenToCyclelen(current_sample_len);
   // yac_host->printf("xxx current_sample_len=%u\n", current_sample_len);


   // yac_host->printf("xxx renderBlockTimestretch: smplen=%u cyclelen=%u\n", current_sample_len, current_cyclelen);
   if(current_sample_len < current_cyclelen)
   {
      return;
   }

   // [14May2019] clip (prevent crash when modulating wavetable sample offset with LFO)
   // (note) this check breaks loops (don't re-enable !!)
   // // if(current_play_offset < 0)
   // //    return;
   // // if((current_play_offset + current_cyclelen) > current_sample_len)
   // //    return;


   //////sF32 cycleLen  = (sF32) (mix_rate * sample->timestretch_granularity);

   ////yac_host->printf("this=%p ts=%f cycleLen=%f\n", this, ts, cycleLen);
   
   sF32 hz = noteToFreq(current_freq * (1 - a) + (next_freq * a));
   
   // mix rate to play sample at base frequency
   sF32 cRate;
   // mix rate to play cycle at desired frequency
   // //sF32 tsRate = (hz / cycleFreq) * (sample->waveform->sample_rate / mix_rate);
   sF32 tsRate;
   sF32 sRate;

   sF32 tsBend = sample->timestretch_bend + mod_timestretch_bend + sample_player->mod_timestretch_bend + mmdst_timestretch_bend;
   if(tsBend > 1.0f)
      tsBend = 1.0f;
   else if(tsBend < -1.0f)
      tsBend = -1.0f;

   sBool bBend = (0.0f != tsBend);

   if(tsBend < 0.0f)
   {
      tsBend = (1.0f / 16.0f) + ((1.0f - (1.0f/16.0f))) * (1.0f + tsBend);
   }
   else
   {
      tsBend = 1.0f + (16.0f - 1.0f) * tsBend;
   }

   if(sample->b_static_cyclelen)
   {
      // Calc current sample rate
      cRate =  noteToSampleRate(current_freq * (1 - a) + (next_freq * a));
      
      // Calc sample rate delta
      sRate = (noteToSampleRate(current_freq * (1 - b) + (next_freq * b)) /* * timeStretch */ - cRate) / blkSz;

      // Cycle replay speed equals replay speed, for wavetable-style chip samples
      tsRate = cRate;

      // modulate move-through-sample speed by timestretch (0= play same cycle over and over again)
      if(sample->b_absolute_timestretch)
      {
         // pitch independent cyclewalking (interpreted ts as 1.0=1 second to reach next cycle)
         ///cRate = (ts * current_cyclelen) / (float_block_size * STSAMPLEVOICE_TICKSPERSEC);
         cRate = (ts) / (float_block_size);
      }
      else
      {
         cRate *= ts;
      }
   }
   else
   {
      // Cycle replay speed depends on cyclelen (for timestretching non-chip samples)
      cRate = 1.0f * (sample->waveform->sample_rate / mix_rate);

      // modulate move-through-sample speed by timestretch (0= play same cycle over and over again)
      cRate *= ts;

      sF64 cycleFreq = (mix_rate / current_cyclelen); // div2 for niquist ??
   
      tsRate = (sF32) (hz / cycleFreq);// * (hz / sample->waveform->base_frequency);
      sRate = 0.0f; // (todo) support pitch block interpolation
   }

   
   
   sF32 tsOff = current_ts_offset;
   
   // sF32 cyclePlayLen = current_cyclelen / cRate;
   
   while(tsOff >= current_cyclelen)
   {
      tsOff -= current_cyclelen;
   }

   sF64 cOff = current_play_offset;
   
   ////yac_host->printf("xxx this=%p hz=%f current_cyclelen=%f cRate=%f tsRate=%f sRate=%f tsOff=%f cyclePlayLen=%f\n", this, hz, current_cyclelen, cRate, tsRate, sRate, tsOff, cyclePlayLen);

   ////yac_host->printf("xxx cOff=%f cRate=%f tsRate=%f tsOff=%f \n", cOff, cRate, tsRate, tsOff);

   ////yac_host->printf("xxx cVolL=%f cVolR=%f\n", cVolL, cVolR);
   
   
   //
   // Timestretch mixloop
      //
      // TODO: make sure that wave is not smaller than fragment size
      //
   while(blkSz > 0)
   {
      sF32 smpLA, smpRA, smpLB, smpRB;
      sF32 *smpDatNext = smpDat;
      sUI nextSampleLen = current_sample_len;

      sSI intWinOff = ((sSI)(cOff / current_cyclelen));
      sUI winOff = (sUI) (intWinOff * current_cyclelen);
      fragment_interpol = sF32((cOff - winOff) / current_cyclelen);

      sUI winOffNext;

      // Calc cycle end offset
      winOffNext = (sUI) ((intWinOff+1) * current_cyclelen);

      // [15May2019] Wrap-around
      if((winOffNext + current_cyclelen) > current_sample_len)
      {
         winOffNext = 0u;
         if((sUI(current_loop_idx) + 1u) < sUI(num_sample_loops))
         {
            if(1u == current_loop_countdown)
            {
               // Lerp to next loop
               YAC_IntArray *sampleLoops = getCurrentSampleLoops();
               sUI sloff = (current_loop_idx + 1u) * 3u;
               sUI nextSampleOffset = *(sUI*)&sampleLoops->elements[sloff + 0];
               nextSampleLen        = *(sUI*)&sampleLoops->elements[sloff + 1];
               alignSampleLenToCyclelen(nextSampleLen);
               calcSmpDat(smpDatNext, nextSampleOffset, nextSampleLen, nextSampleLen);
               // if(! (((sUI)cOff)&15 ) )
               //    yac_host->printf("xxx lerpToNextLoop cOff=%f fragIpol=%f winOff=%u curOff=%u curLen=%u nextOff=%u nextLen=%u tsOff=%f\n", cOff, fragment_interpol, winOff, current_sample_offset, current_sample_len, nextSampleOffset, nextSampleLen, tsOff);
            }
         }
      }

      if(sample->b_waveguide)
      {
         ////printf("xxx check end of cycle: cOff=%f cRate=%f winOffNext=%u\n", cOff, cRate, winOffNext);
         ////if( (cOff + cRate) >= winOffNext )
         if(intWinOff != sSI(waveguide_idx)) // (todo) in case of waveguide_table this should be waveguide_table[waveguide_idx]
         {
            //printf("xxx end of cycle\n");
            if(NULL != sample->waveguide_table)
            {
               // End of cycle
               if(sample->waveguide_table->num_elements > 0)
               {
                  // Select next cycle
                  waveguide_idx++;
                  if(waveguide_idx >= sample->waveguide_table->num_elements)
                  {
                     waveguide_idx = 0;
                  }

                  // Re-calculate current/next play offsets
                  last_relsmpoff = -1;
                  calcCurrentOffset();

                  winOff = (sUI) current_play_offset;
                  cOff = current_play_offset;
                  winOffNext = waveguide_next_play_offset;
                  
                  fragment_interpol = 0.0f;
               }
               else
               {
                  winOffNext = waveguide_next_play_offset;
               }
            }
            else
            {
               // Manual waveguide (use waveguide_idx as absolute cycle index)
               // yac_host->printf("xxx endofcycle: cidx=%d nidx=%d coff=%f nextoff=%u ts=%f\n", 
               //                  waveguide_idx, next_waveguide_idx,
               //                  current_play_offset, waveguide_next_play_offset,
               //                  ts
               //                  );
               waveguide_idx = next_waveguide_idx;
               next_waveguide_idx = queued_waveguide_idx;

               // Re-calculate current/next play offsets
               last_relsmpoff = -1;
               calcCurrentOffset();
               
               winOff = (sUI) current_play_offset;
               cOff = current_play_offset;
               winOffNext = waveguide_next_play_offset;
               
               fragment_interpol = 0.0f;

               // yac_host->printf("xxx endofcycle 2: cidx=%d nidx=%d coff=%f nextoff=%u\n", 
               //                  waveguide_idx, next_waveguide_idx,
               //                  current_play_offset, waveguide_next_play_offset
               //                  );
            }
         }
         else
         {
            winOffNext = waveguide_next_play_offset;
         }
      }
      else
      {
         // Use neighbour cycle
      }

      // Apply fragment interpolation function
      applyFragmentInterpol();

      // if(! (((sUI)cOff)&15 ) )
      //    yac_host->printf("xxx interpol=%f intWinOff=%d tsOff=%f winOff=%u winOffNext=%u cOff=%f current_cyclelen=%f current_sample_len=%u dt=%f\n", 
      //                     fragment_interpol, intWinOff, tsOff, winOff, winOffNext, cOff, current_cyclelen, current_sample_len, (cOff-winOff));
      
      sF32 tsReadOff;
      sF32 tsReadOffXFade;

      if(bBend)
      {
         tsReadOff = tsOff / current_cyclelen;
         tsReadOff = powf(tsReadOff, tsBend);  // (todo) optimize / approximate
         tsReadOff *= current_cyclelen;
      }
      else
      {
         tsReadOff = tsOff;
      }

      // // // tsReadOffXFade = current_cyclelen - 1.0f - tsReadOff;
      // // // if(tsReadOffXFade < 0.0f)
      // // //    tsReadOffXFade += current_cyclelen;
      tsReadOffXFade = current_cyclelen - tsReadOff;
      if(tsReadOffXFade >= current_cyclelen)
         tsReadOffXFade -= current_cyclelen;

      // Read current fragment (linear sample interpolation)
      {
         readWindowedCycleSample(smpDat, winOff, tsReadOff, &smpLA, &smpRA, current_sample_len);

         if(sample->b_timestretch_xfade)
         {
            sF32 l2, r2;
            readWindowedCycleSample(smpDat, winOff, tsReadOffXFade, &l2, &r2, current_sample_len);
            smpLA = (smpLA + l2) * 0.5f;
            smpRA = (smpRA + r2) * 0.5f;
         }

         // // smpLA *= cVolL;
         // // smpRA *= cVolR;
      }
      
      // Read next fragment (linear sample interpolation)
      {
         readWindowedCycleSample(smpDatNext, winOffNext, tsReadOff, &smpLB, &smpRB, nextSampleLen);

         if(sample->b_timestretch_xfade)
         {
            sF32 l2, r2;
            // readWindowedCycleSample(smpDat, winOffNext, ((sUI)current_cyclelen-1)-tsOff, &l2, &r2);
            readWindowedCycleSample(smpDatNext, winOffNext, tsReadOffXFade, &l2, &r2, nextSampleLen);
            smpLB = (smpLB + l2) * 0.5f;
            smpRB = (smpRB + r2) * 0.5f;
         }

         // // smpLB *= cVolL;
         // // smpRB *= cVolR;
      }
      
      // Calculate interpolated sample
      //fragment_interpol = (fragment_interpol*fragment_interpol) * (3.0f - 2.0f * fragment_interpol); // smoothstep
      // // sF32 newSmpL = smpLA * (1.0f - fragment_interpol) + smpLB * fragment_interpol;
      // // sF32 newSmpR = smpRA * (1.0f - fragment_interpol) + smpRB * fragment_interpol;
      sF32 newSmpL = smpLA + (smpLB - smpLA) * fragment_interpol;
      sF32 newSmpR = smpRA + (smpRB - smpRA) * fragment_interpol;

      newSmpL *= cVolL;
      newSmpR *= cVolR;

      // newSmpL = smpLA; // xxx
      // newSmpR = smpRA; // xxx

      // Interpolate samples before and after modulation to avoid clicks when sample offset is being modulated
#if 0  /// xxxxxxxx [15May2019] removed during testing
      if(anticlick_granular_smpoffinterpol_countdown > 0)
      {
         anticlick_granular_smpoffinterpol_countdown--;
         sF32 lcSmpLA, lcSmpRA;
         sF32 lcSmpLB, lcSmpRB;
         // Read current fragment before modulation (linear sample interpolation)
         {
//             printf("xxx anticlick_granular_smpoffinterpol_winoff=%u anticlick_granular_smpoffinterpol_tsoff=%f\n", 
//                    anticlick_granular_smpoffinterpol_winoff,
//                    anticlick_granular_smpoffinterpol_tsoff
//                    );
            // readWindowedSample(smpDat, 
            //                    anticlick_granular_smpoffinterpol_winoff + anticlick_granular_smpoffinterpol_tsoff, 
            //                    &lcSmpLA, &lcSmpRA
            //                    );
            readWindowedCycleSample(smpDat, 
                                    anticlick_granular_smpoffinterpol_winoff,
                                    anticlick_granular_smpoffinterpol_tsoff, 
                                    &lcSmpLA, &lcSmpRA,
                                    current_sample_len
                                    );

            if(sample->b_timestretch_xfade)
            {
               sF32 l2, r2;
               readWindowedCycleSample(smpDat,
                                       anticlick_granular_smpoffinterpol_winoff,
                                       ((sUI)current_cyclelen-1)-anticlick_granular_smpoffinterpol_tsoff,
                                       &l2, &r2,
                                       current_sample_len
                                       );
               lcSmpLA = (lcSmpLA + l2) * 0.5f;
               lcSmpRA = (lcSmpRA + r2) * 0.5f;
            }

            lcSmpLA *= cVolL;
            lcSmpRA *= cVolR;
         }
         
         // Read next fragment before modulation (linear sample interpolation)
         {
            // readWindowedSample(smpDat, 
            //                    anticlick_granular_smpoffinterpol_nextwinoff + anticlick_granular_smpoffinterpol_tsoff,
            //                    &lcSmpLB, &lcSmpRB);
            readWindowedCycleSample(smpDat, 
                                    anticlick_granular_smpoffinterpol_nextwinoff,
                                    anticlick_granular_smpoffinterpol_tsoff,
                                    &lcSmpLB, &lcSmpRB,
                                    current_sample_len
                                    );

            if(sample->b_timestretch_xfade)
            {
               sF32 l2, r2;
               readWindowedCycleSample(smpDat,
                                       anticlick_granular_smpoffinterpol_nextwinoff,
                                       ((sUI)current_cyclelen-1)-anticlick_granular_smpoffinterpol_tsoff,
                                       &l2, &r2,
                                       current_sample_len
                                       );
               lcSmpLB = (lcSmpLB + l2) * 0.5f;
               lcSmpRB = (lcSmpRB + r2) * 0.5f;
            }

            lcSmpLB *= cVolL;
            lcSmpRB *= cVolR;
         }

         // Calculate interpolated sample
         sF32 lcSmpL = lcSmpLA * (1.0f - anticlick_granular_smpoffinterpol_fragoff) + lcSmpLB * anticlick_granular_smpoffinterpol_fragoff;
         sF32 lcSmpR = lcSmpRA * (1.0f - anticlick_granular_smpoffinterpol_fragoff) + lcSmpRB * anticlick_granular_smpoffinterpol_fragoff;

         // Repeat sample calculation for sample offset before modulation
         sF32 t = (((sF32)anticlick_granular_smpoffinterpol_countdown) / ANTICLICK_GRANULAR_SMPOFFINTERPOL_TIME);
         ////printf("cd=%u t=%f\n", anticlick_granular_smpoffinterpol_countdown, t);
         newSmpL = (lcSmpL * t) + (newSmpL * (1.0f - t));
         newSmpR = (lcSmpR * t) + (newSmpR * (1.0f - t));

         // Increase and wrap-around before-modulation cycle offset
         anticlick_granular_smpoffinterpol_tsoff += tsRate;
         while(anticlick_granular_smpoffinterpol_tsoff >= current_cyclelen)
         {
            anticlick_granular_smpoffinterpol_tsoff -= current_cyclelen;
         }
      }
      else
      {
         ////printf("nointerpol: cd=%u\n", anticlick_granular_smpoffinterpol_countdown);
      }
#endif // 0

      // printf("xxx w=%u ts=%f n=(%f; %f) v=(%f; %f)\n", winOff, tsOff, newSmpL, newSmpR, cVolL, cVolR);

      last_sample_l = newSmpL;
      last_sample_r = newSmpR;
      filterLastSample();
      buf[0] += last_sample_l;
      buf[1] += last_sample_r;
      samples_since_cycle++; // for debugging purposes

#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF
      // Store for declick xfade (sampleshift_eol)
      if(sampleshift_eol_xfade_loopstart_frameidx < VOLRAMP_NUMSTEPS)
      {
         sampleshift_eol_xfade_buf_l[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_l;
         sampleshift_eol_xfade_buf_r[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_r;
         sampleshift_eol_xfade_loopstart_frameidx++;
      }
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF
      
      cVolL += sVolL;
      cVolR += sVolR;
      
      tsOff += tsRate;
      tsRate += sRate;
      
      ////yac_host->printf("xxx 1 tsOff=%f tsRate=%f\n", tsOff, tsRate);
      while(tsOff >= current_cyclelen)
      {
         tsOff -= current_cyclelen;
      }
      
      cOff += cRate;

#if 0
      while( cOff < 0.0f )
      {
         // reverse play
         cOff += (sF64) current_sample_len;
         // TODO: handle negative sample rate + loops (reverse play)
      }
      while( ((sUI)cOff) >= current_sample_len )
      {
         if(!handleEndOfLoop(&cOff, &smpDat))
         {
            // End of sample
            return;
         }
      }
#else
      if( cOff < 0.0f )
      {
         // reverse play
         // // cOff += (sF64) (current_sample_len - current_cyclelen);
         cOff += (sF64) (current_sample_len);
         if(cOff < 0)
            cOff = 0;
         // TODO: handle negative sample rate + loops (reverse play)
      }
      if( ((sUI)cOff) >= current_sample_len )
      {
         // yac_host->printf("xxx TS handleEndOfLoop cOff=%f current_sample_len=%u\n", cOff, current_sample_len);
         if(!handleEndOfLoop(&cOff, &smpDat))
         {
            // End of sample
            return;
         }
         alignSampleLenToCyclelen(current_sample_len);
         // yac_host->printf("xxx TS 2 handleEndOfLoop cOff=%f current_sample_len=%u\n", cOff, current_sample_len);
      }
#endif
      
      
      // debug:
      // if(samples_since_cycle >= cyclePlayLen)
      // {
      //    samples_since_cycle = 0;
      // }
      
      
      // Next sample
      buf += 2;
      blkSz--;
   }
   
   current_play_offset = cOff;
   current_ts_offset = tsOff;

   ////printf("xxx   --> end block cOff=%f\n", cOff);
}


sUI StSampleVoice::renderFragmentOptFwdMonoSimple(sF32 *&    buf,
                                                  const sUI  _fragSize,
                                                  sUI &      genSizeHint,
                                                  sF32 *&    smpDat,
                                                  sF32 &     cVolL,
                                                  sF32 &     cVolR,
                                                  sF32 &     cRate
                                                  )
{
   // mono sample, fwd play, constant freq/vol, b_timedloop=0

   sF64 cOff  = current_play_offset;
   ////sUI  numCh = sample->waveform->num_channels;

   //printf("xxx renderFragmentOptFwdMonoSimple: start=%p smpDat=%p cOff=%f\n", sample->waveform->sample_data->elements, smpDat, cOff);

   ////printf("xxx mixmono: coff=%f len=%u\n", cOff, current_sample_len);
   
   //
   // special case optimization: 
   //
   //    ---- mix mono sample at constant speed/volume/pan ----
   //

   sUI smpLeft = (sUI) ( ((current_sample_len - cOff) / cRate) );

   ///printf("xxx opt: smpLeft=%d\n", smpLeft);
   
   /////if( ( (smpLeft < 2) || ((sUI)cOff) == (current_sample_len-1) ) )
//    if( (smpLeft < 2) || 
//        ( (cOff + smpLeft*cRate) >= (current_sample_len-1) )
//        )


   if((smpLeft-2) > _fragSize)
   {
      // Mix until end of block
      smpLeft = _fragSize;
   }
   else
   {
      // Mix until end of sample (last sample is special case because of lerp to start of loop)
      //
      // let generic renderer handle end of sample (loop) special case
      //
      genSizeHint = smpLeft;
      if(genSizeHint > _fragSize)
      {
         genSizeHint = _fragSize;
      }
      return 0;
   }


// // //xxx debug: force generic loop
// //       genSizeHint = smpLeft;
// //       if(genSizeHint > _fragSize)
// //       {
// //          genSizeHint = _fragSize;
// //       }
// //       return 0;


   sSI xxxELO = (sSI)(cOff + smpLeft*cRate);
   //printf("xxx opt: ELO=%d current_sample_len=%d\n", xxxELO);

   if( (smpLeft < 2) || (xxxELO >= (sSI)(current_sample_len-2)) )
   {
      // let generic renderer handle last sample special case
      genSizeHint = smpLeft;
      if(0 == genSizeHint)
      {
         genSizeHint = 8;
         /////printf("xxx 1 genSizeHint is 0 (should never happen ATP) current_sample_len=%d cOff=%f cRate=%f\n", current_sample_len, cOff, cRate);
      }
      if(genSizeHint > _fragSize)
      {
         genSizeHint = _fragSize;
      }

      ////printf("xxx renderFragmentOptFwdMonoSimple: fallback to generic loop: smpLeft=%u xxxELO=%d\n", smpLeft, xxxELO);
      return 0;
   }

   sUI k = smpLeft;

#ifdef USE_STATS
   stat_opt_innermix += k;
#endif // USE_STATS

   ////printf("xxx render opt forward mono\n");
   
   // No loop-bounds crossed during block mixing
   if(STSAMPLE_INTERPOL_LINEAR == sample->interpol_type)
   {
      while(k-- > 0)
      {
         // Note: linear sample interpolation, interpolate to right neighbour
         sUI smpOff = ((sUI)cOff);
//       if( (smpOff+2) >= current_sample_len)
//       {
//          printf("xxx wtf smpLeft=%d cOff=%f current_sample_len=%d k=%d\n", smpLeft, cOff, current_sample_len, k);
//          printf("xxx opt: ELO=%d current_sample_len=%d\n", xxxELO);
//       }
         sF32 smpFrac = sF32(cOff - smpOff);
         sF32 smpL;
         smpL = smpDat[smpOff + 0] * (1-smpFrac) + smpDat[smpOff + 1 + 0] * smpFrac;
         last_sample_l = smpL * cVolL;
         last_sample_r = smpL * cVolR; // mono to stereo
         buf[0] += last_sample_l;
         buf[1] += last_sample_r; 

#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF
      // Store for declick xfade (sampleshift_eol)
      if(sampleshift_eol_xfade_loopstart_frameidx < VOLRAMP_NUMSTEPS)
      {
         sampleshift_eol_xfade_buf_l[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_l;
         sampleshift_eol_xfade_buf_r[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_r;
         sampleshift_eol_xfade_loopstart_frameidx++;
      }
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF

         cOff += cRate;
         buf += 2;
      }
   }
   else
   {
      // No interpolation
      while(k-- > 0)
      {
         sUI smpOff = ((sUI)cOff);
         sF32 smpL;
         smpL = smpDat[smpOff + 0];
         ////printf("xxx smpOff=%u smpL=%f cVolL=%f cVolR=%f\n", smpOff, smpL, cVolL, cVolR);
         last_sample_l = smpL * cVolL;
         last_sample_r = smpL * cVolR; // mono to stereo
         buf[0] += last_sample_l;
         buf[1] += last_sample_r; 

#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF
      // Store for declick xfade (sampleshift_eol)
      if(sampleshift_eol_xfade_loopstart_frameidx < VOLRAMP_NUMSTEPS)
      {
         sampleshift_eol_xfade_buf_l[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_l;
         sampleshift_eol_xfade_buf_r[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_r;
         sampleshift_eol_xfade_loopstart_frameidx++;
      }
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF

         cOff += cRate;
         buf += 2;
      }
   }

   if( ((sUI)cOff) >= current_sample_len )
   {
      // Begin next loop(-iteration)
      //DP("1 handleEndOfLoop pre cOff=%f\n", cOff);
      if(!handleEndOfLoop(&cOff, &smpDat))
      {
         // End of sample
         return 0;
      }
      //DP("1 handleEndOfLoop post cOff=%f\n", cOff);
   }

   current_play_offset = cOff;
   
   return smpLeft; // return #samples processed
}

void StSampleVoice::filterLastSample(void) {
   switch(filter_type)
   {
      default:
      case STSAMPLE_FLT_TYPE_NONE:
         break;

      case STSAMPLE_FLT_TYPE_LP:
      case STSAMPLE_FLT_TYPE_HP:
      case STSAMPLE_FLT_TYPE_BP:
      case STSAMPLE_FLT_TYPE_BR:
      case STSAMPLE_FLT_TYPE_PEQ:
      case STSAMPLE_FLT_TYPE_LSH:
      case STSAMPLE_FLT_TYPE_HSH:
         last_sample_l = biquad_b_1[0].filter(last_sample_l);
         last_sample_r = biquad_b_1[1].filter(last_sample_r);
         break;

      case STSAMPLE_FLT_TYPE_LPLP:
      case STSAMPLE_FLT_TYPE_LPHP:
      case STSAMPLE_FLT_TYPE_LPBP:
      case STSAMPLE_FLT_TYPE_LPBR:
      case STSAMPLE_FLT_TYPE_HPLP:
      case STSAMPLE_FLT_TYPE_HPHP:
      case STSAMPLE_FLT_TYPE_HPBP:
      case STSAMPLE_FLT_TYPE_HPBR:
      case STSAMPLE_FLT_TYPE_BPLP:
      case STSAMPLE_FLT_TYPE_BPHP:
      case STSAMPLE_FLT_TYPE_BPBP:
      case STSAMPLE_FLT_TYPE_BPBR:
      case STSAMPLE_FLT_TYPE_BRLP:
      case STSAMPLE_FLT_TYPE_BRHP:
      case STSAMPLE_FLT_TYPE_BRBP:
      case STSAMPLE_FLT_TYPE_BRBR:
      case STSAMPLE_FLT_TYPE_PEQ2:
      case STSAMPLE_FLT_TYPE_LSH2:
      case STSAMPLE_FLT_TYPE_HSH2:
         last_sample_l = biquad_b_1[0].filter(last_sample_l);
         last_sample_l = biquad_b_2[0].filter(last_sample_l);
         last_sample_r = biquad_b_1[1].filter(last_sample_r);
         last_sample_r = biquad_b_2[1].filter(last_sample_r);
         break;
   }
}

sUI StSampleVoice::renderFragmentGeneric(sF32 *&    buf,
                                         const sUI  _fragSize,
                                         sF32 *&    smpDat,
                                         sF32 &     cVolL, 
                                         sF32 &     cVolR, 
                                         const sF32 sVolL, 
                                         const sF32 sVolR,
                                         sF32 &     cRate,
                                         const sF32 sRate
                                         )
{

   sUI fragSize = _fragSize;

   sF64 cOff  = current_play_offset;
   sUI  numCh = sample->waveform->num_channels;

   // static int xxx = 0;
   // if((++xxx & 127) == 0)
   // {
   // yac_host->printf("xxx cRate=%f sRate=%f\n", cRate, sRate);
   //    yac_host->printf("xxx renderFragmentGeneric: start=%p smpDat=%p cOff=%f\n", sample->waveform->sample_data->elements, smpDat, cOff);
   // }


   // (slow) generic loop
   while(fragSize > 0)
   {
      sF32 fadeVol;

      ////printf("xxx fadein=%d fadeout=%d\n", fadein_countdown, fadeout_countdown);

      if(fadein_countdown > 0)
      {
         fadein_countdown--;
         fadeVol = ( ((sF32)(VOLRAMP_NUMSTEPS - fadein_countdown)) / VOLRAMP_NUMSTEPS);
      }
      else if(fadeout_countdown > 0)
      {
         fadeout_countdown--;
         fadeVol = ( ((sF32)fadeout_countdown) / VOLRAMP_NUMSTEPS);
      }
      else if(b_fading_out)
      {
         fadeVol = 0.0f;
      }
      else
      {
         fadeVol = 1.0f;
      }


      // Note: linear sample interpolation
      sUI intOff = ((sUI)cOff);
      // yac_host->printf("xxx cOff=%f intOff=%d\n", cOff, intOff);

      if(intOff >= current_sample_len)
      {
         intOff = current_sample_len-1;
      }

      sUI smpOff = intOff * numCh;
      sF32 smpFrac = sF32(cOff - intOff);
      sUI smpOffR;
      if((intOff + 1) >= current_sample_len)
      {
         // interpolate to current loop start (ideally: start of next loop..)
         if(sample->b_timestretch)
         {
            smpOffR = 0;
         }
         else
         {
            // workaround to avoid clicks at end of oneshot samples, whose first sample value is != 0
            smpOffR = smpOff;
         }
      }
      else
      {
         // interpolate to next neighbour sample
         smpOffR = smpOff + numCh;
      }

      ////yac_host->printf("xxx smpDat=%p cRate=%f cVolL=%f cVolR=%f cOff=%f smpOff=%u/%u\n", smpDat, cRate, cVolL, cVolR, cOff, smpOff, current_sample_len);
      
      sF32 smpL; 
      sF32 smpR;

      ////printf("xxx smpL=%f cVolL=%f smpFrac=%f fadeVol=%f smpDat=%p zero=%p\n", smpL, cVolL, smpFrac, fadeVol, smpDat, zero_samples);

      if(STSAMPLE_INTERPOL_LINEAR == sample->interpol_type)
      {
         smpL = smpDat[smpOff + 0] * (1-smpFrac) + smpDat[smpOffR + 0] * smpFrac;

         if(numCh > 1)
         {
            smpR = smpDat[smpOff + 1] * (1-smpFrac) + smpDat[smpOffR + 1] * smpFrac;
         }
         else
         {
            smpR = smpL; // mono to stereo
         }
      }
      else
      {
         // No interpolation
         smpL = smpDat[smpOff + 0];

         if(numCh > 1)
         {
            smpR = smpDat[smpOff + 1];
         }
         else
         {
            smpR = smpL; // mono to stereo
         }
      }

      // Calc output
      last_sample_l = smpL;
      last_sample_r = smpR;

      // Filter
      filterLastSample();

      // Amp output
      last_sample_l = last_sample_l * cVolL * fadeVol;
      last_sample_r = last_sample_r * cVolR * fadeVol;

      if(sampleshift_eol_xfade_frames_left > 0u)
      {
         // Crossfade from last loop frame to avoid clicks
#ifndef USE_SAMPLESHIFT_EOL_XFADE_BUF
         sampleshift_eol_xfade_frames_left--;
         sF32 xfadeAmt = sampleshift_eol_xfade_frames_left * (1.0f / VOLRAMP_NUMSTEPS);
         last_sample_l = last_sample_l + (last_sample_sampleshift_eol_l - last_sample_l) * xfadeAmt;
         last_sample_r = last_sample_r + (last_sample_sampleshift_eol_r - last_sample_r) * xfadeAmt;
#else
#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF_THRESHOLD
         sBool bUseXfadeBuf = (sampleshift_eol_xfade_loopstart_frameidx < VOLRAMP_NUMSTEPS);
         if(bUseXfadeBuf)
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF_THRESHOLD
         {
            sUI xfadeBufIdx = VOLRAMP_NUMSTEPS - sampleshift_eol_xfade_frames_left;
            sF32 xfadeAmt = sampleshift_eol_xfade_frames_left * (1.0f / VOLRAMP_NUMSTEPS);
            last_sample_l = last_sample_l + (sampleshift_eol_xfade_buf_l[xfadeBufIdx] - last_sample_l) * xfadeAmt;
            last_sample_r = last_sample_r + (sampleshift_eol_xfade_buf_r[xfadeBufIdx] - last_sample_r) * xfadeAmt;
            sampleshift_eol_xfade_frames_left--;
         }
#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF_THRESHOLD
         else
         {
            sampleshift_eol_xfade_frames_left--;
            sF32 xfadeAmt = sampleshift_eol_xfade_frames_left * (1.0f / VOLRAMP_NUMSTEPS);
            last_sample_l = last_sample_l + (last_sample_sampleshift_eol_l - last_sample_l) * xfadeAmt;
            last_sample_r = last_sample_r + (last_sample_sampleshift_eol_r - last_sample_r) * xfadeAmt;
         }
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF_THRESHOLD
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF
      }

      // Store left channel
      buf[0] += last_sample_l;

      // Store right channel
      buf[1] += last_sample_r;

#ifdef USE_SAMPLESHIFT_EOL_XFADE_BUF
      // Store for declick xfade (sampleshift_eol)
      if(sampleshift_eol_xfade_loopstart_frameidx < VOLRAMP_NUMSTEPS)
      {
         sampleshift_eol_xfade_buf_l[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_l;
         sampleshift_eol_xfade_buf_r[sampleshift_eol_xfade_loopstart_frameidx] = last_sample_r;
         sampleshift_eol_xfade_loopstart_frameidx++;
      }
#endif // USE_SAMPLESHIFT_EOL_XFADE_BUF

      // Next sample
      buf += 2;
      
      cVolL += sVolL;
      cVolR += sVolR;
      
      cOff += cRate;
      cRate += sRate;

    
      // TODO: properly handle negative sample rate + loops (reverse play)

      if( cOff < 0.0f )
      {
         // reverse play
         cOff += (sF64) current_sample_len;
      }
      else 
      {
         if(sample->b_timedloop)
         {
            samples_until_end_of_loopstep--;
            ////printf("xxx cOff=%d csl=%d samples_until_end_of_loopstep=%d\n", (sSI)cOff, current_sample_len, samples_until_end_of_loopstep);

//             {
//                sUI smpUntilZero = ((current_sample_len - ((sUI)cOff)) / cRate);
//                printf("xxx 1 smpUntilZero=%d\n", smpUntilZero);
//             }

            if((samples_until_end_of_loopstep <= 0) || (-1 != mod_jumptoloop))
            {
               // Timeout, start new loop
               //printf("xxx start new loop\n");
               if(!handleEndOfLoop(&cOff, &smpDat))
               {
                  // End of sample
                  return 0;
               }
               ///printf("xxx untilend=%d\n", samples_until_end_of_loopstep);
            }
            else if(samples_until_end_of_loopstep == VOLRAMP_NUMSTEPS)
            {
               /////printf("xxx b_timedloop_fade=%d b_fading_out=%d cd=%d\n", sample->b_timedloop_fade, b_fading_out, fadeout_countdown);
               if(sample->b_timedloop_fade)
               {
                  // start fadeout because of destsample boundary
                  if(!b_fading_out)
                  {
                     fadeout_countdown = VOLRAMP_NUMSTEPS;
                     fadein_countdown = -1;
                     b_fading_out = 1;
                  }
               }
            }
            else if(sample->b_oneshot_loopstep)
            {
               if(!b_zero)
               {
                  if(!b_fading_out)
                  {
                     sUI smpUntilZero = (sUI) ((current_sample_len - ((sUI)cOff)) / cRate);
                     //printf("xxx smpUntilZero=%d\n", smpUntilZero);
                     if( smpUntilZero <= VOLRAMP_NUMSTEPS )
                     {
                        // Fade sample out before auto-inserting silence
                        b_fading_out = 1;
                        fadeout_countdown = smpUntilZero;
                        fadein_countdown = -1;
                        //printf("xxx fadeout countdown=%d\n", fadeout_countdown);
                     }
                  }
               }
            }

            // Continue looping the current cycle
            if( ((sUI)cOff) >= current_sample_len )
            {
               if(sample->b_oneshot_loopstep)
               {
                  //printf("xxx start zero fadeout_countdown=%d\n", fadeout_countdown);
                  b_zero = 1;
                  smpDat = zero_samples;
                  ////printf("xxx zero smpDat=%p\n", smpDat);
                  current_sample_len = NUM_ZERO_SAMPLES;
                  cOff = 0.0f;
               }
               else
               {
                  do 
                  {
                     cOff -= (sF32) current_sample_len;
                  }
                  while( ((sUI)cOff) >= current_sample_len );
               }
            }

         }
         else
         {
            // Pitch-dependent/cycle-boundary loop mode

            // Fade end of sample to avoid clicks?
            //  only allowed for one-shot samples (workaround for DC offset'd waveforms!)
            if(sample->b_timedloop_fade && !num_sample_loops)
            {
               if(!b_fading_out)
               {
                  if( ((sSI)cOff) >= (((sSI)current_sample_len)-VOLRAMP_NUMSTEPS) )
                  {
                     // start fadeout because of destsample boundary
                     fadeout_countdown = VOLRAMP_NUMSTEPS;
                     fadein_countdown = -1;
                     b_fading_out = 1;
                  }
               }
            }

            if( ((sUI)cOff) >= current_sample_len )
            {
               // End of loop cycle, start new loop
               if(!handleEndOfLoop(&cOff, &smpDat))
               {
                  // End of sample
                  return 0;
               }
            }
         }

      } // else if reverse play

      // Next sample
      fragSize--;
   }

   current_play_offset = cOff;

#ifdef USE_STATS
   stat_opt_generic += _fragSize;
#endif

   return _fragSize;
}

sUI  StSampleVoice::renderBlockNormal(sF32 *     buf,
                                      sUI        blkSz,
                                      sF32 *     smpDat,
                                      const sF32 a,
                                      const sF32 b,
                                      sF32       cVolL,
                                      sF32       cVolR,
                                      const sF32 sVolL,
                                      const sF32 sVolR
                                      )
{
   //
   // No timestretching, return number of samples left (>0 if voice stops, for example)
   //
   ////printf("renderblocknormal\n");

   sF32 *bufStart = buf;
   sUI blkSzStart = blkSz;
   
   // // sF32 timeStretch;
   // // if(sample->b_timestretch) // xxx code never reached
   // // {
   // //    timeStretch = (sample->timestretch + mod_timestretch + sample_player->mod_timestretch);
   // // }
   // // else
   // // {
   // //    timeStretch = 1.0f;
   // // }

   // Calc current sample rate
   sF32 cRate =  noteToSampleRate(current_freq * (1 - a) + (next_freq * a));//// * timeStretch;

   // Calc sample rate delta
   sF32 sRate = (noteToSampleRate(current_freq * (1 - b) + (next_freq * b)) /* * timeStretch */ - cRate) / blkSz;

   // printf("xxx cRate=%f sRate=%f\n", cRate, sRate);
 
   // xxx TODO: generic loop optimizations:
   //     - freq/rate interpolation yes/no
   //        - no rate interpol: 
   //            - reverse play yes/no
   //            - loops yes/no
   //               - yes: calc #smps until loop end or use blkSz
   //                 (i.e. do not check for loop bounds in innerloop)
   //            - volume/pan interpolation yes/no
   //            - current/next volume is 0, rate => do not write samples but still calc sampleoff/loopidx
   //     - current_sample_len power of two and only default loop (use and to eliminate bounds check)

   // static int xxx = 0;
   // if((++xxx & 127) == 0)
   // {
   //    yac_host->printf("xxx cRate=%f sRate=%f\n", cRate, sRate);
   // }

  
   while(blkSz > 0)
   {
      sUI numDone = 0; // number of samples handled by fragment renderer
      sUI genSizeHint;
      
      // special case 1:
      if(
         (0 == sample->b_timedloop)&&  // total loop time is pitch dependent
         (cRate >= 0.0f)   &&  // forward play
         (sRate == 0.0f)   &&  // no freq/rate interpolation
         (sVolL == 0.0f)   &&  // no vol. interpolation
         (1 == sample->waveform->num_channels) && // mono
         (STSAMPLE_FLT_TYPE_NONE == filter_type) && // no filtering
         (0u == sampleshift_eol_xfade_frames_left)  // no sampleshift declick/xfade
         )
      {
         genSizeHint = 0;
         numDone = renderFragmentOptFwdMonoSimple(buf, blkSz, genSizeHint, smpDat, cVolL, cVolR, cRate);
         ////printf("xxx opt: numDone=%d genSizeHint=%d blkSize=%d b_playing=%d\n", numDone, genSizeHint, blkSz, b_playing);
      }
      else
      {
         // force generic loop for entire block
         genSizeHint = blkSz;
      }
      
      if(!numDone)
      {
         if(0 != genSizeHint)
         {
            numDone = renderFragmentGeneric(buf, genSizeHint, smpDat, cVolL, cVolR, sVolL, sVolR, cRate, sRate);
            ////printf("xxx gen: numDone=%d genSizeHint=%d blkSize=%d b_playing=%d\n", numDone, genSizeHint, blkSz, b_playing);
         }
         else
         {
            // voice stopped playing in optimized loop
            ////printf("xxx voice stopped in opt. loop\n");
            return blkSz;
         }
      }

      if(!numDone)
      {
         // voice stopped playing in any loop

//          if(buf != bufStart)
//          {
//             last_sample_l = buf[-2];
//             last_sample_r = buf[-1];
//             ////printf("xxx renderBlockNormal: voice stopped, lastSample=(%f; %f)\n", last_sample_l, last_sample_r);
//          }
// //          else
// //          {
// //             last_sample_l = 0.0f;
// //             last_sample_r = 0.0f;
// //          }

         // Return number of unprocessed buffer samples
         return sUI(blkSzStart - ((buf-bufStart)/2));
      }
      else
      {
//          if(buf != bufStart)
//          {
//             last_sample_l = buf[-2];
//             last_sample_r = buf[-1];
//             ////printf("xxx renderBlockNormal: numDone=%u lastSample=(%f; %f)\n", numDone, last_sample_l, last_sample_r);
//          }
//          else
//          {
//             last_sample_l = 0.0f;
//             last_sample_r = 0.0f;
//          }
      }
      
      
      blkSz -= numDone;
      
   } // while blkSize > 0

   return 0;
}


sUI StSampleVoice::renderBlock(sF32 *buf, sUI blkSz, sF32 a, sF32 b, sF32 _volScale) {

   // return number of samples that have NOT been processed (e.g. voice stops)

   sF32 cVol = current_vol * _volScale;
   sF32 nVol = next_vol    * _volScale;

   if(sample->b_freeze_sampleoff_when_quiet && Dfltzero(cVol) && Dfltzero(nVol))
   {
      // early out: speed optimization, do not render samples when vol is 0
      //            the drawback is that the layered samples may be phase-shifted
      return 0;
   }

   ////printf("xxx renderBlock: this=%p blkSz=%d a=%f b=%f cVol=%f nVol=%f\n", this, blkSz, a,b,cVol,nVol);
   ////printf("xxx renderBlock: cVol=%f nVol=%f current_vol=%f next_vol=%f volScale=%f\n", cVol,nVol, current_vol, next_vol, _volScale);
   
   // Lerp volume
   sF32 aVol = cVol + (nVol - cVol) * a;
   sF32 bVol = cVol + (nVol - cVol) * b;

   // Lerp panning
   sF32 aPan = current_pan + (next_pan - current_pan) * a;
   sF32 bPan = current_pan + (next_pan - current_pan) * b;

   // yac_host->printf("xxx aPan=%f bPan=%f\n", aPan, bPan);

   // Hotfix for clicking samples when pan=1.0
   //  (todo) investigate actual root cause
   if(aPan > 0.99999f)
      aPan = 0.99999f;
   if(bPan > 0.99999f)
      bPan = 0.99999f;

   // Calc current/next absolute volume
#if 0
   // old panning code from 2009 (center pan == half volume)
   sF32 ap = ((1.0f - aPan) * 0.5f);
   sF32 cVolL = aVol * ap;
   sF32 cVolR = aVol * (1.0f - ap);

   sF32 bp = ((1.0f - bPan) * 0.5f);
   sF32 bVolL = bVol * bp;
   sF32 bVolR = bVol * (1.0f - bp);
#else
   // PAN_LINEAR (13Dec2018)
   sF32 cVolL = (aPan < 0.0f) ? 1.0f : (1.0f - aPan);
   sF32 cVolR = (aPan > 0.0f) ? 1.0f : (1.0f + aPan);
   cVolL *= aVol;
   cVolR *= aVol;

   sF32 bVolL = (bPan < 0.0f) ? 1.0f : (1.0f - bPan);
   sF32 bVolR = (bPan > 0.0f) ? 1.0f : (1.0f + bPan);
   bVolL *= bVol;
   bVolR *= bVol;

   // yac_host->printf("xxx cVol=(%f; %f) bVol=(%f; %f)\n", cVolL, cVolR, bVolL, bVolR);
#endif

   // Calc volume delta
   sF32 sVolL = (bVolL - cVolL) / blkSz;
   sF32 sVolR = (bVolR - cVolR) / blkSz;

   // Recalc current_play_offset according to mod_sampleoff / sp.mod_sampleoff / mmdst_sampleoff
   calcCurrentSampleLen();
   if(sample->b_timestretch)
   {
      sF32 ts;
      calcCurrentCycleLen(ts); // may be used by calcCurrentOffset
   }
   calcCurrentOffset();

   sF32 *smpDat;
   if(b_zero)
   {
      smpDat = zero_samples;
   }
   else
   {
      calcSmpDat(smpDat, current_sample_offset, current_orig_sample_len, current_sample_len);
   }

   //yac_host->printf("xxx renderBlock: cOff=%f\n", cOff);

   

   sUI blkLeft;

   //
   // Is timestretching enabled ?
   //

   ////printf("xxx renderBlock: cVolL=%f sVolL=%f\n", cVolL, sVolL);

   if(sample->b_timestretch)
   {
      renderBlockTimestretch(buf, blkSz, smpDat, a, b, cVolL, cVolR, sVolL, sVolR);
      blkLeft = 0;
   }
   else
   {
      blkLeft = renderBlockNormal(buf, blkSz, smpDat, a, b, cVolL, cVolR, sVolL, sVolR);
   } // else if b_timestrech

   return blkLeft;
}

void StSampleVoice::calcSmpDat(sF32*&smpDat, 
                               const sUI _curSampleOffset, 
                               const sUI _curOrigSampleLen, 
                               const sUI _curSampleLen
                               ) {
   // Calculate current samplepointer, including (modulated) sample offset

   // (TODO) add sample->b_modshiftorig toggle
   sSI smpShift = (sSI) _curSampleOffset/*current_sample_offset*/;
   sSI smpLeft;
   sUI waveNumFrames = sample->waveform->_getNumFrames();
   if(sample->b_modshiftorig)
   {
      // modShift constant sweep: 0..(len-origsmplen)
      smpLeft = waveNumFrames - (smpShift + _curOrigSampleLen/*current_orig_sample_len*/);
   }
   else
   {
      // modShift dynamic sweep: 0..(len-modsmplen)
      smpLeft = waveNumFrames - (smpShift + _curSampleLen/*current_sample_len*/);
   }
   if(smpLeft > 1)
   {
      sF32 modSh = mod_sampleshift + sample_player->mod_sampleshift + mmdst_sampleshift;
      if(modSh > 0.99f)
      {
         modSh = 0.99f;
      }
      smpShift += (sSI) (smpLeft * modSh);
      ////printf("xxx calcSmpDat: modSh=%f\n", modSh);
   }
   if(smpShift < 0)
      smpShift = 0;
   // // // else if(smpShift >= (current_sample_len - current_sample_offset))
   // // //    smpShift = (current_sample_len - current_sample_offset);
   smpDat = sample->waveform->sample_data->elements + ( smpShift * sample->waveform->num_channels );

   if(b_used && (_curOrigSampleLen == current_orig_sample_len)) // false after softStopVoice() (volrampdown)
      sample->waveform->ui_last_played_offset = sF32(smpShift + current_play_offset);

   ////printf("xxx calcSmpDat: smpOff=%u smpShift=%u last_played_offset=%f\n", current_sample_offset, smpShift, sample->waveform->ui_last_played_offset);

   /////printf("xxx set smpDat=%p smpShift=%d csoff=%d shift=%f spshift=%f\n", smpDat, smpShift, current_sample_offset, mod_sampleshift, sample_player->mod_sampleshift);
}

sUI StSampleVoice::handleNextBlock(void) {
   sUI blkSz = (current_block_size - current_block_offset);
   if(0 == blkSz)
   {
      // Next block
      float_block_off      = (float_block_off + float_block_size) - (sF32)current_block_size;
      current_block_size   = (sUI) (float_block_off + float_block_size);
      current_block_offset = 0;
      blkSz = current_block_size;
      
      ////yac_host->printf("xxx StSampleVoice::render: next block size=%u\n", blkSz);
      
      // Determine new "next_" values (envelopes, modulation, zones)
      calcNextBlockState();

      tickGlide();
      
      replay_ticks++;
   }
   return blkSz;
}

void StSampleVoice::_render(YAC_Object *_buf, sF32 _volScale) {

   ////yac_host->printf("xxx StSampleVoice::_render: b_playing=%i anticlick_fadeout_countdown=%u volScale=%f\n", b_playing, anticlick_fadeout_countdown,_volScale);

   // "add" to stereo buffer
   if(YAC_BCHK(_buf, YAC_CLID_FLOATARRAY))
   {
      YAC_CAST_ARG(YAC_FloatArray, fa, _buf);
      sUI numSamples = fa->yacArrayGetNumElements() / 2; // div by 2 because of stereo
      sF32 *buf = (sF32*) fa->yacArrayGetPointer();
      sF32 *dbgBufStart = buf;
      sUI dbgNumSamples = numSamples;

      if(b_playing)
      {
         while(b_playing && (numSamples > 0))
         {
            sUI blkSz;
            
            ////yac_host->printf("xxx samplevoice: numSamples=%u float_block_size=%f\n", numSamples, float_block_size);

            if(current_delay_countdown > 0)
            {
               ////printf("xxx StSampleVoice::render: current_delay_countdown=%d\n", current_delay_countdown);

               blkSz = current_delay_countdown;
               if(blkSz > numSamples)
               {
                  blkSz = numSamples;
               }
               current_delay_countdown -= blkSz;

               if(current_delay_countdown <= 0)
               {
                  // // b_playing = 0;
                  // // reallyStartVoice(&queued_noteon, 0);

                  current_delay_countdown = 0;

                  queued_noteon.b_valid =0;

                  // Calc initial block size
                  float_block_size     = STSAMPLEVOICE_BLOCK_LEN * queued_noteon._mixRate;
                  float_block_off      = 0.0f;
                  current_block_size   = (sUI) float_block_size;
                  current_block_offset = 0;

                  numSamples           -= blkSz;
                  buf                  += (blkSz << 1); // *2 for stereo

                  blkSz = handleNextBlock();
                  continue;
               }

               // "add" silence
            }
            else
            {
               blkSz = handleNextBlock();
               
               // Clip to outbuf size
               if(blkSz > numSamples)
               {
                  blkSz = numSamples;
               }

               // Calc current/next interpolation factors and render sample block
               sF32 a = (current_block_offset / (sF32)current_block_size);
               sF32 b = (a + (blkSz/(sF32)current_block_size));

               if(b_playing)
               {
                  blkSz = blkSz - renderBlock(buf, blkSz, a, b, _volScale);
               }
               else
               {
                  blkSz = 0;
               }
            }

            current_block_offset += blkSz;
            numSamples           -= blkSz;
            buf                  += (blkSz << 1); // *2 for stereo
         }

      } // if b_playing

      if(0 && (!b_playing && (anticlick_fadeout_countdown > 0)))
      {
         // Handle anticlick fadeout but also sample retrig (in handleNextBlock() -> calcNextBlockState())
         while(numSamples > 0)
         {
            sUI blkSz = handleNextBlock(); // calls calcNextBlockState() if block boundary is hit
            
            // Clip to outbuf size
            if(blkSz > numSamples)
            {
               blkSz = numSamples;
            }

            sUI i;
            sUI k = 0;
            sUI l = (blkSz < anticlick_fadeout_countdown) ? blkSz : anticlick_fadeout_countdown;

            //yac_host->printf("xxx anticlick_fadeout_countdown=%u, numSamples=%u l=%u\n", anticlick_fadeout_countdown, numSamples, l);

            ////yac_host->printf("xxx anticlick_fadeout_countdown=%u, bufOff=%u numSamples=%u l=%u\n", anticlick_fadeout_countdown, (sUI)(buf-dbgBufStart), numSamples, l);
//          for(i=0; i<dbgNumSamples; i++)
//          {
//             printf("%f, ", dbgBufStart[i*2+0]);
//          }
//          printf("\n");
            
            for(i=0; i<l; i++)
            {
               sF32 f = anticlick_fadeout_countdown * (1.0f / ((sF32)ANTICLICK_FADEOUT_TIME));
               ////printf("xxx buf[k+0]=%f\n", buf[k+0]);
               buf[k + 0] += last_sample_l * f;
               buf[k + 1] += last_sample_r * f;
               ////yac_host->printf("xxx StSampleVoice: anticlick fadeout l=%f r=%f lastL=%f lastR=%f\n", buf[k+0], buf[k+1], last_sample_l, last_sample_r);
               anticlick_fadeout_countdown--;
               k += 2;
            }

            if(0 == anticlick_fadeout_countdown)
            {
               ////yac_host->printf("xxx StSampleVoice: replay finally stopped after fadeout last_sample_l=%f last_sample_r=%f queued_noteon.b_valid=%d\n", last_sample_l, last_sample_r, queued_noteon.b_valid);
               
               voice_key = 0; // setting this to 0 at this point will cause problems with noteretrig if sample has finished playing (i.e. must loop)
               b_used = 0;
            }

            current_block_offset += blkSz;
            numSamples           -= blkSz;
            buf                  += (blkSz << 1); // *2 for stereo
         }

      }

      if(0)
      {
         sUI i;
         for(i=0; i<dbgNumSamples; i++)
         {
            sF32 l,r;
            ////printf("%f, ", dbgBufStart[i*2+0]);
             l = dbgBufStart[i*2+0];
             r = dbgBufStart[i*2+1];
             if(!b_playing)
             {
                dbgBufStart[i*2+0] = 0.0f;
                dbgBufStart[i*2+1] = 0.0f;
             }
             printf("xxx [%d] l=%f r=%f\n", i, l, r);
            // Dyac_dbgflt_32(l);
            // Dyac_dbgflt_32(r);
         }
         ////printf("\n---\n");
      }

   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_buf is not a valid FloatArray");
   }
}

void StSampleVoice::_PrintDebugStats(void) {
#ifdef USE_STATS
   sF32 total = (sF32) (stat_opt_innermix + stat_opt_lastmix + stat_opt_generic);
   yac_host->printf("[dbg] StSampleVoice::drawDebugStats: inner:%u(%f %%) last:%u (%f %%) generic:%u (%f %%)\n", 
                    stat_opt_innermix, 100*(stat_opt_innermix/total),
                    stat_opt_lastmix, 100*(stat_opt_lastmix/total), 
                    stat_opt_generic, 100*(stat_opt_generic/total)
                    );
#endif // USE_STATS
}

void StSampleVoice::_setTimedLoopBase(sF32 _base) {
   sF32 oldBase = timedloop_base;

   if(_base > 0.0f)
   {
      timedloop_base = _base;
   }
   else
   {
      timedloop_base = sample->timedloop_base;
   }

   if(b_timedloop)
   {

      //if(samples_until_end_of_loopstep)
      {
         //samples_until_end_of_loopstep = (sSI) (current_loop_countdown * mix_rate / timedloop_base);
         sUI currentNum = (sUI) (samples_until_end_of_loopstep * (oldBase / mix_rate));
         samples_until_end_of_loopstep = (sSI) (currentNum * mix_rate / timedloop_base);
         ////printf("xxx new samples_until_end_of_loopstep=%d\n", samples_until_end_of_loopstep);
         
         if(fadeout_countdown > 0)
         {
            if(fadeout_countdown > samples_until_end_of_loopstep)
            {
               fadeout_countdown = samples_until_end_of_loopstep;
            }
            fadein_countdown = -1;
         }
         
         if(fadein_countdown > 0)
         {
            if(samples_until_end_of_loopstep < fadein_countdown)
            {
               fadeout_countdown = samples_until_end_of_loopstep;
               fadein_countdown = -1;
               b_fading_out = 1;
            }
         }
      }
   }
}

sF32 StSampleVoice::_getTimedLoopBase(void) {
   return timedloop_base;
}

YAC_Object *StSampleVoice::_getOverrideSampleLoops(void) {
   return override_sample_loops;
}

YAC_Object *StSampleVoice::_getOrCreateOverrideSampleLoops(void) {
   if(NULL == override_sample_loops)
   {
      // LOCK_MTX_RENDER;

      override_sample_loops = YAC_New_IntArray();

      // UNLOCK_MTX_RENDER;
   }
   return override_sample_loops;
}

void StSampleVoice::_allocOverrideSampleLoops(void) {
   _freeOverrideSampleLoops();
   if(NULL == override_sample_loops)
   {
      // LOCK_MTX_RENDER;

      override_sample_loops = YAC_New_IntArray();

      // UNLOCK_MTX_RENDER;
   }
}

void StSampleVoice::_freeOverrideSampleLoops(void) {

   // LOCK_MTX_RENDER;

   YAC_DELETE_SAFE(override_sample_loops);

   // UNLOCK_MTX_RENDER;
}

YAC_IntArray *StSampleVoice::getCurrentSampleLoops(void) {
   YAC_IntArray *ret = NULL;

   if(NULL != override_sample_loops)
   {
      ret = override_sample_loops;
   }

   if(NULL == ret)
   {
      if(NULL != sample)
      {
         if(NULL != sample->sample_loops)
         {
            ret = sample->sample_loops;
         }
      }
   }
   
   return ret;
}

sBool StSampleVoice::_verifyOverrideSampleAreas(void) {
   //
   // - return true if one or more sample areas had to be clipped
   // - return false if sample areas are within waveform boundaries
   // - clipping strategy: first reduce len, then offset

   if(NULL != sample)
   {
      if(NULL != sample->waveform)
      {
         // LOCK_MTX_RENDER;

         sUI range = (sUI) sample->waveform->_getNumFrames();
         
         sBool r = YAC_FALSE;

         // Verify loops
         if(NULL != override_sample_loops)
         {
            sUI numLoops = override_sample_loops->num_elements / 3;
            sUI *loopDat = (sUI*) override_sample_loops->elements;
            sUI i;
            sUI k = 0;
            for(i=0; i<numLoops; i++)
            {
               sUI times = loopDat[k + 2];
               if(!(times & StSample::LOOP_OP_MASK)) // skip loop special instructions
               {
                  r |= StSample::verify_sample_area(loopDat[k + 0], loopDat[k + 1], range); // Verify loop
               }
               k += 3;
            }
         }

         // UNLOCK_MTX_RENDER;

         return r;
      }
      else
      {
         // No waveform selected, nothing to clip
         return 0;
      }
   }
   else
   {
      // No sample selected, nothing to clip
      return 0;
   }
}

void StSampleVoice::_setFilterCutOff(sF32 _f) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._filter_cutoff = _f;
   }
   else
   {
      // Modify voice
      mod_filter_cutoff = _f;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setFilterPan(sF32 _f) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._filter_pan = _f;
   }
   else
   {
      // Modify voice
      mod_filter_pan = _f;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setFilterOffset(sF32 _f) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._filter_offset = _f;
   }
   else
   {
      // Modify voice
      mod_filter_offset = _f;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setFilterResonance(sF32 _f) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._filter_resonance = _f;
   }
   else
   {
      // Modify voice
      mod_filter_resonance = _f;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setGlideSwitch(sBool _bEnable) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._glide_switch = _bEnable;
   }
   else
   {
      // Modify voice
      mod_glide_switch = _bEnable;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_setGlideSpeed(sF32 _speed) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._glide_speed = _speed;
   }
   else
   {
      // Modify voice
      mod_glide_speed = _speed;
   }
   UNLOCK_MTX_MOD;
}

void StSampleVoice::_updatePerfCtlPolyPressure(sF32 _value) {
   LOCK_MTX_MOD;

   if(queued_noteon.b_valid)
   {
      // Voice has not been started, yet
      queued_noteon._perfctl_poly_pressure = _value;
   }
   else
   {
      // Modify voice
      perfctl_poly_pressure = _value;
   }
   UNLOCK_MTX_MOD;
}


void StSampleVoiceBiquad::calcQ(sF32 &freq, sF32 &bandwidth, sF32 &qScale, sF32 q) {
   bandwidth = q;

   sF32 ra = ((rand() &65535)*(1.0f/32768.0f)) - 1.0f; // xxx analogue feel
   bandwidth = bandwidth + (ra*0.05f);

   if(bandwidth < 0)
      bandwidth = 0;
   else if(bandwidth > 1)
      bandwidth = 1;

   qScale = 1.0f;

// #define Q_THRESHOLD 0.95f
// #define Q_FREQ_THRESHOLD 0.05f
//    if( (bandwidth >= Q_THRESHOLD) && (freq <= Q_FREQ_THRESHOLD) )
//    {
//       sF32 t = (bandwidth - Q_THRESHOLD) * (1.0f/(1.0f - Q_THRESHOLD));
//       t *= (Q_FREQ_THRESHOLD - freq) * (1.0f/Q_FREQ_THRESHOLD);
//       t = (t*t * (3.0f - 2.0f*t));
//       qScale *= 1.0f - t;
//       qScale *= qScale;
//       qScale *= qScale;
//       //printf("qScale=%f\n");
//    }
// //    else
// #define Q_FREQ_THRESHOLD2 0.005f
//    if(freq <= Q_FREQ_THRESHOLD2)
//    {
//       sF32 t = (Q_FREQ_THRESHOLD2 - freq) * (1.0f/Q_FREQ_THRESHOLD2);
//       t = (t*t * (3.0f - 2.0f*t));
//       qScale = 1.0f - t;
//       qScale *= qScale;
//       qScale *= qScale;
//    }

// #define Q_THRESHOLD2 0.8f
//    if(bandwidth > Q_THRESHOLD2)
//    {
//       sF32 t = (bandwidth - Q_THRESHOLD2) / (1.0f - Q_THRESHOLD2);
//       t = (t*t * (3.0f - 2.0f*t));
//       qScale *= (1.0f - 0.5f*t);
//    }

   bandwidth = 4.0f * (1.0f - bandwidth) + 0.3f;
}

void StSampleVoiceBiquad::calcCoeff(sSI  type,
                                    sF32 dbGain,   // gain of filter  (0..100, def=2)
                                    sF32 freq,     // center frequency
                                    sF32 srate,    // sampling rate
                                    sF32 bandwidth // bandwidth in octaves
                                    )
{
   // sF32 A;
   sF32 omega, sn, cs, alpha;
   // sF32 beta;
   sF32 a0, a1, a2, b0, b1, b2;

   // yac_host->printf("xxx flt freq=%f\n", freq);

   /* setup variables */
   // A = powf(10, dbGain /40);
   omega = (sF32) (2 * sM_PI * freq /srate);
   sn = sinf(omega);
   cs = cosf(omega);
   alpha = (sF32) (sn * sinh(sM_LN2 /2 * bandwidth * omega /sn));
   // beta = sqrtf(A + A);

   switch (type)
   {
      default:
      case LPF:
         b0 = (1 - cs) /2;
         b1 = 1 - cs;
         b2 = (1 - cs) /2;
         a0 = 1 + alpha;
         a1 = -2 * cs;
         a2 = 1 - alpha;
         break;

      case HPF:
         b0 = (1 + cs) /2;
         b1 = -(1 + cs);
         b2 = (1 + cs) /2;
         a0 = 1 + alpha;
         a1 = -2 * cs;
         a2 = 1 - alpha;
         break;

      case BPF:
         b0 = alpha;
         b1 = 0;
         b2 = -alpha;
         a0 = 1 + alpha;
         a1 = -2 * cs;
         a2 = 1 - alpha;
         break;

      case BRF: //NOTCH:
         b0 = 1;
         b1 = -2 * cs;
         b2 = 1;
         a0 = 1 + alpha;
         a1 = -2 * cs;
         a2 = 1 - alpha;
         break;
         
      // case PEQ:
      //    b0 = 1 + (alpha * A);
      //    b1 = -2 * cs;
      //    b2 = 1 - (alpha * A);
      //    a0 = 1 + (alpha /A);
      //    a1 = -2 * cs;
      //    a2 = 1 - (alpha /A);
      //    break;
         
      // case LSH:
      //    b0 = A * ((A + 1) - (A - 1) * cs + beta * sn);
      //    b1 = 2 * A * ((A - 1) - (A + 1) * cs);
      //    b2 = A * ((A + 1) - (A - 1) * cs - beta * sn);
      //    a0 = (A + 1) + (A - 1) * cs + beta * sn;
      //    a1 = -2 * ((A - 1) + (A + 1) * cs);
      //    a2 = (A + 1) + (A - 1) * cs - beta * sn;
      //    break;
         
      // case HSH:
      //    b0 = A * ((A + 1) + (A - 1) * cs + beta * sn);
      //    b1 = -2 * A * ((A - 1) + (A + 1) * cs);
      //    b2 = A * ((A + 1) + (A - 1) * cs - beta * sn);
      //    a0 = (A + 1) - (A - 1) * cs + beta * sn;
      //    a1 = 2 * ((A - 1) - (A + 1) * cs);
      //    a2 = (A + 1) - (A - 1) * cs - beta * sn;
      //    break;
   }
   
   /* precompute the coefficients */
   biquad.a0 = b0 /a0;
   biquad.a1 = b1 /a0;
   biquad.a2 = b2 /a0;
   biquad.a3 = a1 /a0;
   biquad.a4 = a2 /a0;
}

void StSampleVoiceBiquad::calcParams(sSI _type,
                                     sF32 _dbGain,
                                     sF32 _freq,   // 0..1
                                     sF32 _res,    // 0..1
                                     sF32 _srate
                                     ) {
   // Filter type
   sSI type = _type;
   if(type < 0)
      type = 0;
   else if(type > BRF)
      type = BRF;

   // Cutoff/center frequency
   sF32 freq = _freq;
   //yac_host->printf("xxx freq=%f\n", freq);

   freq = sMIN(1.0f, sMAX(freq, 0.0001f));

#define Dfltfreq(f) (powf(2.0f, (f)*(f)) - 1.0f)
// #define Dfltfreq(f) (powf(2.0f, (f)) - 1.0f)
// #define Dfltfreq(f) (powf(2.0f, (f)) - 1.0f)

// #define Dfltfreq(f) ((log((f)*44100.0) * 1.44269504089/*log(2)*/) / 44100.0)
// #define Dfltfreq(f) ((log((f)*44100.0) * 1.44269504089/*log(2)*/) / 1024.0)
   freq = Dfltfreq(_freq);
#undef Dfltfreq

#if 0
   sF32 ra = ((rand()&65535) * (1.0f/32768.0f)) - 1.0f; // xxx analogue feel
   freq = freq * (1.0f + ra*0.15f);
#endif

   if(freq < 0.0f)
      freq = 0.0f;
   if(freq > 1.0f)
      freq = 1.0f;

   if(freq < 0.0005f)
      freq = 0.0005f;

   // Resonance
   calcQ(freq, bandwidth/*out*/, qScale/*out*/, _res/*q*/);

   // Recalc coefficients
   calcCoeff(type, 
             _dbGain,
             freq * 0.5f * _srate,
             _srate,
             bandwidth
             );
}

sF32 StSampleVoiceBiquad::filter(const sF32 _inSmp) {
   sF32 outSmp = sF32(
      qScale * biquad.a0 * qScale * _inSmp + 
      biquad.a1 * state.x1 + 
      biquad.a2 * state.x2 -
      biquad.a3 * state.y1 - 
      biquad.a4 * state.y2
                 );

#if 1
   if(outSmp < -10.0f) // (todo) is this check really necessary ?
      outSmp = 0.0f;
   else if(outSmp > 10.0f)
      outSmp = 0.0f;
#endif

   outSmp = Dyac_denorm_32(outSmp);

   // Shuffle history
   /* shift x1 to x2, sample to x1 */
   state.x2 = state.x1;
   state.x1 = _inSmp /** qScale*/;
   state.x1 = Dyac_denorm_32(state.x1);
      
   /* shift y1 to y2, result to y1 */
   state.y2 = state.y1;
   state.y1 = outSmp;

   return outSmp;
}



void StSampleVoiceBiquad2::calcParams(sF32 _blkSz,
                                      sSI _type,
                                      sF32 _dbGain,
                                      sF32 _freq,   // 0..1
                                      sF32 _res     // 0..1
                                      ) {
#define Q _res
#define Fc _freq

   cur_a0 = a0;
   cur_a1 = a1;
   cur_a2 = a2;
   cur_b1 = b1;
   cur_b2 = b2;

   // (note) sound dies after a short while with fmax=1.0
   _freq = sMIN(0.992f, sMAX(_freq, 0.001f));

   // (note) sound becomes very quiet with res < 0.5
   _res += 0.5f;
   if(_res < 0.5f)
      _res = 0.5f;
   else if(_res > 1.5f)
      _res = 1.5f;

   sF64 norm;
   sF64 V = pow(10, fabs(_dbGain) / 20.0);  // PEQ, LSH, HSH
   sF64 K = tan(sM_PI * Fc);
   switch(_type)
   {
      case LPF:
         norm = 1 / (1 + K / Q + K * K);
         a0 = K * K * norm;
         a1 = 2 * a0;
         a2 = a0;
         b1 = 2 * (K * K - 1) * norm;
         b2 = (1 - K / Q + K * K) * norm;
         break;
            
      case HPF:
         norm = 1 / (1 + K / Q + K * K);
         a0 = 1 * norm;
         a1 = -2 * a0;
         a2 = a0;
         b1 = 2 * (K * K - 1) * norm;
         b2 = (1 - K / Q + K * K) * norm;
         break;
            
      case BPF:
         norm = 1 / (1 + K / Q + K * K);
         a0 = K / Q * norm;
         a1 = 0;
         a2 = -a0;
         b1 = 2 * (K * K - 1) * norm;
         b2 = (1 - K / Q + K * K) * norm;
         break;
            
      case BRF:
         norm = 1 / (1 + K / Q + K * K);
         a0 = (1 + K * K) * norm;
         a1 = 2 * (K * K - 1) * norm;
         a2 = a0;
         b1 = a1;
         b2 = (1 - K / Q + K * K) * norm;
         break;
            
      case PEQ:
         if(_dbGain >= 0)
         {
            // boost
            norm = 1 / (1 + 1/Q * K + K * K);
            a0 = (1 + V/Q * K + K * K) * norm;
            a1 = 2 * (K * K - 1) * norm;
            a2 = (1 - V/Q * K + K * K) * norm;
            b1 = a1;
            b2 = (1 - 1/Q * K + K * K) * norm;
         }
         else
         {
            // cut
            norm = 1 / (1 + V/Q * K + K * K);
            a0 = (1 + 1/Q * K + K * K) * norm;
            a1 = 2 * (K * K - 1) * norm;
            a2 = (1 - 1/Q * K + K * K) * norm;
            b1 = a1;
            b2 = (1 - V/Q * K + K * K) * norm;
         }
         break;

      case LSH:
         if(_dbGain >= 0)
         {
            // boost
            norm = 1 / (1 + sqrt(2) * K + K * K);
            a0 = (1 + sqrt(2*V) * K + V * K * K) * norm;
            a1 = 2 * (V * K * K - 1) * norm;
            a2 = (1 - sqrt(2*V) * K + V * K * K) * norm;
            b1 = 2 * (K * K - 1) * norm;
            b2 = (1 - sqrt(2) * K + K * K) * norm;
         }
         else
         {
            // cut
            norm = 1 / (1 + sqrt(2*V) * K + V * K * K);
            a0 = (1 + sqrt(2) * K + K * K) * norm;
            a1 = 2 * (K * K - 1) * norm;
            a2 = (1 - sqrt(2) * K + K * K) * norm;
            b1 = 2 * (V * K * K - 1) * norm;
            b2 = (1 - sqrt(2*V) * K + V * K * K) * norm;
         }
         break;

      case HSH:
         if(_dbGain >= 0)
         {
            // boost
            norm = 1 / (1 + sqrt(2) * K + K * K);
            a0 = (V + sqrt(2*V) * K + K * K) * norm;
            a1 = 2 * (K * K - V) * norm;
            a2 = (V - sqrt(2*V) * K + K * K) * norm;
            b1 = 2 * (K * K - 1) * norm;
            b2 = (1 - sqrt(2) * K + K * K) * norm;
         }
         else
         {
            // cut
            norm = 1 / (V + sqrt(2*V) * K + K * K);
            a0 = (1 + sqrt(2) * K + K * K) * norm;
            a1 = 2 * (K * K - 1) * norm;
            a2 = (1 - sqrt(2) * K + K * K) * norm;
            b1 = 2 * (K * K - V) * norm;
            b2 = (V - sqrt(2*V) * K + K * K) * norm;
         }
         break;
   }
#undef Q
#undef Fc

   step_a0 = (a0 - cur_a0) / _blkSz;
   step_a1 = (a1 - cur_a1) / _blkSz;
   step_a2 = (a2 - cur_a2) / _blkSz;
   step_b1 = (b1 - cur_b1) / _blkSz;
   step_b2 = (b2 - cur_b2) / _blkSz;
}

sF32 StSampleVoiceBiquad2::filter(const sF32 _inSmp) {
   sF64 out = _inSmp * cur_a0 + z1;
   z1 = _inSmp * cur_a1 + z2 - cur_b1 * out;
   z2 = _inSmp * cur_a2 - cur_b2 * out;

   cur_a0 += step_a0;
   cur_a1 += step_a1;
   cur_a2 += step_a2;
   cur_b1 += step_b1;
   cur_b2 += step_b2;

   sF32 out32 = sF32(out);
   return Dyac_denorm_32(out32);
}
