// ----
// ---- file   : StSample.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, 21Feb2010, 01Mar2010, 29Apr2010, 02May2010
// ----          07Jul2010, 18Jul2010, 04Sep2010, 05Mar2011, 09Mar2011, 14Mar2011, 20Mar2011
// ----          28Mar2011, 08May2013, 09May2013, 23Dec2018, 27Dec2018, 30Dec2018, 31Dec2018
// ----          02Jan2019, 04Jan2019, 07Jan2019, 08Jan2019, 09Jan2019, 30Mar2019, 31Mar2019
// ----          11May2019, 15May2019
// ----
// ----
// ----

#include "tksampler.h"

#define MAX_VOLRAMP_STEPS 128


StSample::StSample(void) {
   next        = NULL;
   name        = NULL;
   waveform    = NULL;
   mutex_group = NULL;

   b_own_waveform = YAC_FALSE;

   parent_samplebank = NULL;

   key_range = NULL;
   vel_range = NULL;
   mod_range = NULL;
   b_skip_range = YAC_FALSE;

   max_voices = STSAMPLE_DEFAULT_MAXVOICES;

   b_enabled = 1;

   sample_delay  = 0.0f;
   sample_offset = 0;
   sample_len    = 0;
   sample_loops  = NULL;

   first_loop_index = 0;
   ui_sync_first_loop_index = 0;

   noteoff_loop_index            = -1;
   b_noteoff_immediate_loop_jump = 0;

   b_timedloop = 0;
   b_timedloop_fade = 1;
   b_oneshot_loopstep = 0;
   timedloop_base = STSAMPLE_DEFAULT_TIMEDLOOP_BASE;

   volume      = 0.0f;
   vol_lfo_amt = 1.0f;
   pan         = 0.0f;
   pan_env_amt = 1.0f;
   pan_lfo_amt = 1.0f;
   transpose = 0.0f;
   ui_transpose_oct = 0;
   ui_transpose_semi = 0;
   ui_transpose_cents = 0;
   freq_env_amt = 1.0f;
   freq_lfo_amt = 1.0f;

   beat_frequency = 0.0f;

   volume_velocity_amount       = 0.0f;
   sampleoffset_velocity_amount = 0.0f;
   b_invert_sampleoffset_velocity_amount = YAC_TRUE;
   sampleoffset_rand_amount     = 0.0f;
   sampleoffset_rel             = 0.0f;
   
   b_timestretch                  = 0;
   b_timestretch_xfade            = YAC_TRUE;
   timestretch                    = 0.0f;
   timestretch_granularity        = 128.0f;
   timestretch_granularity_amount = 0.0f;
   b_static_cyclelen              = YAC_TRUE;
   b_absolute_timestretch         = 0;
   timestretch_interpol_type      = STSAMPLE_TIMESTRETCH_INTERPOL_LINEAR;
   timestretch_bend               = 0.0f;

   b_waveguide                    = 0;
   waveguide_table                = NULL;
   waveguide_idx                  = 0;

   timestretch_startphase_rand_amount = 0.0f;

   adsr_freq = NULL;
   adsr_vol  = NULL;
   adsr_pan  = NULL;
   adsr_aux = NULL;

   lfo_freq = NULL;
   lfo_vol  = NULL;
   lfo_pan  = NULL;
   lfo_aux  = NULL;

   num_volramp_steps_in  = 1;
   num_volramp_steps_out = 4;

   b_freeze_sampleoff_when_quiet = 0;

   b_modshiftorig = 0;

   b_sampleoffnoteon = 0;

   interpol_type = STSAMPLE_INTERPOL_LINEAR;

   alt.b_enable = YAC_FALSE;
   alt.hdr = NULL;
   alt.num = 0u;

   filter_type           = STSAMPLE_FLT_TYPE_NONE;
   filter_cutoff         = 0.503937007874f;  // 64 in range 0..127
   filter_pan            = 0.0f;
   filter_offset         = 0.0f;
   filter_eq_gain        = 0.0f;
   filter_resonance      = 0.0f;
   filter_aux_env_amount = 0.0f;
   filter_aux_env_velocity_amount = 0.0;
   filter_aux_lfo_amount = 0.0f;
   filter_keyboard_amount= 0.5f;

   glide_type       = STSAMPLE_GLIDE_NONE;
   glide_speed_time_up = glide_speed_time_down =  1.0f / 90.0f;    // 90ms
   glide_speed_freq_up = glide_speed_freq_down = 24.0f / 1000.0f;  // two octaves / second
   b_glide_release     = YAC_TRUE;
   glide_max_note_dist = 127;
   b_glide_glissando   = YAC_FALSE;
   glide_min_time      = 50u;
   b_glide_retrig_sample    = YAC_TRUE;
   b_glide_retrig_env_freq  = YAC_TRUE;
   b_glide_retrig_env_vol   = YAC_TRUE;
   b_glide_retrig_env_pan   = YAC_TRUE;
   b_glide_retrig_env_aux   = YAC_TRUE;
   b_glide_retrig_lfo_freq  = YAC_TRUE;
   b_glide_retrig_lfo_vol   = YAC_TRUE;
   b_glide_retrig_lfo_pan   = YAC_TRUE;
   b_glide_retrig_lfo_aux   = YAC_TRUE;

   ::memset((void*)modmatrix, 0, sizeof(modmatrix));
}

StSample::~StSample() {
   YAC_DELETE_SAFE(name);
   YAC_DELETE_SAFE(sample_loops);
   YAC_DELETE_SAFE(adsr_freq);
   YAC_DELETE_SAFE(adsr_vol);
   YAC_DELETE_SAFE(adsr_pan);
   YAC_DELETE_SAFE(adsr_aux);
   YAC_DELETE_SAFE(lfo_freq);
   YAC_DELETE_SAFE(lfo_vol);
   YAC_DELETE_SAFE(lfo_pan);
   YAC_DELETE_SAFE(lfo_aux);
   YAC_DELETE_SAFE(key_range);
   YAC_DELETE_SAFE(vel_range);
   YAC_DELETE_SAFE(mod_range);
   YAC_DELETE_SAFE(waveguide_table);

   if((NULL != waveform) && b_own_waveform)
   {
      YAC_DELETE_SAFE(waveform);
   }

   next = NULL;
}

YAC_Object *StSample::_getNext(void) {
   return next;
}

void StSample::_setNext(YAC_Object *_next) {

   LOCK_MTX_RENDER;

   if(YAC_CHK(_next, clid_StSample))
   {
      next = (StSample*) _next;
   }
   else
   {
      next = NULL;
   }

   UNLOCK_MTX_RENDER;
}

YAC_Object *StSample::_getName(void) {
   return name;
}

void StSample::_setName(YAC_Object *_str) {
   if(NULL == name)
   {
      name = YAC_New_String();
   }
   name->yacCopy(_str);
}

YAC_Object *StSample::_getWaveform (void) {
   return waveform;
}

void StSample::_setWaveform(YAC_Object *_wav) {

   LOCK_MTX_RENDER;

   YAC_DEREF_ARG(StWaveform, wav, _wav);

   if(YAC_CHK(wav, clid_StWaveform))
   {
      if((void*)waveform != (void*)wav)
      {
         if((NULL != waveform) && b_own_waveform)
         {
            YAC_DELETE_SAFE(waveform);
         }
         waveform = (StWaveform*) wav;
         b_own_waveform = wav_deleteme;
      }
   }
   else
   {
      if((NULL != waveform) && b_own_waveform)
      {
         YAC_DELETE_SAFE(waveform);
      }
      waveform = NULL;
      b_own_waveform = YAC_FALSE;
      if(wav_deleteme)
         YAC_DELETE(wav);
   }

   UNLOCK_MTX_RENDER;
}

sBool StSample::verify_sample_area(sUI &offset, sUI &len, const sUI range) {

   if( (offset + len) < offset)
   {
      // 32bit wrap-around / overflow (should never happen)
      offset = 0;
      len    = range;

      return 1;
   }
   if( (offset + len) > range )
   {
      if(offset >= range)
      {
         if(range > 0)
         {
            offset = range - 1;
         }
         else
         {
            offset = 0;
         }
      }
      len = range - offset;

      ////printf("xxx verify_sample_area: new offset=%u len=%u (range=%u)\n", offset, len, range);

      return 1;
   }

   return 0;
}

static void shift_sample_area(sUI &offset, sUI &len, sUI _areaOffset, sSI _areaLen) {
   // (note) negative _areaLen means that samples have been deleted

   sSI newOff = (sSI) offset;
   sSI newLen = (sSI) len;

   sSI ao = sSI(_areaOffset);
   sSI al = -_areaLen;
   sSI to = sSI(offset);
   sSI tl = sSI(len);

   // yac_host->printf("xxx shift_sample_area: oldOff=%u oldLen=%u areaOff=%u areaLen=%d\n", offset, len, _areaOffset, _areaLen);

   if(offset >= _areaOffset)
   {
      if(_areaLen < 0)
      {
         if((ao+al) >= to)
         {
            if((ao+al) >= (to+tl))
            {
               // Cut entire sample area
               // yac_host->printf("xxx cut entire sample area\n");
               newOff = 0;
               newLen = 0;
            }
            else
            {
               // Cut area at sample start
               yac_host->printf("xxx cut area at sample start\n");
               newOff -= (to - ao);
               if(newOff < 0)
                  newOff = 0;
               newLen -= ((ao+al)-to);
            }
         }
         else
         {
            // Cut before sample area
            newOff += _areaLen;
            if(newOff < 0)
               newOff = 0;
         }
      }
      else
      {
         // Paste before sample area
         newOff += _areaLen;
         if(newOff < 0)
            newOff = 0;
      }
   }
   else if(_areaLen < 0)
   {
      if( (ao+al) <= (to+tl) )
      {
         // Cut within sample area
         newLen -= al;
      }
      else if( ao <= (to+tl) )
      {
         // Cut crosses sample end
         newLen -= ((to+tl) - ao);
      }
   }
   else
   {
      if( (ao) < (to+tl) )
      {
         // Paste within sample area
         newLen += _areaLen;
      }
      else
      {
         // Paste after sample start
      }
   }

   // yac_host->printf("xxx shift_sample_area: oldOff=%u newOff=%d areaOff=%u areaLen=%d\n", offset, newOff, _areaOffset, _areaLen);
   if(newLen < 0)
      newLen = 0; // should not be reachable

   offset = (sUI) newOff;
   len    = (sUI) newLen;

   // yac_host->printf("xxx shift_sample_area: => newOff=%u newLen=%u\n", offset, len);
}

void StSample::_handleShiftSampleArea(sUI _offset, sSI _len) {

   // (note) no need to clip exact since verifySampleArea() is always called afterwards

   if(NULL != waveform)
   {
      LOCK_MTX_RENDER;

      // sUI range = (sUI) waveform->_getNumFrames();

      // Shift attack/oneshot sample range
      shift_sample_area(sample_offset, sample_len, _offset, _len);

      // Shift loops
      if(NULL != sample_loops)
      {
         sUI numLoops = sample_loops->num_elements / 3;
         sUI *loopDat = (sUI*) 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
            {
               shift_sample_area(loopDat[k + 0], loopDat[k + 1], _offset, _len);
            }
            k += 3;
         }
      }

      UNLOCK_MTX_RENDER;

   }
   else
   {
      // No waveform selected, nothing to clip
   }
}

static void move_sample_area(sUI &offset, sUI &len, sUI _areaOffset, sUI _areaLen, sUI _newAreaOffset) {

   if( (offset >= _areaOffset) && ((offset + len) <= (_areaOffset + _areaLen)) )
   {
      sSI relOffset = (sSI(offset) - sSI(_areaOffset));
      offset = sUI(sSI(_newAreaOffset) + relOffset);
   }
}

void StSample::_handleMoveSampleArea(sUI _offset, sUI _len, sUI _newOffset) {
   if(NULL != waveform)
   {
      LOCK_MTX_RENDER;

      // sUI range = (sUI) waveform->_getNumFrames();

      // Move attack/oneshot sample range
      move_sample_area(sample_offset, sample_len, _offset, _len, _newOffset);

      // Move loops
      if(NULL != sample_loops)
      {
         sUI numLoops = sample_loops->num_elements / 3;
         sUI *loopDat = (sUI*) 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
            {
               move_sample_area(loopDat[k + 0], loopDat[k + 1], _offset, _len, _newOffset);
            }
            k += 3;
         }
      }

      UNLOCK_MTX_RENDER;

   }
   else
   {
      // No waveform selected, nothing to clip
   }
}

sBool StSample::_verifySampleAreas(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 != waveform)
   {
      LOCK_MTX_RENDER;

      sUI range = (sUI) waveform->_getNumFrames();

      // Verify attack/oneshot sample range
      sBool r = verify_sample_area(sample_offset, sample_len, range);

      // Verify loops
      if(NULL != sample_loops)
      {
         sUI numLoops = sample_loops->num_elements / 3;
         sUI *loopDat = (sUI*) 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 |= 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;
   }
}


YAC_Object *StSample::_getMutexGroup(void) {
   return mutex_group;
}

void StSample::_setMutexGroup(YAC_Object *_mtxGrp) {
   LOCK_MTX_RENDER;

   if(YAC_CHK(_mtxGrp, clid_StSampleMutexGroup))
   {
      mutex_group = (StSampleMutexGroup*) _mtxGrp;
   }
   else
   {
      mutex_group = NULL;
   }

   UNLOCK_MTX_RENDER;
}

void StSample::_setMaxVoices(sUI _maxVoices) {

   LOCK_MTX_RENDER;

   max_voices = _maxVoices;

   UNLOCK_MTX_RENDER;
}

sUI StSample::_getMaxVoices(void) {
   return max_voices;
}

YAC_Object *StSample::_getOrCreateKeyRange(void) {
   if(NULL == key_range)
   {
      LOCK_MTX_RENDER;

      key_range = YAC_NEW(StRange);
      key_range->b_high_inclusive = YAC_TRUE;

      UNLOCK_MTX_RENDER;
   }
   return key_range;
}

YAC_Object *StSample::_getOrCreateVelRange(void) {
   if(NULL == vel_range)
   {
      LOCK_MTX_RENDER;

      vel_range = YAC_NEW(StRange);

      UNLOCK_MTX_RENDER;
   }
   return vel_range;
}

YAC_Object *StSample::_getOrCreateModRange(void) {
   if(NULL == mod_range)
   {
      LOCK_MTX_RENDER;

      mod_range = YAC_NEW(StRange);

      UNLOCK_MTX_RENDER;
   }
   return mod_range;
}

YAC_Object *StSample::_getKeyRange(void) {
   return key_range;
}

YAC_Object *StSample::_getVelRange(void) {
   return vel_range;
}

YAC_Object *StSample::_getModRange(void) {
   return mod_range;
}

YAC_Object *StSample::_getOrCreateFreqADSR(void) {
   if(NULL == adsr_freq)
   {
      LOCK_MTX_RENDER;

      adsr_freq = YAC_NEW(StADSR);

      UNLOCK_MTX_RENDER;
   }
   return adsr_freq;
}

YAC_Object *StSample::_getOrCreateVolADSR(void) {
   if(NULL == adsr_vol)
   {
      LOCK_MTX_RENDER;

      adsr_vol = YAC_NEW(StADSR);

      UNLOCK_MTX_RENDER;
   }
   return adsr_vol;
}

YAC_Object *StSample::_getOrCreatePanADSR(void) {
   if(NULL == adsr_pan)
   {
      LOCK_MTX_RENDER;

      adsr_pan = YAC_NEW(StADSR);

      UNLOCK_MTX_RENDER;
   }
   return adsr_pan;
}

YAC_Object *StSample::_getOrCreateAuxADSR(void) {
   if(NULL == adsr_aux)
   {
      LOCK_MTX_RENDER;

      adsr_aux = YAC_NEW(StADSR);

      UNLOCK_MTX_RENDER;
   }
   return adsr_aux;
}

YAC_Object *StSample::_getFreqADSR(void) {
   return adsr_freq;
}

YAC_Object *StSample::_getVolADSR(void) {
   return adsr_vol;
}

YAC_Object *StSample::_getPanADSR(void) {
   return adsr_pan;
}

YAC_Object *StSample::_getAuxADSR(void) {
   return adsr_aux;
}

YAC_Object *StSample::_getOrCreateFreqLFO(void) {
   if(NULL == lfo_freq)
   {
      LOCK_MTX_RENDER;

      lfo_freq = YAC_NEW(StLFO);

      UNLOCK_MTX_RENDER;
   }
   return lfo_freq;
}

YAC_Object *StSample::_getOrCreateVolLFO(void) {
   if(NULL == lfo_vol)
   {
      LOCK_MTX_RENDER;

      lfo_vol = YAC_NEW(StLFO);

      UNLOCK_MTX_RENDER;
   }
   return lfo_vol;
}

YAC_Object *StSample::_getOrCreatePanLFO(void) {
   if(NULL == lfo_pan)
   {
      LOCK_MTX_RENDER;

      lfo_pan = YAC_NEW(StLFO);

      UNLOCK_MTX_RENDER;
   }
   return lfo_pan;
}

YAC_Object *StSample::_getOrCreateAuxLFO(void) {
   if(NULL == lfo_aux)
   {
      LOCK_MTX_RENDER;

      lfo_aux = YAC_NEW(StLFO);

      UNLOCK_MTX_RENDER;
   }
   return lfo_aux;
}

void StSample::_deleteFreqLFO(void) {
   YAC_DELETE_SAFE(lfo_freq);
}

void StSample::_deleteVolLFO(void) {
   YAC_DELETE_SAFE(lfo_vol);
}

void StSample::_deletePanLFO(void) {
   YAC_DELETE_SAFE(lfo_pan);
}

void StSample::_deleteAuxLFO(void) {
   YAC_DELETE_SAFE(lfo_aux);
}

YAC_Object *StSample::_getFreqLFO(void) {
   return lfo_freq;
}

YAC_Object *StSample::_getVolLFO(void) {
   return lfo_vol;
}

YAC_Object *StSample::_getPanLFO(void) {
   return lfo_pan;
}

YAC_Object *StSample::_getAuxLFO(void) {
   return lfo_aux;
}

YAC_Object *StSample::_getLFOByIndex(sUI _idx) {
   switch(_idx)
   {
      default:
         return NULL;

      case 0:
         return lfo_freq;

      case 1:
         return lfo_vol;

      case 2:
         return lfo_pan;

      case 3:
         return lfo_aux;
   }
   return NULL;
}

YAC_Object *StSample::_getOrCreateLFOByIndex(sUI _idx) {
   switch(_idx)
   {
      default:
         return NULL;

      case 0:
         return _getOrCreateFreqLFO();

      case 1:
         return _getOrCreateVolLFO();

      case 2:
         return _getOrCreatePanLFO();

      case 3:
         return _getOrCreateAuxLFO();
   }
   return NULL;
}

YAC_Object *StSample::_getSampleLoops(void) {
   return sample_loops;
}

YAC_Object *StSample::_getOrCreateSampleLoops(void) {
   if(NULL == sample_loops)
   {
      LOCK_MTX_RENDER;

      sample_loops = YAC_New_IntArray();

      UNLOCK_MTX_RENDER;
   }
   return sample_loops;
}

void StSample::_allocSampleLoops(void) {
   _freeSampleLoops();
   if(NULL == sample_loops)
   {
      LOCK_MTX_RENDER;

      sample_loops = YAC_New_IntArray();

      UNLOCK_MTX_RENDER;
   }
}

void StSample::_freeSampleLoops(void) {

   LOCK_MTX_RENDER;

   YAC_DELETE_SAFE(sample_loops);

   UNLOCK_MTX_RENDER;
}

sUI StSample::_getNumSampleLoops(void) {
   if(NULL != sample_loops)
   {
      return sample_loops->num_elements / 3;
   }
   return 0;
}

void StSample::_setFirstLoopIndex(sSI _loopIdx) {
   first_loop_index = _loopIdx;
}

sSI StSample::_getFirstLoopIndex(void) {
   return first_loop_index;
}

void StSample::_uiSetEnableSyncFirstLoopIndex(sBool _bEnabled) {
   ui_sync_first_loop_index = _bEnabled;
}

sBool StSample::_uiGetEnableSyncFirstLoopIndex(void) {
   return ui_sync_first_loop_index;
}

void StSample::_setNoteOffLoopIndex(sSI _loopIdx) {

   LOCK_MTX_RENDER;

   noteoff_loop_index = _loopIdx;

   UNLOCK_MTX_RENDER;
}

sSI  StSample::_getNoteOffLoopIndex(void) {
   return noteoff_loop_index;
}

void StSample::_setEnableNoteOffLoopImmediately(sSI _bEnabled) {
   LOCK_MTX_RENDER;

   b_noteoff_immediate_loop_jump = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI  StSample::_getEnableNoteOffLoopImmediately(void) {
   return b_noteoff_immediate_loop_jump;
}

sSI  StSample::_getFirstOffset(void) {
   sSI r = 0;

   if(NULL != sample_loops)
   {
      if(sample_loops->num_elements >= 3)
      {
         r = sample_loops->elements[0];      
      }
   }

   return r;
}

void StSample::_setFirstOffset(sSI _off) {
   (void)_getOrCreateSampleLoops();

   LOCK_MTX_RENDER;

   // yac_host->printf("xxx StSample::setFirstOffset: sample_loops=%p\n", sample_loops);
   // yac_host->printf("xxx StSample::setFirstOffset: num_elements=%u\n", sample_loops->num_elements);

   if(0u == sample_loops->num_elements)
   {
      sample_loops->yacArrayRealloc(3);
      sample_loops->elements[1] = 0;  // len
      sample_loops->elements[2] = 1;  // rep
      sample_loops->num_elements = 3;
   }
   // yac_host->printf("xxx StSample::setFirstOffset: 2 sample_loops=%p\n", sample_loops);
   // yac_host->printf("xxx StSample::setFirstOffset: 3 num_elements=%u\n", sample_loops->num_elements);
   // yac_host->printf("xxx StSample::setFirstOffset: 4 elements=%p\n", sample_loops->elements);
   sample_loops->elements[0] = _off;

   UNLOCK_MTX_RENDER;
}

sSI  StSample::_getFirstLen(void) {
   sSI r = 0;

   if(NULL != sample_loops)
   {
      if(sample_loops->num_elements >= 3)
      {
         r = sample_loops->elements[1];      
      }
   }

   return r;
}

void StSample::_setFirstLen(sSI _len) {
   (void)_getOrCreateSampleLoops();

   LOCK_MTX_RENDER;

   if(0 == sample_loops->num_elements)
   {
      sample_loops->yacArrayRealloc(3);
      sample_loops->num_elements = 3;
      sample_loops->elements[0] = 0;  // off
      sample_loops->elements[2] = 0;  // len
   }
   sample_loops->elements[1] = _len;

   UNLOCK_MTX_RENDER;
}

sSI  StSample::_getFirstLoopOffset(void) {
   sSI r = 0;

   if(NULL != sample_loops)
   {
      if(sample_loops->num_elements >= (3*sUI(first_loop_index+1)))
      {
         r = sample_loops->elements[3*first_loop_index + 0];
      }
   }

   return r;
}

void StSample::_setFirstLoopOffset(sSI _off) {
   (void)_getOrCreateSampleLoops();

   if(sample_loops->num_elements >= (3*sUI(first_loop_index+1)))
   {
      LOCK_MTX_RENDER;

      sample_loops->elements[3*first_loop_index + 0] = _off;      

      UNLOCK_MTX_RENDER;
   }
}

sSI  StSample::_getFirstLoopLen(void) {
   sSI r = 0;

   if(NULL != sample_loops)
   {
      if(sample_loops->num_elements >= (3*sUI(first_loop_index+1)))
      {
         r = sample_loops->elements[3*first_loop_index + 1];
      }
   }

   return r;
}

void StSample::_setFirstLoopLen(sSI _len) {
   (void)_getOrCreateSampleLoops();

   if(sample_loops->num_elements >= (3*sUI(first_loop_index+1)))
   {
      LOCK_MTX_RENDER;

      sample_loops->elements[3*first_loop_index + 1] = _len;      

      UNLOCK_MTX_RENDER;
   }
}

sSI  StSample::_getFirstLoopRep(void) {
   sSI r = 0;

   if(NULL != sample_loops)
   {
      if(sample_loops->num_elements >= (3*sUI(first_loop_index+1)))
      {
         r = sample_loops->elements[3*first_loop_index + 2];
      }
   }

   return r;
}

void StSample::_setFirstLoopRep(sSI _num) {
   (void)_getOrCreateSampleLoops();

   if(sample_loops->num_elements >= (3*sUI(first_loop_index+1)))
   {
      LOCK_MTX_RENDER;

      sample_loops->elements[3*first_loop_index + 2] = _num;      

      UNLOCK_MTX_RENDER;
   }
}

sSI  StSample::_getOffset(void) {
   return (sSI) sample_offset;
}

void StSample::_setOffset(sSI _off) {

   LOCK_MTX_RENDER;

   sample_offset = (sUI) _off;

   UNLOCK_MTX_RENDER;
}

sSI  StSample::_getLen(void) {
   return (sSI) sample_len;
}

void StSample::_setLen(sSI _len) {

   LOCK_MTX_RENDER;

   sample_len = (sUI) _len;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getVolume(void) {
   return volume;
}

void StSample::_setVolume(sF32 _vol) {
   LOCK_MTX_RENDER;

   volume = _vol;

   UNLOCK_MTX_RENDER;
}

void StSample::_setVolLFOAmt(sF32 _amt) {
   LOCK_MTX_RENDER;

   vol_lfo_amt = _amt;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getVolLFOAmt(void) {
   return vol_lfo_amt;
}

sF32 StSample::_getPan(void) {
   return pan;
}

void StSample::_setPan(sF32 _pan) {
   LOCK_MTX_RENDER;

   pan = _pan;

   UNLOCK_MTX_RENDER;
}

void StSample::_setPanEnvAmt(sF32 _amt) {
   LOCK_MTX_RENDER;

   pan_env_amt = _amt;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getPanEnvAmt(void) {
   return pan_env_amt;
}

void StSample::_setPanLFOAmt(sF32 _amt) {
   LOCK_MTX_RENDER;

   pan_lfo_amt = _amt;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getPanLFOAmt(void) {
   return pan_lfo_amt;
}

sF32 StSample::_getVolumeVelocityAmount(void) {
   return volume_velocity_amount;
}

void StSample::_setVolumeVelocityAmount(sF32 _a) {

   LOCK_MTX_RENDER;

   volume_velocity_amount = _a;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getSampleOffsetVelocityAmount(void) {
   return sampleoffset_velocity_amount;
}

void StSample::_setSampleOffsetVelocityAmount(sF32 _a) {

   LOCK_MTX_RENDER;

   sampleoffset_velocity_amount = _a;

   UNLOCK_MTX_RENDER;
}

sSI  StSample::_getEnableInvertSampleOffsetVelocityAmount(void) {
   return b_invert_sampleoffset_velocity_amount;
}

void StSample::_setEnableInvertSampleOffsetVelocityAmount(sSI _bEnable) {

   LOCK_MTX_RENDER;

   b_invert_sampleoffset_velocity_amount = _bEnable;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getSampleOffsetRandAmount(void) {
   return sampleoffset_rand_amount;
}

void StSample::_setSampleOffsetRandAmount(sF32 _a) {

   LOCK_MTX_RENDER;

   sampleoffset_rand_amount = _a;

   UNLOCK_MTX_RENDER;
}

void StSample::_setSampleOffsetRel(sF32 _pct) {
   sampleoffset_rel = _pct;
}

sF32 StSample::_getSampleOffsetRel(void) {
   return sampleoffset_rel;
}

sF32 StSample::_getTranspose(void) {
   return transpose;
}

void StSample::_setTranspose(sF32 _transpose) {

   LOCK_MTX_RENDER;

   transpose = _transpose;

   UNLOCK_MTX_RENDER;
}

void StSample::updateTranspose(void) {
   _setTranspose(ui_transpose_oct * 12 + ui_transpose_semi + ui_transpose_cents/100.0f);
}

void StSample::_setUiTransposeOct(sSI _oct) {
   ui_transpose_oct = sS8(_oct);
   updateTranspose();
}

sSI StSample::_getUiTransposeOct(void) {
   return ui_transpose_oct;
}

void StSample::_setUiTransposeSemi(sSI _semi) {
   ui_transpose_semi = sS8(_semi);
   updateTranspose();
}

sSI StSample::_getUiTransposeSemi(void) {
   return ui_transpose_semi;
}

void StSample::_setUiTransposeCents(sF32 _cents) {
   ui_transpose_cents = _cents;
   updateTranspose();
}

sF32 StSample::_getUiTransposeCents(void) {
   return ui_transpose_cents;
}

void StSample::_setTransposeAndUpdateOctSemi(sSI _key) {
   ui_transpose_oct = sS8(_key / 12);
   ui_transpose_semi = sS8(_key - (ui_transpose_oct * 12));
   updateTranspose();
}

void StSample::_setBeatFrequency(sF32 _cents) {
   beat_frequency = _cents;
}

sF32 StSample::_getBeatFrequency(void) {
   return beat_frequency;
}

void StSample::_setFreqEnvAmt(sF32 _amt) {
   LOCK_MTX_RENDER;

   freq_env_amt = _amt;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getFreqEnvAmt(void) {
   return freq_env_amt;
}

void StSample::_setFreqLFOAmt(sF32 _amt) {
   LOCK_MTX_RENDER;

   freq_lfo_amt = _amt;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getFreqLFOAmt(void) {
   return freq_lfo_amt;
}

sF32 StSample::_getDelay (void) {
   return sample_delay;
}

void StSample::_setDelay (sF32 _dly) {

   LOCK_MTX_RENDER;

   sample_delay = _dly;

   UNLOCK_MTX_RENDER;
}

sBool StSample::filterNoteOn(sSI _note, sF32 _vel, sF32 _mod) {
   sBool r = 1;

   // yac_host->printf("xxx filterNoteOn: mod=%f b_skip_range=%d\n", _mod, b_skip_range);

   if(!b_skip_range)
   {
      if(NULL != key_range)
      {
         r = r && key_range->filterNoteOn((sF32)_note);
      }

      if(NULL != vel_range)
      {
         r = r && vel_range->filterNoteOn(_vel);
      }

      if(NULL != mod_range)
      {
         r = r && mod_range->filterNoteOn(_mod);
      }
   }
   return r;
}

sF32 StSample::calcVelZoneVolume(sF32 _vel) {
   sF32 v;
   
   v = (1.0f - volume_velocity_amount) + (_vel * volume_velocity_amount);
   ////printf("xxx v=%f vel_range=%p\n", v, vel_range);

   if(vel_range && !b_skip_range)
   {
      v = v * vel_range->calcAmount(_vel);
   }

   if(v > 1.0f)
   {
      v = 1.0f;
   }
   else if(v < 0.0f)
   {
      v = 0.0f;
   }
   
   return v;
}

sF32 StSample::calcKeyModZoneVolume(sF32 _freq, sF32 _mod) {
   sF32 v = 1.0f;

   if(key_range && !b_skip_range)
   {
      // yac_host->printf("xxx calcKeyModZoneVolume: freq=%f v=%f\n", _freq, v);
      v = v * key_range->calcAmount(_freq);
   }

   if(mod_range && !b_skip_range)
   {
      v = v * mod_range->calcAmount(_mod);
   }

   return v;
}

void StSample::setEnableTimestretch(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_timestretch = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::getEnableTimestretch(void) {
   return b_timestretch;
}

void StSample::setEnableTimestretchXFade(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_timestretch_xfade = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::getEnableTimestretchXFade(void) {
   return b_timestretch_xfade;
}

void StSample::setTimestretch(sF32 _timestretch) {

   LOCK_MTX_RENDER;

   timestretch = _timestretch;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::getTimestretch(void) {
   return timestretch;
}

void StSample::setTimestretchGranularity(sF32 _gran) {

   LOCK_MTX_RENDER;

   if(_gran < 2.0f)
   {
      _gran = 2.0f;
   }
   timestretch_granularity = _gran;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::getTimestretchGranularity(void) {
   return timestretch_granularity;
}

void StSample::setTimestretchGranularityAmount(sF32 _a) {

   LOCK_MTX_RENDER;

   timestretch_granularity_amount = _a;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::getTimestretchGranularityAmount(void) {
   return timestretch_granularity_amount;
}

void StSample::setEnableStaticCyclelen(sSI _bEnabled) {
   b_static_cyclelen = _bEnabled;
}

sSI StSample::getEnableStaticCyclelen(void) {
   return b_static_cyclelen;
}

void StSample::setEnableAbsoluteTimestretch(sSI _bEnabled) {
   b_absolute_timestretch = _bEnabled;
}

sSI StSample::getEnableAbsoluteTimestretch(void) {
   return b_absolute_timestretch;
}

void StSample::setTimestretchInterpolType(sSI _type) {
   timestretch_interpol_type = _type;
}

sSI StSample::getTimestretchInterpolType(void) {
   return timestretch_interpol_type;
}

void StSample::setTimestretchStartPhaseRandAmount(sF32 _amount) {
   timestretch_startphase_rand_amount = _amount;
}

sF32 StSample::getTimestretchStartPhaseRandAmount(void) {
   return timestretch_startphase_rand_amount;
}

void StSample::setTimestretchBend(sF32 _amount) {
   timestretch_bend = _amount;
}

sF32 StSample::getTimestretchBend(void) {
   return timestretch_bend;
}

void StSample::_setEnableTimedLoop(sSI _bEnabled) {
   LOCK_MTX_RENDER;

   b_timedloop = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::_getEnableTimedLoop(void) {
   return b_timedloop;
}

void StSample::_setEnableTimedLoopFade(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_timedloop_fade = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::_getEnableTimedLoopFade(void) {
   return b_timedloop_fade;
}

void StSample::_setTimedLoopBase(sF32 _base) {

   LOCK_MTX_RENDER;

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

   UNLOCK_MTX_RENDER;
}

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

void StSample::_setEnableOneShotLoopStep(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_oneshot_loopstep = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::_getEnableOneShotLoopStep(void) {
   return b_oneshot_loopstep;
}

// // void StSample::_setVolumeRampSteps(sSI _num) {

// //    LOCK_MTX_RENDER;

// //    num_volramp_steps = (sUI) _num;
// //    if(num_volramp_steps > MAX_VOLRAMP_STEPS)
// //    {
// //       num_volramp_steps = MAX_VOLRAMP_STEPS;
// //    }

// //    UNLOCK_MTX_RENDER;
// // }

// // sSI StSample::_getVolumeRampSteps (void) {
// //    return num_volramp_steps;
// // }

void StSample::_setVolumeRampStepsMillisecIn(sF32 _millisec) {

   LOCK_MTX_RENDER;
   
   sF32 sec = _millisec * 0.001f;

   sUI numSteps = (sUI) (sec / STSAMPLEVOICE_BLOCK_LEN);

   if(numSteps > MAX_VOLRAMP_STEPS)
   {
      numSteps = MAX_VOLRAMP_STEPS;
   }

   num_volramp_steps_in = numSteps;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getVolumeRampStepsMillisecIn(void) {
   return num_volramp_steps_in * (STSAMPLEVOICE_BLOCK_LEN * 1000.0f);
}

void StSample::_setVolumeRampStepsMillisecOut(sF32 _millisec) {

   LOCK_MTX_RENDER;
   
   sF32 sec = _millisec * 0.001f;

   sUI numSteps = (sUI) (sec / STSAMPLEVOICE_BLOCK_LEN);
   if(numSteps > MAX_VOLRAMP_STEPS)
   {
      numSteps = MAX_VOLRAMP_STEPS;
   }

   num_volramp_steps_out = numSteps;

   UNLOCK_MTX_RENDER;
}

sF32 StSample::_getVolumeRampStepsMillisecOut(void) {
   return num_volramp_steps_out * (STSAMPLEVOICE_BLOCK_LEN * 1000.0f);
}

void StSample::_setEnableFreezeSampleOffWhenQuiet(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_freeze_sampleoff_when_quiet = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::_getEnableFreezeSampleOffWhenQuiet(void) {
   return b_freeze_sampleoff_when_quiet;
}

void StSample::_setEnableModShiftOrig(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_modshiftorig = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::_getEnableModShiftOrig(void) {
   return b_modshiftorig;
}

void StSample::_setEnableSampleOffNoteOn(sSI _bEnabled) {

   LOCK_MTX_RENDER;

   b_sampleoffnoteon = _bEnabled;

   UNLOCK_MTX_RENDER;
}

sSI StSample::_getEnableSampleOffNoteOn(void) {
   return b_sampleoffnoteon;
}

void StSample::_setEnabled(sSI _bEnabled) {
   b_enabled = _bEnabled;
}

sSI StSample::_getEnabled(void) {
   return b_enabled;
}

void StSample::_setInterpolType(sSI _interpolType) {
   switch(_interpolType)
   {
      case STSAMPLE_INTERPOL_NONE:
         interpol_type = STSAMPLE_INTERPOL_NONE;
         break;

      default:
      case STSAMPLE_INTERPOL_LINEAR:
         interpol_type = STSAMPLE_INTERPOL_LINEAR;
         break;
   }
}

sSI StSample::_getInterpolType(void) {
   return interpol_type;
}

YAC_Object *StSample::_getOrCreateWaveguideTable() {
   if(NULL == waveguide_table)
   {
      waveguide_table = YAC_New_FloatArray();
   }
   return waveguide_table;
}

void StSample::_setEnableWaveguide(sBool _bEnabled) {
   b_waveguide = _bEnabled;
}

sBool StSample::_getEnableWaveguide(void) {
   return b_waveguide;
}

void StSample::_setWaveguideIndex(sSI _idx) {
   waveguide_idx = _idx;
}

sSI StSample::_getWaveguideIndex(void) {
   return waveguide_idx;
}

void StSample::_setEnableSkipRange(sSI _bEnable) {
   b_skip_range = _bEnable;
}

void StSample::_setEnableAlt(sSI _bEnable) {
   alt.b_enable = _bEnable;
}

sSI StSample::_getEnableAlt(void) {
   return alt.b_enable;
}

StSample *StSample::getNextAltRandSample(void) {
   if(alt.b_enable && (alt.num > 0u))
   {
      // also see "tks-examples/non_repeating_rand.tks"
      sUI r;
      sUI retIdx;
      if(1u == alt.rand_num_left)
      {
         retIdx = alt.rand_tbl[0];
         alt.rand_num_left--;
         return alt.samples[retIdx];
      }
      else if(0u == alt.rand_num_left)
      {
         alt.rand_num_left = alt.num;
         do r = sUI(::rand() % alt.num);
         while(0u == r);
      }
      else
      {
         r = sUI(::rand() % alt.rand_num_left);
      }
      retIdx = alt.rand_tbl[r];
      alt.rand_num_left--;
      alt.rand_tbl[r] = alt.rand_tbl[alt.rand_num_left];
      alt.rand_tbl[alt.rand_num_left] = retIdx;
      return alt.samples[retIdx];
   }
   return NULL;
}

void StSample::_setFilterType(sSI _type) {
   filter_type = sUI(_type);
}

sSI StSample::_getFilterType(void) {
   return sSI(filter_type);
}

void StSample::_setFilterCutOff(sF32 _f) {
   filter_cutoff = _f;
}

sF32 StSample::_getFilterCutOff(void) {
   return filter_cutoff;
}

void StSample::_setFilterPan(sF32 _f) {
   filter_pan = _f;
}

sF32 StSample::_getFilterPan(void) {
   return filter_pan;
}

void StSample::_setFilterOffset(sF32 _f) {
   filter_offset = _f;
}

sF32 StSample::_getFilterOffset(void) {
   return filter_offset;
}

void StSample::_setFilterEQGain(sF32 _f) {
   filter_eq_gain = _f;
}

sF32 StSample::_getFilterEQGain(void) {
   return filter_eq_gain;
}

void StSample::_setFilterResonance(sF32 _f) {
   filter_resonance = _f;
}

sF32 StSample::_getFilterResonance(void) {
   return filter_resonance;
}

void StSample::_setFilterAuxEnvAmount(sF32 _amount) {
   filter_aux_env_amount = _amount;
}

sF32 StSample::_getFilterAuxEnvAmount(void) {
   return filter_aux_env_amount;
}

void StSample::_setFilterAuxEnvVelocityAmount(sF32 _amount) {
   filter_aux_env_velocity_amount = _amount;
}

sF32 StSample::_getFilterAuxEnvVelocityAmount(void) {
   return filter_aux_env_velocity_amount;
}

void StSample::_setFilterAuxLFOAmount(sF32 _amount) {
   filter_aux_lfo_amount = _amount;
}

sF32 StSample::_getFilterAuxLFOAmount(void) {
   return filter_aux_lfo_amount;
}

void StSample::_setFilterKeyboardAmount(sF32 _amount) {
   filter_keyboard_amount = _amount;
}

sF32 StSample::_getFilterKeyboardAmount(void) {
   return filter_keyboard_amount;
}

void StSample::_setGlideType(sUI _type) {
   glide_type = _type;
}

sUI StSample::_getGlideType(void) {
   return glide_type;
}

void StSample::_setGlideSpeedTimeUp(sF32 _millisec) {
   if(_millisec > 0.0f)
   {
      glide_speed_time_up = 1.0f / _millisec;
   }
   else
   {
      glide_speed_time_up = 0.0f;
   }
}

sF32 StSample::_getGlideSpeedTimeUp(void) {
   if(glide_speed_time_up > 0.0f)
      return 1.0f / glide_speed_time_up;
   else
      return 0.0f;
}

void StSample::_setGlideSpeedTimeDown(sF32 _millisec) {
   if(_millisec > 0.0f)
   {
      glide_speed_time_down = 1.0f / _millisec;
   }
   else
   {
      glide_speed_time_down = 0.0f;
   }
}

sF32 StSample::_getGlideSpeedTimeDown(void) {
   if(glide_speed_time_down > 0.0f)
      return 1.0f / glide_speed_time_down;
   else
      return 0.0f;
}

void StSample::_setGlideSpeedFreqUp(sF32 _semiPerSec) {
   glide_speed_freq_up = _semiPerSec / 1000.0f;
}

sF32 StSample::_getGlideSpeedFreqUp(void) {
   return glide_speed_freq_up * 1000.0f;
}

void StSample::_setGlideSpeedFreqDown(sF32 _semiPerSec) {
   glide_speed_freq_down = _semiPerSec / 1000.0f;
}

sF32 StSample::_getGlideSpeedFreqDown(void) {
   return glide_speed_freq_down * 1000.0f;
}

void StSample::_setEnableGlideRelease(sBool _bEnable) {
   b_glide_release = _bEnable;
}

sBool StSample::_getEnableGlideRelease(void) {
   return b_glide_release;
}

void StSample::_setGlideMaxNoteDist(sSI _dist) {
   glide_max_note_dist = _dist;
}

sSI StSample::_getGlideMaxNoteDist(void) {
   return glide_max_note_dist;
}

void StSample::_setEnableGlideGlissando(sBool _bEnable) {
   b_glide_glissando = _bEnable;
}

sBool StSample::_getEnableGlideGlissando(void) {
   return b_glide_glissando;
}

void StSample::_setGlideMinTime(sUI _millisec) {
   glide_min_time = _millisec;
}

sUI StSample::_getGlideMinTime(void) {
   return glide_min_time;
}

void StSample::_setEnableGlideRetrigSample(sBool _bEnable) {
   b_glide_retrig_sample = _bEnable;
}

sBool StSample::_getEnableGlideRetrigSample(void) {
   return b_glide_retrig_sample;
}

void  StSample::_setEnableGlideRetrigEnvFreq(sBool _bEnable) {
   b_glide_retrig_env_freq = _bEnable;
}

sBool StSample::_getEnableGlideRetrigEnvFreq(void) {
   return b_glide_retrig_env_freq;
}

void  StSample::_setEnableGlideRetrigEnvVol(sBool _bEnable) {
   b_glide_retrig_env_vol = _bEnable;
}

sBool StSample::_getEnableGlideRetrigEnvVol(void) {
   return b_glide_retrig_env_vol;
}

void  StSample::_setEnableGlideRetrigEnvPan(sBool _bEnable) {
   b_glide_retrig_env_pan = _bEnable;
}

sBool StSample::_getEnableGlideRetrigEnvPan(void) {
   return b_glide_retrig_env_pan;
}

void  StSample::_setEnableGlideRetrigEnvAux(sBool _bEnable) {
   b_glide_retrig_env_aux = _bEnable;
}

sBool StSample::_getEnableGlideRetrigEnvAux(void) {
   return b_glide_retrig_env_aux;
}

void  StSample::_setEnableGlideRetrigLFOFreq(sBool _bEnable) {
   b_glide_retrig_lfo_freq = _bEnable;
}

sBool StSample::_getEnableGlideRetrigLFOFreq(void) {
   return b_glide_retrig_lfo_freq;
}

void  StSample::_setEnableGlideRetrigLFOVol(sBool _bEnable) {
   b_glide_retrig_lfo_vol = _bEnable;
}

sBool StSample::_getEnableGlideRetrigLFOVol(void) {
   return b_glide_retrig_lfo_vol;
}

void  StSample::_setEnableGlideRetrigLFOPan(sBool _bEnable) {
   b_glide_retrig_lfo_pan = _bEnable;
}

sBool StSample::_getEnableGlideRetrigLFOPan(void) {
   return b_glide_retrig_lfo_pan;
}

void  StSample::_setEnableGlideRetrigLFOAux(sBool _bEnable) {
   b_glide_retrig_lfo_aux = _bEnable;
}

sBool StSample::_getEnableGlideRetrigLFOAux(void) {
   return b_glide_retrig_lfo_aux;
}

void StSample::_setMMEnable(sUI _idx, sBool _bEnable) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      modmatrix[_idx].b_enable = (YAC_FALSE != _bEnable);
   }
}

sBool StSample::_getMMEnable(sUI _idx) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      return modmatrix[_idx].b_enable;
   }
   return YAC_FALSE;
}

void StSample::_setMMOp(sUI _idx, sUI _op) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      if(_op < STSAMPLE_MM_NUM_OP)
      {
         modmatrix[_idx].op = _op;
      }
   }
}

sUI StSample::_getMMOp(sUI _idx) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      return modmatrix[_idx].op;
   }
   return STSAMPLE_MM_OP_AUTO;
}

void StSample::_setMMSrc(sUI _idx, sUI _src) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      if(_src < STSAMPLE_MM_NUM_SRC)
      {
         modmatrix[_idx].src = _src;
      }
   }
}

sUI StSample::_getMMSrc(sUI _idx) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      return modmatrix[_idx].src;
   }
   return STSAMPLE_MM_SRC_NONE;
}

void StSample::_setMMDst(sUI _idx, sUI _dst) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      if(_dst < STSAMPLE_MM_NUM_DST)
      {
         modmatrix[_idx].dst = _dst;
      }
   }
}

sUI StSample::_getMMDst(sUI _idx) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      return modmatrix[_idx].dst;
   }
   return STSAMPLE_MM_DST_NONE;
}

void StSample::_setMMAmt(sUI _idx, sF32 _amt) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      if(_amt < -2.0f)
         _amt = -2.0f;
      else if(_amt > 2.0f)
         _amt = 2.0f;
      modmatrix[_idx].amt = _amt;
   }
}

sF32 StSample::_getMMAmt(sUI _idx) {
   if(_idx < STSAMPLE_NUM_MODMATRIX_ENTRIES)
   {
      return modmatrix[_idx].amt;
   }
   return 0.0f;
}
