// ----
// ---- file   : sample_utils.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   : 
// ----
// ---- created: 25Oct2009
// ---- changed: 29Apr2010, 11May2010, 25Feb2011, 04May2013, 08May2013, 12Feb2014, 07Aug2016
// ----          23Feb2018, 24Feb2018, 30Mar2018, 09Apr2018, 22Jun2018, 21Jul2018, 18Dec2018
// ----          22Dec2018, 04May2019, 09May2019
// ----
// ----
// ----

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

#include "tksampleedit.h"


void tksampleedit_ringbuf_copy_samples(YAC_Object *_dst, sUI _dstNumCh, sUI _dstChOff, sUI _dstFrameOff, 
                                       sUI _numFrames, sUI _numCh, 
                                       YAC_Object *_src, sUI _srcNumCh, sUI _srcChOff, sUI _srcFrameOff
                                       ) {
   if(YAC_Is_FloatArray(_dst))
   {
      if(YAC_Is_FloatArray(_src))
      {
         YAC_CAST_ARG(YAC_FloatArray, src, _src);
         YAC_CAST_ARG(YAC_FloatArray, dst, _dst);

         sF32 *d = dst->elements + _dstChOff;
         sF32 *s = src->elements + _srcChOff;

         sUI srcSmpOff = _srcFrameOff * _srcNumCh;
         sUI dstSmpOff = _dstFrameOff * _dstNumCh;
         sUI i;

         for(i=0; i<_numFrames; i++)
         {
            if(dstSmpOff >= dst->num_elements)
            {
               dstSmpOff -= dst->num_elements;
            }

            if(srcSmpOff >= src->num_elements)
            {
               srcSmpOff -= src->num_elements;
            }

            for(sUI chIdx = 0; chIdx < _numCh; chIdx++)
            {
               d[dstSmpOff+chIdx] = s[srcSmpOff+chIdx];
            }

            dstSmpOff += _dstNumCh;
            srcSmpOff += _srcNumCh;
         }
      }
   }
}


void tksampleedit_copy_looped_samples_mono(YAC_Object *_dst, sSI _dstFrameOff,
                                           YAC_Object *_src, sSI _srcFrameOff,
                                           sSI _loopStart, sSI _loopEnd,
                                           sUI _numFrames
                                           ) {
   if(YAC_Is_FloatArray(_dst))
   {
      if(YAC_Is_FloatArray(_src))
      {
         YAC_CAST_ARG(YAC_FloatArray, src, _src);
         YAC_CAST_ARG(YAC_FloatArray, dst, _dst);

         if(dst->max_elements >= (sUI(_dstFrameOff) + _numFrames))
         {
            sF32 *d = dst->elements;
            sF32 *s = src->elements;

            // printf("xxx srcFrameOffStart=%d loop start=%d end=%d\n", _srcFrameOff, _loopStart, _loopEnd);

            for(sUI i = 0; (i < _numFrames); i++)
            {
               sSI srcIdxW;

               if(_srcFrameOff < _loopStart)
               {
                  srcIdxW = (_srcFrameOff - _loopStart) + _loopEnd;
               }
               else if(_srcFrameOff >= _loopEnd)
               {
                  srcIdxW = (_srcFrameOff - _loopEnd) + _loopStart;
               }
               else
               {
                  srcIdxW = _srcFrameOff;
               }

               if((srcIdxW >= 0) && (sUI(srcIdxW) < src->max_elements))
               {
                  d[_dstFrameOff] = s[srcIdxW];
               }
               else
               {
                  d[_dstFrameOff] = 0.0f;
               }

               // Next sample frame
               _srcFrameOff++;
               _dstFrameOff++;
            }
         }
      }
   }
}


sF32 YAC_CALL tksampleedit_buf_get_max(YAC_Object *_src, sUI _numFrames, sUI _numCh, sUI _chOff) {
   sF32 r = 0.0f;

   if(YAC_Is_FloatArray(_src))
   {
      YAC_CAST_ARG(YAC_FloatArray, src, _src);

      sF32 *s = src->elements;

      sUI i;
      sUI off =  _chOff;
      for(i=0; i<_numFrames; i++)
      {
         sF32 f = s[off];

         if(f < 0)
            f = -f;

         if(f > r)
            r = f;

         off += _numCh;
      }
   }

   return r;
}

void YAC_CALL tksampleedit_mix_stereo_input_buf(YAC_Object *_faOutput,
                                                YAC_Object *_faInput,
                                                YAC_Object *_faMixerLevels,
                                                sUI _numInputChannels,
                                                sUI _numOutputChannels,
                                                sUI _numFrames,
                                                sUI _outChOff,
                                                sUI _inChOff
                                                ) {
   if(_numInputChannels > 0)
   {
      if(YAC_Is_FloatArray(_faOutput))
      {
         YAC_CAST_ARG(YAC_FloatArray, output, _faOutput);
         
         if(YAC_Is_FloatArray(_faInput))
         {
            YAC_CAST_ARG(YAC_FloatArray, input, _faInput);
            
            if(YAC_Is_FloatArray(_faMixerLevels))
            {
               YAC_CAST_ARG(YAC_FloatArray, mixerLevels, _faMixerLevels);

               sUI ooff = _outChOff;
               sUI ioff = _inChOff;

               for(sUI frameNr=0; frameNr<_numFrames; frameNr++)
               {
                  sF32 l = input->elements[ioff + 0] * mixerLevels->elements[_inChOff + 0];
                  sF32 r = input->elements[ioff + 1] * mixerLevels->elements[_inChOff + 1];

                  output->elements[ooff + 0] = l;
                  output->elements[ooff + 1] = r;

                  // Next frame
                  ooff += _numOutputChannels; 
                  ioff += _numInputChannels;
               }
            }
         }
      }
   }
}


void YAC_CALL tksampleedit_mixclip_stereo_mixbuf(YAC_Object *_faOutput,
                                                 YAC_Object *_faMixBuf,
                                                 sF32 _levelL,
                                                 sF32 _levelR,
                                                 sF32 _clipLevel,
                                                 sUI _numOutputChannels,
                                                 sUI _numFrames,
                                                 sUI _outChOff
                                                 ) {

   if(YAC_Is_FloatArray(_faOutput))
   {
      YAC_CAST_ARG(YAC_FloatArray, output, _faOutput);

      if(YAC_Is_FloatArray(_faMixBuf))
      {
         YAC_CAST_ARG(YAC_FloatArray, mixBuf, _faMixBuf);

         sUI inOff = 0;

         for(sUI frameNr=0; frameNr<_numFrames; frameNr++)
         {
            sF32 l = output->elements[_outChOff + 0] + mixBuf->elements[inOff + 0] * _levelL;
            if(l < -_clipLevel)
            {
               l = -_clipLevel;
            }
            else if(l > _clipLevel)
            {
               l = _clipLevel;
            }
            output->elements[_outChOff + 0] = l;


            sF32 r = output->elements[_outChOff + 1] + mixBuf->elements[inOff + 1] * _levelR;
            if(r < -_clipLevel)
            {
               r = -_clipLevel;
            }
            else if(r > _clipLevel)
            {
               r = _clipLevel;
            }
            output->elements[_outChOff + 1] = r;

            // Next frame
            _outChOff += _numOutputChannels;
            inOff += 2;
         }
         
      }
   }
}


void YAC_CALL tksampleedit_fill_mono(YAC_Object *_faOut, sUI _outOff, sF32 _val, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = _val;
   }
}


void YAC_CALL tksampleedit_copy_mono_to_mono(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff++];
   }
}

void YAC_CALL tksampleedit_amp_mono_to_mono(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames, sF32 _amp) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff++] * _amp;
   }
}

void YAC_CALL tksampleedit_copy_mono_adv_to_mono(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff];
      _inOff += _inAdv;
   }
}

void YAC_CALL tksampleedit_copy_mono_adv_to_mono_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faIn[_inOff];
      _outOff += _outAdv;
      _inOff  += _inAdv;
   }
}

void YAC_CALL tksampleedit_copy_mono_adv_to_mono_adv_ring(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, sUI _outRingSize, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      _outOff = _outOff % _outRingSize;
      faOut[_outOff] = faIn[_inOff];
      _outOff += _outAdv;
      _inOff  += _inAdv;
   }
}

void YAC_CALL tksampleedit_copy_mono_adv_ring_to_mono_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _inRingSize, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      _inOff = _inOff % _inRingSize;
      faOut[_outOff] = faIn[_inOff];
      _outOff += _outAdv;
      _inOff  += _inAdv;
   }
}

void YAC_CALL tksampleedit_copy_mono_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff] = faIn[_inOff];
      faOutR[_outOff] = faIn[_inOff++];
      _outOff++;
   }
}

void YAC_CALL tksampleedit_copy_mono_adv_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff] = faIn[_inOff];
      faOutR[_outOff] = faIn[_inOff];
      _inOff += _inAdv;
      _outOff++;
   }
}

void YAC_CALL tksampleedit_copy_mono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff];
      faOut[_outOff++] = faIn[_inOff++];
   }
}

void YAC_CALL tksampleedit_copy_pan_mono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff] * _lvlL;
      faOut[_outOff++] = faIn[_inOff++] * _lvlR;
   }
}

void YAC_CALL tksampleedit_copy_dualmono_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff]   = faInL[_inOff];
      faOutR[_outOff++] = faInR[_inOff++];
   }
}

void YAC_CALL tksampleedit_copy_dualmono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faInL[_inOff];
      faOut[_outOff++] = faInR[_inOff];
      _inOff++;
   }
}

void YAC_CALL tksampleedit_copy_pan_dualmono_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff] = faInL[_inOff] * _lvlL;
      faOutR[_outOff] = faInR[_inOff] * _lvlR;
      _outOff++;
      _inOff++;
   }
}

void YAC_CALL tksampleedit_copy_pan_stereowidth_dualmono_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sF32 _stereoWidth, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      sF32 l = faInL[_inOff];
      sF32 r = faInR[_inOff];
      sF32 m = (l + r) * 0.5f;
      l = m + ((l - m) * _stereoWidth);
      r = m + ((r - m) * _stereoWidth);

      faOutL[_outOff] = l * _lvlL;
      faOutR[_outOff] = r * _lvlR;
      _outOff++;
      _inOff++;
   }
}

void YAC_CALL tksampleedit_copy_pan_dualmono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faInL[_inOff] * _lvlL;
      faOut[_outOff++] = faInR[_inOff] * _lvlR;
      _inOff++;
   }
}

void YAC_CALL tksampleedit_copy_stereo_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff] = faIn[_inOff++];
      faOutR[_outOff] = faIn[_inOff++];
      _outOff++;
   }
}

void YAC_CALL tksampleedit_copy_stereo_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff++];
      faOut[_outOff++] = faIn[_inOff++];
   }
}

void YAC_CALL tksampleedit_copy_pan_stereo_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] = faIn[_inOff++] * _lvlL;
      faOut[_outOff++] = faIn[_inOff++] * _lvlR;
   }
}

void YAC_CALL tksampleedit_copy_pan_stereo_adv_to_stereo_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff+0] = faIn[_inOff+0] * _lvlL;
      faOut[_outOff+1] = faIn[_inOff+1] * _lvlR;

      _inOff  += _inAdv;
      _outOff += _outAdv;
   }
}

void YAC_CALL tksampleedit_copy_pan_tanh_dim_stereo_adv_to_stereo_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvlL, sF32 _lvlR, sF32 _dim, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      sF32 x = faIn[_inOff] * _lvlL;
      if((x > 1.0f) || (x < -1.0f))
      {
#if 0
         x = (sF32) (
            (expf(sinf(x)*4.0f) - expf(-sinf(x)*4.0f*1.25f)) /
            (expf(sinf(x)*4.0f) + expf(-sinf(x)*4.0f))
                     );
#else
         x = tanhf(x);
#endif
      }
      faOut[_outOff] = x * _dim;

      x = faIn[_inOff+1] * _lvlR;
      if((x > 1.0f) || (x < -1.0f))
      {
#if 0
         x = (sF32) (
            (expf(sinf(x)*4.0f) - expf(-sinf(x)*4.0f*1.25f)) /
            (expf(sinf(x)*4.0f) + expf(-sinf(x)*4.0f))
                     );
#else
         x = tanhf(x);
#endif
      }
      faOut[_outOff+1] = x * _dim;

      _inOff  += _inAdv;
      _outOff += _outAdv;
   }
}

void YAC_CALL tksampleedit_add_pan_mono_adv_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faIn = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff] = faOutL[_outOff] + faIn[_inOff] * _lvlL;
      faOutR[_outOff] = faOutR[_outOff] + faIn[_inOff] * _lvlR;
      _outOff++;
      _inOff += _inAdv;
   }
}

void YAC_CALL tksampleedit_add_dualmono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faInL[_inOff];
      _outOff++;
      faOut[_outOff] = faOut[_outOff] + faInR[_inOff++];
      _outOff++;
   }
}


void YAC_CALL tksampleedit_add_stereo_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff++];
      _outOff++;
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff++];
      _outOff++;
   }
}


void YAC_CALL tksampleedit_add_amp_mono_adv_to_mono_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvl, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff] * _lvl;
      _outOff += _outAdv;
      _inOff  += _inAdv;
   }
}


void YAC_CALL tksampleedit_add_mono_to_mono(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff++] += faIn[_inOff++];
   }
}

void YAC_CALL tksampleedit_xfade_mono(YAC_Object *_faOut, YAC_Object *_faIn, sUI _numFrames, sUI _xfadeFrames) {
   if((_numFrames > 0u) && (_numFrames >= (_xfadeFrames * 2u)))
   {
      sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
      const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

      sUI numOut = _numFrames - _xfadeFrames;
      sUI numUnfaded = numOut - _xfadeFrames;

      sUI j = _xfadeFrames;
      sUI i = 0u;

      for(; i < numUnfaded; i++)
      {
         faOut[i] = faIn[j++];
      }

      sUI k = 0u;
      sF64 t = 0.0;
      sF64 step = 1.0 / _xfadeFrames;
      // yac_host->printf("xxx (numOut-i)=%u _xfadeFrames=%u\n", (numOut - i), _xfadeFrames);

      for(; i < numOut; i++)
      {
         float a = faIn[j++];
         float b = faIn[k++];
         faOut[i] = a + t * (b - a);
         t += step;
      }
   }
}

void YAC_CALL tksampleedit_add_pan_mono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff] * _lvlL;
      _outOff++;
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff++] * _lvlR;
      _outOff++;
   }
}

void YAC_CALL tksampleedit_add_pan_mono_adv_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff] * _lvlL;
      _outOff++;
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff] * _lvlR;
      _outOff++;
      _inOff += _inAdv;
   }
}


void YAC_CALL tksampleedit_add_pan_mono_adv_to_stereo_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff + 0u] = faOut[_outOff + 0u] + faIn[_inOff] * _lvlL;
      faOut[_outOff + 1u] = faOut[_outOff + 1u] + faIn[_inOff] * _lvlR;
      _outOff += _outAdv;
      _inOff  += _inAdv;
   }
}


void YAC_CALL tksampleedit_add_pan_mono_to_stereo_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff + 0u] = faOut[_outOff + 0u] + faIn[_inOff] * _lvlL;
      faOut[_outOff + 1u] = faOut[_outOff + 1u] + faIn[_inOff] * _lvlR;
      _outOff += _outAdv;
      _inOff++;
   }
}


void YAC_CALL tksampleedit_add_pan_dualmono_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faInL[_inOff] * _lvlL;
      _outOff++;
      faOut[_outOff] = faOut[_outOff] + faInR[_inOff++] * _lvlR;
      _outOff++;
   }
}

void YAC_CALL tksampleedit_add_pan_dualmono_to_stereo_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff + 0u] = faOut[_outOff + 0u] + faInL[_inOff] * _lvlL;
      faOut[_outOff + 1u] = faOut[_outOff + 1u] + faInR[_inOff] * _lvlR;
      _outOff += _outAdv;
      _inOff++;
   }
}

void YAC_CALL tksampleedit_add_pan_stereo_to_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOutL[_outOff] = faOutL[_outOff] + faIn[_inOff++] * _lvlL;
      faOutR[_outOff] = faOutR[_outOff] + faIn[_inOff++] * _lvlR;
      _outOff++;
   }
}

void YAC_CALL tksampleedit_add_pan_stereo_to_stereo(YAC_Object *_faOut, sUI _outOff, YAC_Object *_faIn, sUI _inOff, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff++] * _lvlL;
      _outOff++;
      faOut[_outOff] = faOut[_outOff] + faIn[_inOff++] * _lvlR;
      _outOff++;
   }
}

void YAC_CALL tksampleedit_add_pan_stereo_adv_to_stereo_adv(YAC_Object *_faOut, sUI _outOff, sUI _outAdv, YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sF32 _lvlL, sF32 _lvlR, sUI _numFrames) {
   sF32 *faOut = (sF32*)_faOut->yacArrayGetPointer();
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faOut[_outOff + 0u] = faOut[_outOff + 0u] + faIn[_inOff + 0u] * _lvlL;
      faOut[_outOff + 1u] = faOut[_outOff + 1u] + faIn[_inOff + 1u] * _lvlR;
      _outOff += _outAdv;
      _inOff  += _inAdv;
   }
}

sF32 YAC_CALL tksampleedit_calc_peak_mono_adv(YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _numFrames) {
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   sF32 peak = 0.0f;

   if(_numFrames > 0)
   {
      sUI k = _inOff;

      for(sUI i = 0; i < _numFrames; i++, k += _inAdv)
      {
         sF32 f = faIn[k];

         if(f < 0.0f)
            f = -f;

         if(f > peak)
            peak = f;
      }
   }

   return peak;
}

sF32 YAC_CALL tksampleedit_calc_rms_mono_adv(YAC_Object *_faIn, sUI _inOff, sUI _inAdv, sUI _numFrames) {
   const sF32 *faIn  = (const sF32*)_faIn->yacArrayGetPointer();

   sF32 rms = 0.0f;

   if(_numFrames > 0)
   {
      sUI k = _inOff;

      for(sUI i = 0; i < _numFrames; i++, k += _inAdv)
      {
         rms += faIn[k] * faIn[k];
      }

      rms = sqrtf(rms / _numFrames);
      
      rms += 0.01f;
      rms -= 0.01f;
      
   }

   return rms;
}

void YAC_CALL tksampleedit_ms_convert_dualmono(YAC_Object *_faOutL, YAC_Object *_faOutR, sUI _outOff, YAC_Object *_faInL, YAC_Object *_faInR, sUI _inOff, sF32 _lvlOut, sUI _numFrames) {

   sF32 *faOutL = (sF32*)_faOutL->yacArrayGetPointer();
   sF32 *faOutR = (sF32*)_faOutR->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      // (note) M = L+R  (*0.5)
      // (note) S = L-R  (*0.5)
      // (note) L = M+S
      // (note) R = M-S

      sF32 a = faInL[_inOff];
      sF32 b = faInR[_inOff++];

      faOutL[_outOff]   = (a + b) * _lvlOut;
      faOutR[_outOff++] = (a - b) * _lvlOut;
   }
}

void YAC_CALL tksampleedit_monomix_restore_dualmono(YAC_Object *_faMonoMixOut, YAC_Object *_faRestoreOut, YAC_Object *_faInL, YAC_Object *_faInR, YAC_Object *_faRestoreIn, sUI _numFrames) {

   sF32 *faMonoMixOut = (sF32*)_faMonoMixOut->yacArrayGetPointer();
   sF32 *faRestoreOut = (sF32*)_faRestoreOut->yacArrayGetPointer();
   const sF32 *faInL = (const sF32*)_faInL->yacArrayGetPointer();
   const sF32 *faInR = (const sF32*)_faInR->yacArrayGetPointer();
   const sF32 *faRestoreIn = (const sF32*)_faRestoreIn->yacArrayGetPointer();

   for(sUI i = 0u; i < _numFrames; i++)
   {
      faMonoMixOut[i] = (faInL[i] + faInR[i]) * 0.5f;
      faRestoreOut[i] = faRestoreIn[i];
   }
}

void YAC_CALL tksampleedit_autochop(YAC_Object *_retStartOffsets, YAC_Object *_retEndOffsets, YAC_Object *_smp, sUI _numChannels, sF32 _startThreshold, sF32 _startMinThreshold, sUI _startMinNumFramesBack, sF32 _endThreshold, sUI _endNumFramesThreshold, sF32 _endThresholdRestart, sUI _restartNumFramesThreshold) {
   if(YAC_Is_IntArray(_retStartOffsets))
   {
      if(YAC_Is_IntArray(_retEndOffsets))
      {
         if(YAC_Is_FloatArray(_smp))
         {
            YAC_CAST_ARG(YAC_IntArray, startOffsets, _retStartOffsets);
            YAC_CAST_ARG(YAC_IntArray, endOffsets, _retEndOffsets);
            YAC_CAST_ARG(YAC_FloatArray, samples, _smp);

            const sF32 *smpData = samples->elements;
            if(NULL == smpData)
               return;

            if(0 == _numChannels)
               return;

            sUI numFrames = samples->num_elements / _numChannels;
            if(0 == numFrames)
               return;

            // trace "xxx processData: numFrames="+numFrames;

            int frameIdx = 0;
            int sliceIdx = 0;
            int frameIdxBwd;

            int state = 0; // 0=find start, 1=find end

            // float lastStartMinSmp = 99999;
            int lastStartMinIdx = -1;

            sUI endNumFramesCnt;

            sBool bAllowRestart = YAC_FALSE;
            int allowRestartCnt = 0;

            YAC_Value vi;
            vi.initInt(0);

            int startIdx = 0;
            int endIdx = 0;
            sF32 smp;

            for(sUI i = 0u; i < numFrames; i++)
            {
               for(sUI chIdx = 0u; chIdx < 1u/*_numChannels*/; chIdx++)
               {
                  smp = smpData[(frameIdx*_numChannels+chIdx)];
                  if(smp < 0)
                     smp = -smp;
           
                  if(0 == state)
                  {
                     if(smp > _startThreshold)
                     {
                        if(-1 == lastStartMinIdx)
                        {
                           startIdx = frameIdx;
                        }
                        else
                        {
                           startIdx = lastStartMinIdx;
                        }

                        Dprintf("[dbg] slice %d start at %d (curNum=%u)\n", sliceIdx, startIdx, startOffsets->num_elements);
                        // if(autochop_start_offsets.numElements < maxSlices)
                        {
                           // if( (-1 == pickSlice) || (sliceIdx == pickSlice) )
                           {
                              vi.value.int_val = startIdx;
                              startOffsets->yacArraySet(NULL/*context*/, startOffsets->num_elements, &vi);
                           }
                        }
                        endNumFramesCnt = 0u;
                        state = 1;
                        lastStartMinIdx = -1;
                        allowRestartCnt = 0;
                        bAllowRestart = YAC_FALSE;
                     }
                     else if(smp > _startMinThreshold)
                     {
                        if(-1 == lastStartMinIdx)
                        {
                           // Force update (first candidate)
                           // lastStartMinSmp = smp;
                           lastStartMinIdx = frameIdx;
                        }
                        else if( lastStartMinIdx < (frameIdx - sSI(_startMinNumFramesBack)) )
                        {
                           // Force update (last start candidate is too old)
                           // lastStartMinSmp = smp;
                           lastStartMinIdx = frameIdx;
                        }
                     }
                     else if( lastStartMinIdx < (frameIdx - sSI(_startMinNumFramesBack)) )
                     {
                        // Discard last candidate (too old)
                        lastStartMinIdx = -1;
                     }
                  }
                  else
                  {
                     if(smp < _endThresholdRestart)
                     {
                        allowRestartCnt++;

                        if(allowRestartCnt >= sSI(_restartNumFramesThreshold))
                        {
                           bAllowRestart = YAC_TRUE; // b_autochop_restart/*true*/;
                        }
                     }
                     else
                     {
                        allowRestartCnt = 0;
                     }

                     if(smp < _endThreshold)
                     {
                        endNumFramesCnt++;
                        if(endNumFramesCnt >= _endNumFramesThreshold)
                        {
                           Dprintf("[dbg] slice end at %d  (after %u frames of silence)\n", frameIdx, endNumFramesCnt);

                           // if(endOffsets.numElements < maxSlices)
                           {
                              // if( (-1 == pickSlice) || (sliceIdx == pickSlice) )
                              {
                                 // Search backward and skip zero samples
                                 endIdx = frameIdx;
                                 frameIdxBwd = frameIdx;
                                 while(--frameIdxBwd > startIdx)
                                 {
                                    smp = smpData[(frameIdxBwd*_numChannels+chIdx)];
                                    if(0 != smp)
                                    {
                                       endIdx = frameIdxBwd + 1;
                                       break;
                                    }
                                 }
                                 vi.value.int_val = endIdx;
                                 endOffsets->yacArraySet(NULL/*context*/, endOffsets->num_elements, &vi);
                              }
                              sliceIdx++;
                           }
                           state = 0;
                        }                 
                     }
                     else
                     {
                        endNumFramesCnt = 0u;

                        if(bAllowRestart)
                        {
                           // trace "xxx bAllowRestart: smp="+smp+" startThreshold="+startThreshold;
                           if(smp > _startThreshold)
                           {
                              vi.value.int_val = frameIdx;
                              endOffsets->yacArraySet(NULL/*context*/, endOffsets->num_elements, &vi);

                              startIdx = frameIdx;

                              Dprintf("[dbg] slice restart at %d\n", startIdx);

                              // if(autochop_start_offsets.numElements < maxSlices)
                              {
                                 // if( (-1 == pickSlice) || (sliceIdx == pickSlice) )
                                 {
                                    vi.value.int_val = startIdx;
                                    startOffsets->yacArraySet(NULL/*context*/, startOffsets->num_elements, &vi);
                                 }
                              }
                              endNumFramesCnt = 0u;
                              //state = 1;
                              lastStartMinIdx = -1;
                              allowRestartCnt = 0;
                              bAllowRestart = YAC_FALSE;
                           }

                        }
                     }
                  }
            
               }

               frameIdx++;
            }

            if(1 == state)
            {
               // if(autochop_end_offsets.numElements < maxSlices)
               {
                  // if( (-1 == pickSlice) || (sliceIdx == pickSlice) )
                  {
                     // Search backward and skip zero samples
                     endIdx = frameIdx;
                     frameIdxBwd = frameIdx;
                     while(--frameIdxBwd > startIdx)
                     {
                        smp = smpData[(frameIdxBwd*_numChannels+0/*chIdx*/)];
                        if(0 != smp)
                        {
                           endIdx = frameIdxBwd + 1;
                           break;
                        }
                     }
                     vi.value.int_val = endIdx;
                     endOffsets->yacArraySet(NULL/*context*/, endOffsets->num_elements, &vi);
                  }
               }
            }

         } // if smp
      } // if retEndOffsets
   } // if retStartOffsets
}

sUI tksampleedit_find_zerocrossing_near(YAC_Object *_fa, sUI _chOff, sUI _numChannels, sUI _off) {
   const sF32 *fa = (const sF32*)_fa->yacArrayGetPointer();
   sUI frameIdx = 1;
   sUI k = frameIdx * _numChannels + _chOff;
   sUI numFrames = _fa->yacArrayGetNumElements() / _numChannels;
   sF32 lastVal = fa[_chOff];
   sSI bestFrameIdx = -1;
   sSI bestDist = 9999999;

   while(frameIdx < numFrames)
   {
      sF32 cVal = fa[k];

      if( ((lastVal <= 0.0f) && (cVal >= 0.0f)) ||
          ((lastVal >= 0.0f) && (cVal <= 0.0f)) 
          )
      {
         sSI cDist = sABS(sSI(frameIdx) - sSI(_off));
         if(cDist < bestDist)
         {
            bestDist = cDist;
            bestFrameIdx = frameIdx;
         }
      }

      // Next frame
      lastVal = cVal;
      frameIdx++;
      k += _numChannels;
   }

   if(-1 != bestFrameIdx)
   {
      return sUI(bestFrameIdx);
   }
   else
   {
      // No zero-crossing found
      return _off;
   }
}
