// ----
// ---- file   : StSamplePlayer.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, 01Mar2010, 29Apr2010, 11May2010, 28Jun2010, 29Jun2010
// ----          01Jun2010, 05Sep2010, 10Mar2011, 28Mar2011, 24Mar2013, 13Dec2018, 29Dec2018
// ----          31Dec2018, 05Jan2019, 17Jan2019, 23Mar2019, 24Mar2019, 30Mar2019, 11May2019
// ----          12May2019, 15May2019
// ----
// ----
// ----
#include <stdio.h>

#include "tksampler.h"


#include "StWaveform.h"
#include "StADSR.h"
#include "StRange.h"
#include "StSampleMutexGroup.h"
#include "StSample.h"
#include "StSampleBank.h"


#define Dbeginloopvoicesbykey \
   sUI voiceKey = (sUI) _key;\
   if(voiceKey)\
   {\
      sUI i;\
      for(i=0; i<num_voices; i++)\
      {\
         StSampleVoice *v = voices[i];\
         if(voiceKey == v->voice_key)\
         {

#define Dendloopvoicesbykey \
         }\
      }\
   }


#define Dbeginloopvoicesbysample \
   {\
      sUI i;\
      for(i=0; i<num_voices; i++)\
      {\
         StSampleVoice *v = voices[i];\
         if(_sample == v->sample)\
         {

#define Dendloopvoicesbysample \
         }\
      }\
   }


#define Dbeginloopallvoices \
   {\
      sUI i;\
      for(i=0; i<num_voices; i++)\
      {\
         StSampleVoice *v = voices[i];\
         if(v->voice_key > 0)\
         {

#define Dendloopallvoices \
         }\
      }\
   }




StSamplePlayer::StSamplePlayer(void) {
   voices     = NULL;
   num_voices = 0;
   volume     = 1.0f;
   poly_mode  = STSAMPLEPLAYER_POLYMODE_OLDEST;

   default_mixrate    = 48000.0f;
   default_freq_table = NULL;

   next_voice_key = 1;

   global_tick_nr = 0u;

   resetModulators();
}

StSamplePlayer::~StSamplePlayer() {
   freeVoices();
}

void StSamplePlayer::resetModulators(void) {
   mod_vol                   = 1.0f;
   mod_pan                   = 0.0f;
   mod_mod                   = 0.0f;
   mod_freq                  = 0.0f;
   mod_freq2                 = 0.0f;
   mod_timestretch           = 0.0f;
   mod_timestretch_bend      = 0.0f;
   mod_sampleoff             = 0.0f;
   mod_cyclelen              = 1.0f;
   mod_sampleshift           = 0.0f;
   mod_sampleshift_endofloop = 0.0f;
   mod_altvolume             = 1.0f;

   ::memset((void*)perf_ctl, 0, sizeof(perf_ctl));
   perf_ctl[STSAMPLEPLAYER_PERFCTL_PITCHBEND]    = 8192.0f;
   perf_ctl[STSAMPLEPLAYER_PERFCTL_CC8_BALANCE]  = 64.0f;
   perf_ctl[STSAMPLEPLAYER_PERFCTL_CC10_PAN]     = 64.0f;
}

void StSamplePlayer::freeVoices(void) {
   
   LOCK_MTX_RENDER;

   if(NULL != voices)
   {
      sUI i;
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = (StSampleVoice*) voices[i];
         YAC_DELETE(v);
      }
      delete [] voices;
      voices = NULL;
   }

   UNLOCK_MTX_RENDER;
}

sSI StSamplePlayer::allocVoices(sUI _maxPoly) {
   
   // ::printf("xxx StSamplePlayer::allocVoices(maxPoly=%u)\n", _maxPoly);

   freeVoices();

   if(_maxPoly > STSAMPLEPLAYER_MAXVOICES)
   {
      _maxPoly = STSAMPLEPLAYER_MAXVOICES;
   }

   voices = new StSampleVoice*[_maxPoly];
   sUI i;
   for(i=0; i<_maxPoly; i++)
   {
      voices[i] = YAC_NEW(StSampleVoice);
      voices[i]->voice_idx = i;
   }
   num_voices = _maxPoly;

   return 1;
}

sUI StSamplePlayer::getNumVoices(void) {
   return num_voices;
}

sUI StSamplePlayer::getNumActiveVoices(void) {
   sUI r = 0;
   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->voice_key > 0)
      {
         r++;
      }
   }
   return r;
}

sUI StSamplePlayer::getNumPlayingVoices(void) {
   sUI r = 0;
   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->voice_key > 0)
      {
         if(v->b_playing)
         {
            r++;
         }
      }
   }
   return r;
}

sUI StSamplePlayer::getNumActiveNotes(void) {
   sUI r = 0;
   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->voice_key > 0)
      {
         if(!v->b_release)
         {
            r++;
         }
      }
   }
   return r;
}

YAC_Object *StSamplePlayer::_getVoice(sUI _idx) {
   if(_idx < num_voices)
   {
      return voices[_idx];
   }
   else
   {
      return NULL;
   }
}

void StSamplePlayer::stopSampleBankNotes(StSampleBank *_sb, sSI _note) {
   LOCK_MTX_RENDER;

   sUI i;

   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->isAllocated())
      {
         if(v->sample->parent_samplebank == _sb)
         {
            if(v->note == _note)
            {
               v->softStopVoice();
            }
         }
      }
   }

   UNLOCK_MTX_RENDER;
}

void StSamplePlayer::applyMaxSampleBankVoicesConstraint(StSampleBank *_sb) {
   LOCK_MTX_RENDER;

   //
   //
   // Apply samplebank.max_voices constraint
   //  - called during voice allocation
   //
   //
   sUI i;

   sUI uniqueKeys[STSAMPLEPLAYER_MAXVOICES];
   sUI numUniqueKeys = 0;

   //
   // Count number of times the samplebank has been triggered (unique voice keys)
   //
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->isAllocated())
      {
         if(v->sample->parent_samplebank == _sb)
         {
            // Check if voice_key is already on list
            sUI j = 0;
            while(j < numUniqueKeys)
            {
               if(v->voice_key == uniqueKeys[j])
                  break;
               j++;
            }
            if(numUniqueKeys == j)
            {
               // Not in list, add it
               uniqueKeys[numUniqueKeys++] = v->voice_key;
            }
         }
      }
   }

   //printf("xxx apply sb.maxvoices constraint: numUsed=%d, stop voices according to polymode\n", numUniqueKeys);

   // (note) this is actually the number of samplebank noteons to stop
   sSI numVoicesToStop = (numUniqueKeys - _sb->max_voices);

   // (note) compare >= 0 since new voices have to be started (0== all voices in use)
   // (note) can this ever be > 0 ? (probably not since the constraint is applied each time a samplebank is started)
   if(numVoicesToStop >= 0)
   {
      //
      // Stop voices according to poly_mode 
      //  - iterate all voices and find best matching voice, then stop all voices that use the same voice_key
      //
      sSI bestMatchIdx   = -1;
      sUI bestMatchTicks = 0;
      sSI bestMatchNote  = (STSAMPLEPLAYER_POLYMODE_HIGHEST==poly_mode) ? 0 : 127;

      // First try to stop voice that is already in release phase
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         if(v->isAllocated())
         {
            if(v->sample->parent_samplebank == _sb)
            {
               if(v->b_release)
               {
                  // Is candidate, check poly_mode
                  switch(poly_mode)
                  {
                     default:
                     case STSAMPLEPLAYER_POLYMODE_OLDEST:
                     {
                        if(v->replay_ticks >= bestMatchTicks)
                        {
                           bestMatchTicks = v->replay_ticks;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                     case STSAMPLEPLAYER_POLYMODE_LOWEST:
                     {
                        if(v->note <= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                     case STSAMPLEPLAYER_POLYMODE_HIGHEST:
                     {
                        if(v->note >= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                  } // switch poly_mode
               }
            }
         } // if v playing
      } // find voice

      if(-1 == bestMatchIdx)
      {
         // Stop any voice
         for(i=0; i<num_voices; i++)
         {
            StSampleVoice *v = voices[i];
            if(v->isAllocated())
            {
               if(v->sample->parent_samplebank == _sb)
               {
                  // Is candidate, check poly_mode
                  switch(poly_mode)
                  {
                     default:
                     case STSAMPLEPLAYER_POLYMODE_OLDEST:
                     {
                        if(v->replay_ticks >= bestMatchTicks)
                        {
                           bestMatchTicks = v->replay_ticks;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                     case STSAMPLEPLAYER_POLYMODE_LOWEST:
                     {
                        if(v->note <= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                     case STSAMPLEPLAYER_POLYMODE_HIGHEST:
                     {
                        if(v->note >= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                  } // switch poly_mode
               }
            } // if v playing
         } // find voice
      }

      ////printf("xxx StSamplePlayer::allocVoice: count samplebankvoices numUsed=%d\n", numUsed);

      //printf("xxx maxsbvoices: bestMathIdx=%d bestMatchTicks=%d\n", bestMatchIdx, bestMatchTicks);

      //
      // Stop bestmatching voice(s)
      //   - stop all voices that match v's voicekey
      //
      StSampleVoice *v = voices[bestMatchIdx];
      sUI j;
      for(j=0; j<num_voices; j++)
      {
         StSampleVoice *ov = voices[j];
         // // if(ov->b_playing)
         // // {
         if(ov->voice_key == v->voice_key)
         {
            ov->softStopVoice();
         }
         // // }
      }

   }
   else
   {
      // Ok, no need to stop more voices
   }

   UNLOCK_MTX_RENDER;
}

void StSamplePlayer::applyMaxVoicesConstraint(StSample *_sample) {
   // Called during voice allocation

   sUI i;
   sUI numUsed = 0;

   // Count number of voices currently playing the given sample
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->isAllocated())
      {
         if( (v->sample == _sample)  ) //// && (v != nv) )
         {
            numUsed++;
         }
      }
   }

   //printf("xxx smp=\"%s\" numUsed=%d max=%d\n", (_sample->name?(const char*)_sample->name->chars:""), numUsed, _sample->max_voices);

   // Stop voices according to poly_mode
   while(numUsed >= _sample->max_voices)
   {
      sUI bestMatchIdx   = 0;
      sUI bestMatchTicks = 0;
      sSI bestMatchNote  = (STSAMPLEPLAYER_POLYMODE_HIGHEST==poly_mode) ? 0 : 127;

      // First try to stop voice that is already in release phase
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         if(v->isAllocated())
         {
            if(v->sample == _sample)
            {
               if(v->b_release)
               {
                  // Is candidate, check poly_mode
                  switch(poly_mode)
                  {
                     default:
                     case STSAMPLEPLAYER_POLYMODE_OLDEST:
                     {
                        if(v->replay_ticks >= bestMatchTicks)
                        {
                           bestMatchTicks = v->replay_ticks;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                     case STSAMPLEPLAYER_POLYMODE_LOWEST:
                     {
                        if(v->note <= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                     case STSAMPLEPLAYER_POLYMODE_HIGHEST:
                     {
                        if(v->note >= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                  } // switch poly_mode
               }
            }
         } // if v playing
      } // find voice

      if(-1 == bestMatchIdx)
      {
         for(i=0; i<num_voices; i++)
         {
            StSampleVoice *v = voices[i];
            if(v->isAllocated())
            {
               if(v->sample == _sample)
               {
                  // Is candidate, check poly_mode
                  switch(poly_mode)
                  {
                     default:
                     case STSAMPLEPLAYER_POLYMODE_OLDEST:
                     {
                        if(v->replay_ticks >= bestMatchTicks)
                        {
                           bestMatchTicks = v->replay_ticks;
                           bestMatchIdx = i;
                        }
                     }
                     break;
                     
                     case STSAMPLEPLAYER_POLYMODE_LOWEST:
                     {
                        if(v->note <= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;
                     
                     case STSAMPLEPLAYER_POLYMODE_HIGHEST:
                     {
                        if(v->note >= bestMatchNote)
                        {
                           bestMatchNote = v->note;
                           bestMatchIdx = i;
                        }
                     }
                     break;

                  } // switch poly_mode
               }
            }
         } // find bestmatch samplevoice
      }

      // Stop best matching sample voice
      /////printf("xxx stopsamplevoice numUsed=%d idx=%d\n", numUsed, bestMatchIdx);
      StSampleVoice *v = voices[bestMatchIdx];
      v->softStopVoice();

      // Next iteration
      numUsed--;
   } // while numUsed>=max
}

StSampleVoice *StSamplePlayer::findBestMatchVoice(sBool _bSoftStoppedOnly, sBool _bReleaseOnly) {
      ////printf("xxx find bestmatch voice\n");

   StSampleVoice *nv = NULL;

   // All voices in use. Voice allocation strategy applies..
   sUI bestMatchIdx = 999999;
      
   switch(poly_mode)
   {
      default:
      case STSAMPLEPLAYER_POLYMODE_OLDEST:
      {
         sUI bestMatchTicks = 0;
         sUI i;
         for(i=0; i<num_voices; i++)
         {
            StSampleVoice *v = voices[i];
            if(v->canAllocate())
            {
               if(v->replay_ticks >= bestMatchTicks)
               {
                  if(_bSoftStoppedOnly && !v->isSoftStopped())
                  {
                     continue;
                  }
                  else if(_bReleaseOnly && !v->b_release)
                  {
                     continue;
                  }
               
                  bestMatchTicks = v->replay_ticks;
                  bestMatchIdx = i;
               }
            }
         }
      }
      break;
         
      case STSAMPLEPLAYER_POLYMODE_LOWEST:
      {
         sSI bestMatchNote = 127;
         sUI i;
         for(i=0; i<num_voices; i++)
         {
            StSampleVoice *v = voices[i];
            if(v->canAllocate())
            {
               if(v->note <= bestMatchNote)
               {
                  if(_bSoftStoppedOnly && !v->isSoftStopped())
                  {
                     continue;
                  }
                  else if(_bReleaseOnly && !v->b_release)
                  {
                     continue;
                  }

                  bestMatchNote = v->note;
                  bestMatchIdx = i;
               }
            }
         }
      }
      break;
         
      case STSAMPLEPLAYER_POLYMODE_HIGHEST:
      {
         sSI bestMatchNote = 0;
         sUI i;
         for(i=0; i<num_voices; i++)
         {
            StSampleVoice *v = voices[i];
            if(v->canAllocate())
            {
               if(v->note >= bestMatchNote)
               {
                  if(_bSoftStoppedOnly && !v->isSoftStopped())
                  {
                     continue;
                  }
                  else if(_bReleaseOnly && !v->b_release)
                  {
                     continue;
                  }

                  bestMatchNote = v->note;
                  bestMatchIdx = i;
               }
            }
         }
      }
      break;
         
   } // switch poly_mode

   if(bestMatchIdx < num_voices)
   {
      nv = voices[bestMatchIdx];
   }

   return nv;
}

StSampleVoice *StSamplePlayer::allocSampleVoice(StSample *_sample, sSI _note) {

   LOCK_MTX_RENDER;

   StSampleVoice *nv = NULL;

   ////printf("xxx StSamplePlayer::allocSampleVoice\n");

   //
   //
   // Apply sample.max_voices constraint
   //  (note) this only make sense in special cases so the default for a sample
   //         is to just apply the sample bank voice limiter (i.e. sample->max_voices should usually be 0)
   //
   //
   if(_sample->max_voices > 0)
   {
      applyMaxVoicesConstraint(_sample);

   }

   // Check whether there are active voices that belong to the same mutex group as _sample
   if(NULL != _sample->mutex_group)
   {
      sUI i;
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         if((v->voice_key > 0) && (v->voice_key != next_voice_key))
         {
            if(v->sample->mutex_group == _sample->mutex_group)
            {
               // Stop mutual exclusive sample voice
               v->softStopVoice();
            }
         }
      }
   }


   // Find first inactive voice
   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(!v->voice_key) // Note: breaks retrig as described above, i.e. retrig only works if sample is still playing
         //                       (voice key must be unset when voice really stops)
      {
         nv = v;
         break;
      }
   }
   //printf("\n\nxxx find first inactive voice nv=%p\n", nv);

     
   if(NULL == nv)
   {
      // All voices in use
      // Try to find voice that is soft-stopped already
      nv = findBestMatchVoice(true, false);

      if(NULL == nv)
      {
         // Try to find voice that is in release phase
         nv = findBestMatchVoice(false, true);
         
         if(NULL == nv)
         {
            // Find *any* voice (voice stealing!)
            nv = findBestMatchVoice(false, false);
            // printf("xxx steal voice nv=%p\n\n", nv);
         }
         else
         {
            //printf("xxx re-use release voice nv=%p\n", nv);
         }
      }
      else
      {
         //printf("xxx re-use softstopped voice nv=%p\n", nv);
      }
   } // polymode voicealloc

   
   // (note) nv can still be 0 if multiple notes are triggered on the same tick (and same track)
   //         in that case simply do not start a new voice
   if(NULL != nv)
   {

      // Stop all other voices that share the allocated voice's voicekey
      {
         sUI i;
         for(i=0; i<num_voices; i++)
         {
            StSampleVoice *v = voices[i];
            if(v->b_playing && (v->voice_key == nv->voice_key))
            {
               v->softStopVoice();
            }
         }
      }

      ////printf("xxx end allocSampleVoice nv=0x%p\n", nv);
      
      nv->prepareToPlay(this, _sample, next_voice_key);
   }

   //printf("xxx nv=0x%p nv->voice_key=%d\n", nv, nv->voice_key);


   UNLOCK_MTX_RENDER;

   // Return allocated voice
   return nv;
}

StSampleVoice *StSamplePlayer::allocSampleVoiceGlide(StSample *_sample, sSI _note) {

   LOCK_MTX_RENDER;

   StSampleVoice *nv = NULL;

   sSI bestMatchNoteDist = 999999;

   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];

      sBool bCanAlloc = v->b_playing && (v->sample == _sample) && !v->b_alloc;
      bCanAlloc = bCanAlloc && (v->sample->b_glide_release || !v->b_release);
      if(bCanAlloc && (0u != _sample->glide_min_time))
      {
         bCanAlloc = (v->replay_ticks >= _sample->glide_min_time);
      }

      if(bCanAlloc)
      {
         sSI noteDist = v->note - _note;
         noteDist = sABS(noteDist);
         if( (noteDist <= _sample->glide_max_note_dist) && (noteDist < bestMatchNoteDist) )
         {
            bestMatchNoteDist = noteDist;
            nv = v;
         }
      }
   }

   UNLOCK_MTX_RENDER;

   // Return allocated voice
   return nv;
}

sF32 StSamplePlayer::getDefaultMixRate(void) {
   return default_mixrate;
}

void StSamplePlayer::setDefaultMixRate(sF32 _mixRate) {
   LOCK_MTX_RENDER;

   default_mixrate = _mixRate;

   UNLOCK_MTX_RENDER;
}

YAC_Object *StSamplePlayer::getDefaultFreqTable(void) {
   return default_freq_table;
}

void StSamplePlayer::setDefaultFreqTable(YAC_Object *_fa) {
   LOCK_MTX_RENDER;

   YAC_FloatArray *tbl = NULL;

   if(YAC_BCHK(_fa, YAC_CLID_FLOATARRAY))
   {
      YAC_CAST_ARG(YAC_FloatArray, fa, _fa);
      if(fa->num_elements >= 128)
      {
         tbl = fa;
      }
      else
      {
         yac_host->printf("StSamplePlayer::setDefaultFreqTable: table too small (%d < 128)\n", fa->num_elements);
      }
   }
   else
   {
      ////Dyac_throw_def(InvalidPointer, "freqTable is not a valid FloatArray object");
   }
   default_freq_table = tbl;

   UNLOCK_MTX_RENDER;
}

void StSamplePlayer::setVolume(sF32 _vol) {
   LOCK_MTX_RENDER;

   volume = _vol;

   UNLOCK_MTX_RENDER;
}

sF32 StSamplePlayer::getVolume(void) {
   return volume;
}

void StSamplePlayer::setPolyMode(sUI _mode) {
   poly_mode = _mode;
   if(poly_mode >= STSAMPLEPLAYER_NUM_POLYMODES)
   {
      poly_mode = STSAMPLEPLAYER_POLYMODE_OLDEST;
   }
}

sUI StSamplePlayer::getPolyMode(void) {
   return poly_mode;
}

void StSamplePlayer::allNotesOff(void) {
   LOCK_MTX_RENDER;

   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->voice_key > 0)
      {
         // // v->noteOff(0.0f/*velocity*/);
         v->softStopVoice();
      }
      else
      {
         v->_resetVoice();
      }
   }

   // Reset controllers
   resetModulators();

   UNLOCK_MTX_RENDER;
}

void StSamplePlayer::resetVoices(void) {
   LOCK_MTX_RENDER;

   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      v->_resetVoice();
   }

   // Reset controllers
   resetModulators();

   UNLOCK_MTX_RENDER;
}

sSI StSamplePlayer::noteOff(sSI _note, sF32 velocity) {
   LOCK_MTX_RENDER;

   // printf("xxx StSamplePlayer::noteoff: note=%d\n", _note);

   sSI r = 0;

   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->voice_key > 0)
      {
         if(v->note == _note)
         {
            v->noteOff(velocity);
            r++;
         }
      }
   }

   UNLOCK_MTX_RENDER;

   return r;
}

sSI StSamplePlayer::noteOffByKey(sSI _key, sF32 velocity) {
   LOCK_MTX_RENDER;

   // printf("xxx StSamplePlayer::noteoff: key=%d\n", _key);

   sSI r = 0;

   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(sSI(v->voice_key) == _key)
      {
         // printf("xxx StSamplePlayer::noteOff: key=%d note=%d\n", _key, v->note);
         v->noteOff(velocity);
         r++;
      }
   }

   UNLOCK_MTX_RENDER;

   return r;
}

YAC_FloatArray *StSamplePlayer::getCurrentFreqTable(YAC_Object *_freqTable) {
   // Return default freq table or _freqTable (if valid)
   YAC_FloatArray *freqTable = default_freq_table;

   if(YAC_BCHK(_freqTable, YAC_CLID_FLOATARRAY))
   {
      YAC_CAST_ARG(YAC_FloatArray, ft, _freqTable);

      if(ft->num_elements >= 128)
      {
         freqTable = ft;
      }
   }

   return freqTable;
}

sSI StSamplePlayer::startSample(YAC_Object *_sample, YAC_Object *_freqTable, 
                                sSI _note, sF32 _vel, sF32 _mod, sF32 _vol, sF32 _pan, sF32 _freq) 
{
   //
   // Start single sample
   //

   LOCK_MTX_RENDER;

   sSI r = 0; // returns voice key

   if(YAC_CHK(_sample, clid_StSample))
   {
      YAC_CAST_ARG(StSample, sample, _sample);

      YAC_FloatArray *freqTable = getCurrentFreqTable(_freqTable);
      if(NULL != freqTable)
      {
         StSampleVoice *v = allocSampleVoice(sample, _note);

         if(NULL != v)
         {
            v->_startVoice(_sample, freqTable, default_mixrate, _note, _vel, _mod, _vol, _pan, _freq);

            r = (sSI) next_voice_key;

            next_voice_key++;
         }
         else
         {
            r = 0;
         }
      }
      else
      {
         yac_host->printf("[---] StSamplePlayer::startSample: FAILED (invalid frequency table).\n");
      }
   }

   UNLOCK_MTX_RENDER;

   return r;
}

sSI StSamplePlayer::startSampleBank(YAC_Object *_sampleBank, YAC_Object *_freqTable, 
                                    sSI _note, sF32 _vel, sF32 _mod, sF32 _vol, sF32 _pan, sF32 _freq)
{
   //
   // Start all samples that match the given freq/vel/mod zone
   //
   // Also see initStartedVoiceByKey()
   //

   LOCK_MTX_RENDER;

   sSI r = 0; // Returns voice key

   if(YAC_CHK(_sampleBank, clid_StSampleBank))
   {
      YAC_CAST_ARG(StSampleBank, sb, _sampleBank);

      YAC_FloatArray *freqTable = getCurrentFreqTable(_freqTable);
      if(NULL != freqTable)
      {
         if(sb->b_unique_notes)
         {
            // Soft-stop voices (=> b_used=false, b_playing=true)
            stopSampleBankNotes(sb, _note);
         }

         applyMaxSampleBankVoicesConstraint(sb);

         sF32 freq2 = 0.0f;
         sF32 vol2  = sb->volume;
         sF32 pan2  = sb->pan;
         sF32 off2  = 0.0f;

         if(sb->freq_rand_amount > 0.0f)
         {
            freq2 += ((sF32) ((rand()*(1.999999999999f/((sF32)RAND_MAX)))*sb->freq_rand_amount)) - sb->freq_rand_amount;
         }

         if(sb->volume_rand_amount > 0.0f)
         {
            vol2 += ((sF32) ((rand()*(1.999999999999f/((sF32)RAND_MAX)))*sb->volume_rand_amount)) - sb->volume_rand_amount;
         }

         if(sb->pan_rand_amount > 0.0f)
         {
            pan2 += ((sF32) ((rand()*(1.999999999999f/((sF32)RAND_MAX)))*sb->pan_rand_amount)) - sb->pan_rand_amount;
         }

         if(sb->sampleoffset_rand_amount > 0.0f)
         {
            off2 += ((sF32) ((rand()*(0.999999999999f/((sF32)RAND_MAX)))*sb->sampleoffset_rand_amount));
         }

         ////printf("xxx StSamplePlayer::startSampleBank: BEGIN\n");
         StSample *c = sb->first_sample;
         if(NULL != c)
         {
            sUI i = 0;
            while((NULL != c) && (i < num_voices))
            {
               StSample *n = c->next;

               if(c->filterNoteOn(_note, _vel, _mod))
               {
                  if(c->alt.b_enable && (c->alt.num > 0u))
                  {
                     // Alt group header, pick random sample from group
                     StSample *a = c->getNextAltRandSample();

                     if(NULL != a)
                     {
                        // Skip other alt group samples
                        n = c;
                        for(sUI i = 0u;  i < c->alt.num; i++)
                        {
                           if(NULL == n)
                           {
                              // Should not be reachable
                              yac_host->printf("[~~~] StSamplePlayer::startSampleBank: seek: n == NULL !!!\n");
                              break;
                           }
                           n = n->next;
                        }
                        c = a;
                     }
                     else
                     {
                        // Should not be reachable
                        yac_host->printf("[~~~] StSamplePlayer::startSampleBank: getNextAltSample() returned NULL !!!\n");
                     }
                  }

                  StSampleVoice *v = NULL;

                  if(STSAMPLE_GLIDE_NONE != c->glide_type)
                  {
                     // Find voice that is still playing and has nearest distance to new note
                     v = allocSampleVoiceGlide(c, _note);

                     if(NULL != v)
                     {
                        // Assign new voice key
                        v->voice_key = next_voice_key;
                        v->b_glide = YAC_TRUE;

                        // Set glide source note
                        v->glide_src_note = v->glide_src_note_orig = v->getCurrentGlideNote();
                        v->glide_time = 0.0f;
                     }
                  }

                  if(NULL == v)
                  {
                     // Allocate new voice (calls prepareToPlay() and assigns voice key)
                     v = allocSampleVoice(c, _note);

                     if(NULL != v)
                     {
                        v->glide_src_note = v->glide_src_note_orig = sF32(_note);
                        v->glide_time = 1.0f;
                        v->b_glide = YAC_FALSE;
                     }
                  }

                  if(NULL != v)
                  {
                     // Set "alloc" flag to prevent voice from being reallocate twice
                     v->b_alloc = YAC_TRUE;

                     v->_startVoice(c, freqTable, default_mixrate, _note, _vel, _mod, _vol, _pan, _freq);
                     v->_setFreq2(freq2);
                     v->_setVol2(vol2);
                     v->_setPan2(pan2);
                     v->_setSampleOff2(off2);
                     // // r++;//?
                     i++;
                  }
                  else
                  {
                     UNLOCK_MTX_RENDER;
                     return 0;
                  }
               }
               c = n;
            }
            /////printf("xxx StSamplePlayer::startSampleBank: END\n");

            r = next_voice_key;

            next_voice_key++;

            // // // Reset "alloc" flag (==> done in initStartedVoicesByKey())
            // // {
            // //    sUI i;
            // //    for(i=0; i < num_voices; i++)
            // //    {
            // //       StSampleVoice *v = (StSampleVoice*) voices[i];
            // //       v->b_alloc = YAC_FALSE;
            // //    }
            // // }
         }
         else
         {
            // No samples to play
         }
      }
      else
      {
         yac_host->printf("[---] StSamplePlayer::startSampleBank: FAILED (invalid frequency table).\n");
      }
   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_sampleBank is not a valid StSampleBank object");
   }

   UNLOCK_MTX_RENDER;

   return r;
}

sSI StSamplePlayer::_findVoicesByKey(sSI _key, YAC_Object *_retPA) {
   sSI r = 0;

   if(YAC_BCHK(_retPA, YAC_CLID_POINTERARRAY))
   {
      LOCK_MTX_RENDER;

      sUI voiceKey = (sUI) _key;
      YAC_CAST_ARG(YAC_PointerArray, pa, _retPA);

      sUI i;
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         if(voiceKey == v->voice_key)
         {
            YAC_Value av;
            av.initObject(v, 0);
            pa->yacArraySet(NULL, pa->num_elements, &av); // add element
            r++;
         }
      } // loop voices

      UNLOCK_MTX_RENDER;
   }

   return r;
}


void StSamplePlayer::_initStartedVoicesByKey(sSI _key) {
   sUI voiceKey = (sUI) _key;

   // yac_host->printf("xxx initStartedVoicesByKey: key=%d\n", _key);

   if(voiceKey)
   {
      LOCK_MTX_RENDER;

      // yac_host->printf("xxx initStartedVoicesByKey: key=%d num_voices=%d voices=0x%p\n", voiceKey, num_voices, voices);

      sUI i;
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         // printf("xxx initStartedVoicesByKey: i=%d v=0x%p\n", i, v);
         if(voiceKey == v->voice_key)
         {
            ////printf("xxx initStartedVoice i=%d\n", i);
            // printf("xxx initStartedVoicesByKey: 1 init v=0x%p\n", v);
            // printf("xxx initStartedVoicesByKey: 1 init v=0x%p v->sample=0x%p\n", v, v->sample);
            // printf("xxx initStartedVoicesByKey: 1 init v=0x%p v->sample->waveform=0x%p\n", v, v->sample->waveform);
            // printf("xxx initStartedVoicesByKey: 1 init v=0x%p v->sample->waveform->sample_data=0x%p\n", v, v->sample->waveform->sample_data);
            v->initStartedVoice();
            // printf("xxx initStartedVoicesByKey: 2 init v=0x%p\n", v);
         }
      } // loop voices

      UNLOCK_MTX_RENDER;
   }

}

void StSamplePlayer::_restartVoicesByKey(sSI _key) {
   sUI voiceKey = (sUI) _key;

   if(voiceKey)
   {
      LOCK_MTX_RENDER;

      sUI i;
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         if(voiceKey == v->voice_key)
         {
            /////printf("xxx StSamplePlayer::restartVoicesByKey: key=%d\n", voiceKey);
            v->_restartVoice();
         }
      } // loop voices

      UNLOCK_MTX_RENDER;
   }
}

void StSamplePlayer::stopVoicesByKey(sSI _key) {
   Dbeginloopvoicesbykey;
   /////v->stopVoice();
   v->softStopVoice();
   Dendloopvoicesbykey;
}

void StSamplePlayer::stopVoicesBySample(YAC_Object *_sample) {
   for(sUI i = 0u; i < num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->isAllocated())
      {
         if((void*)v->sample == (void*)_sample)
         {
            v->stopVoice();
         }
      }
   }
}

void StSamplePlayer::stopLFOVolBySample(YAC_Object *_sample) {
   Dbeginloopvoicesbysample;
   v->lfo_vol.stopLFO();
   Dendloopvoicesbysample;
}

void StSamplePlayer::stopLFOPanBySample(YAC_Object *_sample) {
   Dbeginloopvoicesbysample;
   v->lfo_pan.stopLFO();
   Dendloopvoicesbysample;
}

void StSamplePlayer::stopLFOFreqBySample(YAC_Object *_sample) {
   Dbeginloopvoicesbysample;
   v->lfo_freq.stopLFO();
   Dendloopvoicesbysample;
}

void StSamplePlayer::stopADSRVolBySample(YAC_Object *_sample) {
   Dbeginloopvoicesbysample;
   v->adsr_vol.stopADSR();
   Dendloopvoicesbysample;
}

void StSamplePlayer::stopADSRPanBySample(YAC_Object *_sample) {
   Dbeginloopvoicesbysample;
   v->adsr_pan.stopADSR();
   Dendloopvoicesbysample;
}

void StSamplePlayer::stopADSRFreqBySample(YAC_Object *_sample) {
   Dbeginloopvoicesbysample;
   v->adsr_freq.stopADSR();
   Dendloopvoicesbysample;
}

void StSamplePlayer::_render(YAC_Object *_fa) {
   if(YAC_BCHK(_fa, YAC_CLID_FLOATARRAY))
   {
      YAC_CAST_ARG(YAC_FloatArray, buf, _fa);

      LOCK_MTX_RENDER;

      global_tick_nr++;

      sUI i;
      sF32 volScale = volume; //// * (1.0f / num_voices);  // 13Dec2018: just add the voices
      sUI numVoicesRendered = 0;
      for(i=0; i<num_voices; i++)
      {
         StSampleVoice *v = voices[i];
         if(v->voice_key > 0)
         {
            ////printf("xxx render voice %d\n", i);
            v->_render(buf, volScale);
            numVoicesRendered++;
         }
         // else
         // {
         //    printf("xxx voice %d is not allocated (voice_key==0)\n", i);
         // }
      }

      UNLOCK_MTX_RENDER;

      // Debug: dump samples
      if(0 && (numVoicesRendered>0))
      {
         sUI i;
         for(i=0; i<(buf->num_elements/2); i++)
         {
            printf("%f, ", buf->elements[i*2+0]);
         }
         printf("\n~~~~\n");
      }

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

void StSamplePlayer::updateVol(sF32 _v) {
   mod_vol = _v;
}

void StSamplePlayer::updateVolByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setVol(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updatePan(sF32 _v) {
   if(_v > 1.0f)
      _v = 1.0f;
   else if(_v < -1.0f)
      _v = -1.0f;
   mod_pan = _v;
}

void StSamplePlayer::updatePanByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setPan(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateFreq(sF32 _v) {
   mod_freq = _v;
}

void StSamplePlayer::updateFreqByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setFreq(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateFreq2(sF32 _v) {
   mod_freq2 = _v;
}

void StSamplePlayer::updateFreq2ByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setFreq2(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateMod(sF32 _v) {
   mod_mod = _v;
}

void StSamplePlayer::updateModByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setMod(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateTimestretch(sF32 _v) {
   mod_timestretch = _v;
}

void StSamplePlayer::updateTimestretchByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setTimestretch(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateTimestretchBend(sF32 _v) {
   mod_timestretch_bend = _v;
}

void StSamplePlayer::updateTimestretchBendByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setTimestretchBend(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSampleOff(sF32 _v) {
   mod_sampleoff = _v;
}

void StSamplePlayer::updateSampleOffByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setSampleOff(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateCycleLen(sF32 _v) {
   mod_cyclelen = _v;
}

void StSamplePlayer::updateCycleLenByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setCycleLen(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSampleShift(sF32 _v) {
   mod_sampleshift = _v;
}

void StSamplePlayer::updateSampleShiftByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setSampleShift(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSampleShiftEndOfLoop(sF32 _v) {
   mod_sampleshift_endofloop = _v;
}

void StSamplePlayer::updateSampleShiftEndOfLoopByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setSampleShiftEndOfLoop(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateAltVolume(sF32 _v) {
   ////printf("xxx StSamplePlayer::updateAltVolume: v=%f\n", _v);
   mod_altvolume = _v;
}

void StSamplePlayer::updateAltVolumeByKey(sSI _key, sF32 _v) {
   Dbeginloopvoicesbykey;
   v->_setAltVolume(_v);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateRetrigMask(sUI _mask) {
   sUI i;
   for(i=0; i<num_voices; i++)
   {
      StSampleVoice *v = voices[i];
      if(v->voice_key > 0) // is active ?
      {
         v->_setRetrigMask(_mask);
      }
   }
}

void StSamplePlayer::updateRetrigMaskByKey(sSI _key, sUI _mask) {
   Dbeginloopvoicesbykey;
   v->_setRetrigMask(_mask);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOFreqSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setLFOFreqSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOFreqSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setLFOFreqSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOFreqLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setLFOFreqLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOFreqLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setLFOFreqLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOVolSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setLFOVolSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOVolSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setLFOVolSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOVolLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setLFOVolLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOVolLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setLFOVolLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOPanSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setLFOPanSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOPanSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setLFOPanSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOPanLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setLFOPanLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOPanLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setLFOPanLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOAuxSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setLFOAuxSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOAuxSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setLFOAuxSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateLFOAuxLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setLFOAuxLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateLFOAuxLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setLFOAuxLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRFreqSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setADSRFreqSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRFreqSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setADSRFreqSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRFreqLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setADSRFreqLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRFreqLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setADSRFreqLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRFreqInt(sF32 _int) {
   Dbeginloopallvoices;
   v->_setADSRFreqInt(_int);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRFreqIntByKey(sSI _key, sF32 _int) {
   Dbeginloopvoicesbykey;
   v->_setADSRFreqInt(_int);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRVolSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setADSRVolSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRVolSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setADSRVolSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRVolLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setADSRVolLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRVolLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setADSRVolLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRVolInt(sF32 _int) {
   Dbeginloopallvoices;
   v->_setADSRVolInt(_int);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRVolIntByKey(sSI _key, sF32 _int) {
   Dbeginloopvoicesbykey;
   v->_setADSRVolInt(_int);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRPanSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setADSRPanSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRPanSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setADSRPanSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRPanLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setADSRPanLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRPanLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setADSRPanLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRPanInt(sF32 _int) {
   Dbeginloopallvoices;
   v->_setADSRPanInt(_int);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRPanIntByKey(sSI _key, sF32 _int) {
   Dbeginloopvoicesbykey;
   v->_setADSRPanInt(_int);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRAuxSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setADSRAuxSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRAuxSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setADSRAuxSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRAuxLvl(sF32 _lvl) {
   Dbeginloopallvoices;
   v->_setADSRAuxLvl(_lvl);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRAuxLvlByKey(sSI _key, sF32 _lvl) {
   Dbeginloopvoicesbykey;
   v->_setADSRAuxLvl(_lvl);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateADSRAuxInt(sF32 _int) {
   Dbeginloopallvoices;
   v->_setADSRAuxInt(_int);
   Dendloopallvoices;
}

void StSamplePlayer::updateADSRAuxIntByKey(sSI _key, sF32 _int) {
   Dbeginloopvoicesbykey;
   v->_setADSRAuxInt(_int);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateJumpToLoop(sSI _idx) {
   Dbeginloopallvoices;
   v->_setJumpToLoop(_idx);
   Dendloopallvoices;
}

void StSamplePlayer::updateJumpToLoopByKey(sSI _key, sSI _idx) {
   Dbeginloopvoicesbykey;
   v->_setJumpToLoop(_idx);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateJumpToLoopRel(sF32 _relIdx) {
   Dbeginloopallvoices;
   v->_setJumpToLoopRel(_relIdx);
   Dendloopallvoices;
}

void StSamplePlayer::updateJumpToLoopRelByKey(sSI _key, sF32 _relIdx) {
   Dbeginloopvoicesbykey;
   v->_setJumpToLoopRel(_relIdx);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateWaveguideIndex(sSI _idx) {
   Dbeginloopallvoices;
   v->_setWaveguideIndex(_idx);
   Dendloopallvoices;
}

void StSamplePlayer::updateWaveguideIndexByKey(sSI _key, sSI _idx) {
   Dbeginloopvoicesbykey;
   v->_setWaveguideIndex(_idx);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateAFreqSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setAFreqSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateAFreqSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setAFreqSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSFreqSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setSFreqSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateSFreqSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setSFreqSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateRFreqSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setRFreqSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateRFreqSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setRFreqSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateAVolSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setAVolSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateAVolSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setAVolSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSVolSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setSVolSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateSVolSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setSVolSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateRVolSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setRVolSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateRVolSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setRVolSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateAPanSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setAPanSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateAPanSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setAPanSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSPanSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setSPanSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateSPanSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setSPanSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateRPanSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setRPanSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateRPanSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setRPanSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateAAuxSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setAAuxSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateAAuxSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setAAuxSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateSAuxSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setSAuxSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateSAuxSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setSAuxSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateRAuxSpd(sF32 _spd) {
   Dbeginloopallvoices;
   v->_setRAuxSpd(_spd);
   Dendloopallvoices;
}

void StSamplePlayer::updateRAuxSpdByKey(sSI _key, sF32 _spd) {
   Dbeginloopvoicesbykey;
   v->_setRAuxSpd(_spd);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateGlideSwitch(sBool _bEnable) {
   Dbeginloopallvoices;
   v->_setGlideSwitch(_bEnable);
   Dendloopallvoices;
}

void StSamplePlayer::updateGlideSwitchByKey(sSI _key, sBool _bEnable) {
   Dbeginloopvoicesbykey;
   v->_setGlideSwitch(_bEnable);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updateGlideSpeed(sF32 _speed) {
   Dbeginloopallvoices;
   v->_setGlideSpeed(_speed);
   Dendloopallvoices;
}

void StSamplePlayer::updateGlideSpeedByKey(sSI _key, sF32 _speed) {
   Dbeginloopvoicesbykey;
   v->_setGlideSpeed(_speed);
   Dendloopvoicesbykey;
}

void StSamplePlayer::updatePerfCtl(sUI _idx, sF32 _val) {
   if(_idx < STSAMPLEPLAYER_NUM_PERFCTL)
   {
      perf_ctl[_idx] = _val;
   }
}

void StSamplePlayer::updatePerfCtlByKey(sSI _key, sUI _idx, sF32 _val) {
   if(STSAMPLEPLAYER_PERFCTL_PRESSURE == _idx)
   {
      Dbeginloopvoicesbykey;
      v->_updatePerfCtlPolyPressure(_val);
      Dendloopvoicesbykey;
   }
}
