// ----
// ---- file   : StSampleBank.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, 24Mar2013, 23Dec2018, 27Dec2018, 31Dec2018
// ----          17Jan2019, 23Apr2019
// ----
// ----
// ----

#include "tksampler.h"


StSampleBank::StSampleBank(void) {
   name               = NULL;
   first_sample       = NULL;
   first_mutex_group  = NULL;
   num_samples        = 0;
   max_voices         = 16;
   transpose          = 0.0f;
   freq_rand_amount   = 0.0f;
   volume             = 1.0f;
   volume_rand_amount = 0.0f;
   pan                = 0.0f;
   pan_rand_amount    = 0.0f;
   sampleoffset_rand_amount = 0.0f;
   b_unique_notes     = 0;
}

StSampleBank::~StSampleBank() {
   YAC_DELETE_SAFE(name);

   _freeSamples();
   _freeMutexGroups();
}

void StSampleBank::_freeSamples(void) {
   if(NULL != first_sample)
   {
      StSample *c = first_sample;
      while(c != NULL)
      {
         StSample *n = c->next;
         YAC_DELETE(c);
         c = n;
      }
      first_sample = NULL;
      num_samples = 0;
   }
}

void StSampleBank::_freeMutexGroups(void) {
   if(NULL != first_mutex_group)
   {
      StSampleMutexGroup *c = first_mutex_group;
      while(c != NULL)
      {
         StSampleMutexGroup *n = c->next;
         YAC_DELETE(c);
         c = n;
      }
      first_mutex_group = NULL;
   }
}

sUI StSampleBank::_getNumSamples(void) {
   return num_samples;
}

YAC_Object *StSampleBank::_getFirstSample(void) {
   return first_sample;
}

YAC_Object *StSampleBank::_getSampleByIdx(sUI _idx) {
   StSample *sam = first_sample;
   sUI i = 0;
   while( (i++<_idx) && (sam != NULL) )
   {
      sam = sam->next;
   }
   return sam;
}

sSI StSampleBank::_getSampleIdx(YAC_Object *_s) {
   StSample *c = first_sample;
   sUI i = 0;
   while(NULL != c)
   {
      if((void*)c == (void*)_s)
         return sSI(i);
      i++;
      c = c->next;
   }
   return -1;
}

void StSampleBank::_addSample(YAC_Object *_sample) {

   LOCK_MTX_RENDER;

   YAC_DEREF_ARG(StSample, sample, _sample);
   if(sample_deleteme)
   {
      if(YAC_CHK(sample, clid_StSample))
      {
         sample->parent_samplebank = this;

         // Add sample to end of list
         StSample *s = first_sample;
         if(NULL == s)
         {
            first_sample = sample;
         }
         else
         {
            while(NULL != s->next)
            {
               s = s->next;
            }
            s->next = sample;
         }
         num_samples++;
         updateAltGroups();
      }
      else
      {
         YAC_DELETE(sample);
         Dyac_throw_def(InvalidPointer, "_sample is not a valid StSample object");
      }
   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_sample is not a deletable StSample object");
   }

   UNLOCK_MTX_RENDER;
}

void StSampleBank::_removeSample(YAC_Object *_sample) {

   LOCK_MTX_RENDER;

   if(YAC_CHK(_sample, clid_StSample))
   {
      StSample *s = first_sample;
      if(NULL == s)
      {
         UNLOCK_MTX_RENDER;
         return;
      }
      else if(_sample == s)
      {
         first_sample = s->next;
         YAC_DELETE(_sample);
         num_samples--;
         updateAltGroups();
      }
      else
      {
         while( (NULL != s) && (s->next != _sample) )
         {
            s = s->next;
         }
         if(NULL != s)
         {
            StSample *n = s->next;
            s->next = n->next;
            YAC_DELETE(n);
            num_samples--;
            updateAltGroups();
         }
      }
   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_sample is not a valid StSample object");
   }

   UNLOCK_MTX_RENDER;
}

void StSampleBank::_setEnableSkipRange(sBool _bEnable) {

   LOCK_MTX_RENDER;

   StSample *s = first_sample;
   while(NULL != s)
   {
      s->b_skip_range = _bEnable;
      s = s->next;
   }

   UNLOCK_MTX_RENDER;
}

void StSampleBank::_swapSamples(sUI _idxLeft) {

   LOCK_MTX_RENDER;

   if((_idxLeft < num_samples) && ((_idxLeft + 1u) < num_samples))
   {
      if(0u == _idxLeft)
      {
         StSample *oldFirst = first_sample;
         StSample *oldNext = first_sample->next->next;
         first_sample = oldFirst->next;
         first_sample->next = oldFirst;
         oldFirst->next = oldNext;
         updateAltGroups();
      }
      else
      {
         StSample *s = first_sample;
         StSample *p = NULL;
         sUI idx = 0u;
         while(NULL != s)
         {
            if(idx == _idxLeft)
            {
               StSample *oldFirst = p->next;
               StSample *oldNext = p->next->next->next;
               p->next = oldFirst->next;
               p->next->next = oldFirst;
               oldFirst->next = oldNext;
               updateAltGroups();
               break;
            }
            else
            {
               idx++;
               p = s;
               s = s->next;
            }
         }
      }
   }

   UNLOCK_MTX_RENDER;
}

YAC_Object *StSampleBank::_getFirstMutexGroup(void) {
   return first_mutex_group;
}

YAC_Object *StSampleBank::_getMutexGroupByIdx(sUI _idx) {
   StSampleMutexGroup *mgrp = first_mutex_group;
   sUI i = 0;
   while( (i++ < _idx) && (NULL != mgrp) )
   {
      mgrp = mgrp->next;
   }
   return mgrp;
}

void StSampleBank::_addMutexGroup(YAC_Object *_mtxgrp) {

   LOCK_MTX_RENDER;

   YAC_DEREF_ARG(StSampleMutexGroup, mtxgrp, _mtxgrp);

   if(mtxgrp_deleteme)
   {
      if(YAC_CHK(mtxgrp, clid_StSampleMutexGroup))
      {
         StSampleMutexGroup *mg = first_mutex_group;
         if(NULL == mg)
         {
            first_mutex_group = mtxgrp;
         }
         else
         {
            while(NULL != mg->next)
            {
               mg = mg->next;
            }
            mg->next = mtxgrp;
         }
      }
      else
      {
         YAC_DELETE(mtxgrp);
         Dyac_throw_def(InvalidPointer, "_mtxgrp is not a valid StSampleMutexGroup object");
      }
   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_mtxgrp is not a deletable StSampleMutexGroup object");
   }

   UNLOCK_MTX_RENDER;
}

void StSampleBank::_removeMutexGroup(YAC_Object *_mtxgrp) {

   LOCK_MTX_RENDER;

   if(YAC_CHK(_mtxgrp, clid_StSampleMutexGroup))
   {
      StSampleMutexGroup *mg = first_mutex_group;
      if(NULL == mg)
      {
         UNLOCK_MTX_RENDER;
         return;
      }
      else if(_mtxgrp == mg)
      {
         first_mutex_group = mg->next;
         YAC_DELETE(_mtxgrp);
      }
      else
      {
         while( (NULL != mg) && (mg->next != _mtxgrp) )
         {
            mg = mg->next;
         }
         if(NULL != mg)
         {
            StSampleMutexGroup *n = mg->next;
            mg->next = n->next;
            YAC_DELETE(n);
         }
      }
   }
   else
   {
      Dyac_throw_def(InvalidPointer, "_mtxgrp is not a valid StSampleMutexGroup object");
   }

   UNLOCK_MTX_RENDER;
}

sSI StSampleBank::_getMutexGroupIndexByObject(YAC_Object *_mtxgrp) {
   StSampleMutexGroup *g = first_mutex_group;
   sSI r = 0;
   while(NULL != g)
   {
      if(_mtxgrp == g)
      {
         return r;
      }
      r++;
      g = g->next;
   }
   return r;
}

sUI StSampleBank::_getNumMutexGroups(void) {
   sUI r = 0;
   StSampleMutexGroup *g = first_mutex_group;
   while(NULL != g)
   {
      r++;
      g = g->next;
   }
   return r;
}

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

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

void StSampleBank::_setMaxVoices(sUI _maxVoices) {

   LOCK_MTX_RENDER;

   max_voices = _maxVoices;

   UNLOCK_MTX_RENDER;
}

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

void StSampleBank::_setTranspose(sF32 _transpose) {
   transpose = _transpose;
}

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

void StSampleBank::_setFreqRandAmount(sF32 _amt) {
   freq_rand_amount = _amt;
}

sF32 StSampleBank::_getFreqRandAmount(void) {
   return freq_rand_amount;
}

void StSampleBank::_setVolume(sF32 _vol) {
   volume = _vol;
}

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

void StSampleBank::_setVolumeRandAmount(sF32 _amt) {
   volume_rand_amount = _amt;
}

sF32 StSampleBank::_getVolumeRandAmount(void) {
   return volume_rand_amount;
}

void StSampleBank::_setPan(sF32 _pan) {
   pan = _pan;
}

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

void StSampleBank::_setPanRandAmount(sF32 _amt) {
   pan_rand_amount = _amt;
}

sF32 StSampleBank::_getPanRandAmount(void) {
   return pan_rand_amount;
}

void StSampleBank::_setSampleOffsetRandAmount(sF32 _amt) {
   sampleoffset_rand_amount = _amt;
}

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

void StSampleBank::_setEnableUniqueNotes (sBool _bEnable) {
   b_unique_notes = _bEnable;
}

sSI StSampleBank::_getEnableUniqueNotes (void) {
   return b_unique_notes;
}

void StSampleBank::updateAltGroups(void) {
   StSample *s;
   StSample *curAltHdr = NULL;
   // StRange *rangeKeyHdr = NULL;
   // StRange *rangeVelHdr = NULL;
   // StRange *rangeModHdr = NULL;

   LOCK_MTX_RENDER;

   // Reset all alt groups
   s = first_sample;
   while(NULL != s)
   {
      s->alt.num = 0u;
      s->alt.hdr = NULL;
      s = s->next;
   }

   // Scan samples and build alt groups
   s = first_sample;
   while(NULL != s)
   {
      // yac_host->printf("xxx curAltHdr=%p s=%p altEn=%d\n", curAltHdr, s, s->alt.b_enable);

      if(s->alt.b_enable)
      {
         sBool bNewHdr = (NULL == curAltHdr);

         if(bNewHdr)
         {
            curAltHdr = s;
         }
         else
         {
            sBool bMatch = YAC_TRUE;

            if(NULL != curAltHdr->key_range)
            {
               if(NULL != s->key_range)
               {
                  bMatch = bMatch &&
                     (sSI(curAltHdr->key_range->lo) == sSI(s->key_range->lo)) &&
                     (sSI(curAltHdr->key_range->hi) == sSI(s->key_range->hi)) ;
                  // yac_host->printf("xxx match key=%d\n", bMatch);
               }
            }

            if(NULL != curAltHdr->vel_range)
            {
               if(NULL != s->vel_range)
               {
                  bMatch = bMatch &&
                     (sSI(curAltHdr->vel_range->lo * 127) == sSI(s->vel_range->lo * 127)) &&
                     (sSI(curAltHdr->vel_range->hi * 127) == sSI(s->vel_range->hi * 127)) ;
                  // yac_host->printf("xxx match vel=%d\n", bMatch);
               }
            }

            if(NULL != curAltHdr->mod_range)
            {
               if(NULL != s->mod_range)
               {
                  bMatch = bMatch &&
                     (sSI(curAltHdr->mod_range->lo * 127) == sSI(s->mod_range->lo * 127)) &&
                     (sSI(curAltHdr->mod_range->hi * 127) == sSI(s->mod_range->hi * 127)) ;
                  // yac_host->printf("xxx match mod=%d\n", bMatch);
                  // yac_host->printf("xxx hdr.mod=(%d; %d) s.mod=(%d; %d)\n",
                  //                  sSI(curAltHdr->mod_range->lo * 127),
                  //                  sSI(curAltHdr->mod_range->hi * 127),
                  //                  sSI(s->mod_range->lo * 127),
                  //                  sSI(s->mod_range->hi * 127)
                  //                  );
               }
            }

            if(bMatch)
            {
               // Matches alt group header sample ranges, add to group
               if(0u == curAltHdr->alt.num)
               {
                  curAltHdr->alt.samples[curAltHdr->alt.num++] = curAltHdr;
               }

               curAltHdr->alt.samples[curAltHdr->alt.num++] = s;
               s->alt.hdr = curAltHdr;

               yac_host->printf("xxx add alt, num=%u\n", curAltHdr->alt.num);

               if(STSAMPLE_MAX_ALT_SAMPLES == curAltHdr->alt.num)
               {
                  yac_host->printf("[~~~] StSampleBank::updateAltGroups: MAX_ALT_SAMPLES(%u) reached.\n", STSAMPLE_MAX_ALT_SAMPLES);
                  curAltHdr = s;  // force-start new alt group
               }
            }
            else
            {
               // Key/Vel/Mod ranges don't match, start new alt group
               curAltHdr = s;
            }
         }
      } // if alt.b_enable
      else
      {
         curAltHdr = NULL;
      }

      s = s->next;
   }

   // Init non-repeating random number generator(s)
   s = first_sample;
   while(NULL != s)
   {
      if(s->alt.b_enable && (s->alt.num > 0u))
      {
         s->alt.rand_num_left = s->alt.num;
         for(sUI i = 0u; i < s->alt.rand_num_left; i++)
         {
            s->alt.rand_tbl[i] = i;
         }
      }
      s = s->next;
   }

   UNLOCK_MTX_RENDER;
}

sSI StSampleBank::_getNumAltGroups(void) {
   sSI r = 0;
   StSample *s = first_sample;
   while(NULL != s)
   {
      if(s->alt.b_enable && (s->alt.num > 0u))
         r++;
      s = s->next;
   }
   return r;
}
