// ----
// ---- file   : clk_4.cpp
// ---- author : 
// ---- legal  : Distributed under terms of the MIT license (https://opensource.org/licenses/MIT)
// ----
// ----          Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// ----          associated documentation files (the "Software"), to deal in the Software without restriction, including
// ----          without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// ----          copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
// ----          the following conditions:
// ----
// ----          The above copyright notice and this permission notice shall be included in all copies or substantial
// ----          portions of the Software.
// ----
// ----          THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
// ----          NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// ----          IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// ----          WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// ----          SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ----
// ---- info   : auto-generated by "Cycle"
// ----           $ g++ -Wall -Wno-unused-function -Wno-unused-variable -I../../tksampler -c clk_4.cpp -o clk_4.o
// ---- created: 23Nov2024 11:05:34
// ----
// ----
// ----

// (note) pre-define CYCLE_SKIP_UI to skip all code used only for UI/editing purposes (e.g. when compiling for standalone replay)
// #define CYCLE_SKIP_UI  defined

#include <plugin.h>

#include <stdlib.h>
#include <math.h>
#include <string.h>
#define OVERSAMPLE_FACTOR  4.0f

#define PARAM_AMP                0
#define PARAM_SHIFT              1
#define PARAM_VSYNC              2
#define PARAM_ENV_SCL            3
#define NUM_PARAMS               4
#ifndef CYCLE_SKIP_UI
static const char *loc_param_names[NUM_PARAMS] = {
   "amp",                     // 0: AMP
   "shift",                   // 1: SHIFT
   "vsync",                   // 2: VSYNC
   "env_scl",                 // 3: ENV_SCL

};
#endif // CYCLE_SKIP_UI
static float loc_param_resets[NUM_PARAMS] = {
   0.5f,                      // 0: AMP
   0.0f,                      // 1: SHIFT
   0.0f,                      // 2: VSYNC
   0.5f,                      // 3: ENV_SCL

};

#define MOD_VEL                  0
#define MOD_AMP                  1
#define MOD_SHIFT                2
#define MOD_VSYNC                3
#define MOD_ENV_SCL              4
#define NUM_MODS                 5
#ifndef CYCLE_SKIP_UI
static const char *loc_mod_names[NUM_MODS] = {
   "vel",                  // 0: VEL
   "amp",                  // 1: AMP
   "shift",                // 2: SHIFT
   "vsync",                // 3: VSYNC
   "env_scl",              // 4: ENV_SCL

};
#endif // CYCLE_SKIP_UI

typedef struct clk_4_info_s {
   st_plugin_info_t base;
} clk_4_info_t;

typedef struct clk_4_shared_s {
   st_plugin_shared_t base;
   float params[NUM_PARAMS];

} clk_4_shared_t;

typedef struct clk_4_voice_s {
   st_plugin_voice_t base;
   float             sample_rate;
   float             bpm;
   float             velocity;
   float             note_speed_fixed;
   float             note_speed_cur;
   float             note_speed_inc;
   float             note_cur;
   float             note_inc;
   float             mods[NUM_MODS];
   float mod_vel_cur;
   float mod_vel_inc;
   float mod_amp_cur;
   float mod_amp_inc;
   float mod_shift_cur;
   float mod_shift_inc;
   float mod_vsync_cur;
   float mod_vsync_inc;
   float mod_env_scl_cur;
   float mod_env_scl_inc;

   short tmp1;
   unsigned short tmp2_lfsr_state;
   short tmp3_lfsr_state_signed;
   float tmp4;
   short tmp5;
   short tmp6;
   float tmp7;
   float tmp8;
   float tmp9;
   float tmp10_clk_fr_left;
   float tmp11_clk_fr_duty_left;
   float tmp12;
   float tmp13;
   float tmp14;
   short tmp15_tsq_step;
   float tmp16_tsq_fr_duty_left;
   float tmp17_tsq_last_gate;
   float tmp18_tsq_last_clk;
   float tmp19_tsq_last_clkgate;
   float tmp20_tsq_scl;
   short tmp21_nth_step;
   float tmp22_nth_last;
   float tmp23_nth_out;
   float tmp24;
   short tmp25;
   float tmp26;
   float tmp27;
   short tmp28;
   unsigned short tmp29_lfsr_state;
   short tmp30_lfsr_state_signed;
   float tmp31_sin_phase;
   float tmp32_win_phase;
   float tmp33_adsrv2_last_gate;
   float tmp34_adsrv2_level;
   short tmp35_adsrv2_segidx;
   float tmp36_adsrv2_vel;
   float tmp37;
   float tmp38;
   float tmp39_svf_lp;
   float tmp40_svf_hp;
   float tmp41_svf_bp;
   float var_x;
   float var_v_clk;
   float var_v_trig;
   float var_v_rand_1;
   float var_v_gate_pulse;
   float var_v_lpf_lin;
   float var_v_lpf;
   float sr_factor;

} clk_4_voice_t;

#define loop(X)  for(unsigned int i = 0u; i < (X); i++)
#define clamp(a,b,c) (((a)<(b))?(b):(((a)>(c))?(c):(a)))

static inline float mathLerpf(float _a, float _b, float _t) { return _a + (_b - _a) * _t; }
static inline float mathClampf(float a, float b, float c) { return (((a)<(b))?(b):(((a)>(c))?(c):(a))); }
static inline float mathMinf(float a, float b) { return (a<b)?a:b; }
static inline float mathMaxf(float a, float b) { return (a>b)?a:b; }
static inline float mathAbsMaxf(float _x, float _y) { return ( ( (_x<0.0f)?-_x:_x)>((_y<0.0f)?-_y:_y)?_x:_y ); }
static inline float mathAbsMinf(float _x, float _y) { return ( ((_x<0.0f)?-_x:_x)<((_y<0.0f)?-_y:_y)?_x:_y ); }
static inline float frac(float _x) { return _x - ((int)_x); }

static inline float winLinear(const float *_s, float _index) {
   int idx = (int)_index;
   float r = _index - (float)idx;
   return mathLerpf(_s[idx], _s[idx+1], r);
}

static float ffrac_s(float _f) { int i; if(_f >= 0.0f) { i = (int)_f; return _f - (float)i; } else { i = (int)-_f; return 1.0f - (-_f - (float)i); } }
static short curve_0[256] = {
0, 14, 28, 41, 54, 67, 79, 91, 102, 113, 124, 135, 145, 155, 165, 174, 184, 193, 202, 211, 220, 228, 237, 245, 253, 261, 269, 277, 285, 292, 300, 307, 
315, 322, 329, 336, 343, 350, 357, 364, 370, 377, 383, 390, 396, 403, 409, 415, 422, 428, 434, 440, 446, 452, 458, 464, 469, 475, 481, 487, 492, 498, 504, 509, 
515, 520, 526, 531, 537, 542, 547, 553, 558, 563, 569, 574, 579, 584, 590, 595, 600, 605, 610, 615, 620, 625, 630, 635, 640, 645, 650, 655, 660, 665, 670, 675, 
680, 685, 690, 695, 700, 705, 710, 715, 720, 725, 730, 735, 740, 745, 750, 755, 760, 765, 771, 777, 783, 789, 796, 803, 810, 818, 826, 834, 843, 851, 860, 870, 
879, 889, 899, 909, 919, 930, 940, 951, 962, 973, 985, 996, 1008, 1020, 1032, 1044, 1056, 1068, 1080, 1093, 1105, 1118, 1130, 1143, 1156, 1168, 1181, 1194, 1207, 1220, 1232, 1245, 
1258, 1271, 1284, 1296, 1309, 1322, 1334, 1347, 1360, 1372, 1385, 1397, 1409, 1422, 1434, 1446, 1458, 1470, 1482, 1493, 1505, 1517, 1528, 1539, 1551, 1562, 1573, 1584, 1595, 1605, 1616, 1627, 
1637, 1647, 1657, 1667, 1677, 1687, 1697, 1706, 1716, 1725, 1734, 1744, 1752, 1761, 1770, 1779, 1787, 1795, 1804, 1812, 1820, 1827, 1835, 1843, 1850, 1857, 1865, 1872, 1879, 1886, 1892, 1899, 
1905, 1912, 1918, 1924, 1930, 1936, 1942, 1947, 1953, 1958, 1963, 1968, 1973, 1978, 1983, 1988, 1992, 1997, 2001, 2005, 2010, 2014, 2017, 2021, 2025, 2028, 2032, 2035, 2038, 2042, 2045, 2048, 
};

#define USE_CYCLE_SINE_TBL  defined
static float cycle_sine_tbl_f[16384];

static float mathLogf(const float _x) {
   union {
      float f;
      unsigned int u;
   } bx;
   bx.f = _x;
   const unsigned int ex = bx.u >> 23;
   const signed int t = (signed int)ex - (signed int)127;
   const unsigned int s = (t < 0) ? (-t) : t;
   bx.u = 1065353216u | (bx.u & 8388607u);
   return
      -1.7417939f + (2.8212026f + (-1.4699568f + (0.44717955f - 0.056570851f * bx.f)*bx.f)*bx.f)*bx.f
      + 0.6931471806f * t;
}

static float mathPowerf(float _x, float _y) {
   float r;
   if(_y != 0.0f)
   {
      if(_x < 0.0f)
      {
         r = (float)( -expf(_y*mathLogf(-_x)) );
      }
      else if(_x > 0.0f)
      {
         r = (float)( expf(_y*mathLogf(_x)) );
      }
      else
      {
         r = 0.0f;
      }
   }
   else
   {
      r = 1.0f;
   }
   return Dstplugin_fix_denorm_32(r);
}

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

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

static void loc_init(st_plugin_voice_t *_voice);
static void loc_prepare(st_plugin_voice_t *_voice);

void loc_init(st_plugin_voice_t *_voice) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   ST_PLUGIN_VOICE_SHARED_CAST(clk_4_shared_t);

   float out = 0.0f;
   (void)out;
   // -------- lane "init" modIdx=0 class=fsr
   
   // -- mod="fsr" dstVar=out
   voice->tmp1/*i2f*/ = (int)(out * 2048);  // IntFallback: F2I
   voice->tmp2_lfsr_state ^= voice->tmp2_lfsr_state >> 7;
   voice->tmp2_lfsr_state ^= voice->tmp2_lfsr_state << 9;
   voice->tmp2_lfsr_state ^= voice->tmp2_lfsr_state >> 13;
   voice->tmp3_lfsr_state_signed = (voice->tmp2_lfsr_state & 65520);
   voice->tmp1/*i2f*/ = voice->tmp3_lfsr_state_signed >> 4;
   out = voice->tmp1/*i2f*/ / ((float)(2048));  // IntFallback: I2F
   
   // -- mod="sto v_rand_1" dstVar=out
   voice->var_v_rand_1 = out;
   
   // -- mod="set v_gate_pulse" dstVar=out
   voice->var_v_gate_pulse = 1;
   
   // -- mod="set v_lpf_lin" dstVar=out
   voice->var_v_lpf_lin = 1;
} /* end init */

void loc_prepare(st_plugin_voice_t *_voice) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   ST_PLUGIN_VOICE_SHARED_CAST(clk_4_shared_t);

   float out = 0.0f;
   (void)out;
   // -------- lane "prepare" modIdx=0 class=$v_lpf_lin
   
   // -- mod="$v_lpf_lin" dstVar=out
   out = voice->var_v_lpf_lin;
   
   // -- mod="0.998" dstVar=out
   out *= 0.998f;
   
   // -- mod="sto v_lpf_lin" dstVar=out
   voice->var_v_lpf_lin = out;
   
   // -- mod="lut" dstVar=out
   voice->tmp4/*lut_f*/ = (out * 256);
   voice->tmp5/*lut_idx_a*/ = (int)voice->tmp4/*lut_f*/;
   voice->tmp7/*lut_frac*/ = voice->tmp4/*lut_f*/ - (float)voice->tmp5/*lut_idx_a*/;
   voice->tmp8/*lut_a*/ = curve_0[( (unsigned int)voice->tmp5/*lut_idx_a*/      ) & 255] * (1.0f / 2048);
   voice->tmp9/*lut_b*/ = curve_0[(((unsigned int)voice->tmp5/*lut_idx_a*/) + 1u) & 255] * (1.0f / 2048);
   out = voice->tmp8/*lut_a*/ + (voice->tmp9/*lut_b*/ - voice->tmp8/*lut_a*/) * voice->tmp7/*lut_frac*/;
   
   // -- mod="p2s" dstVar=out
   out  = (mathPowerf(2.0f, out * 7.0f) - 1.0f);
   out *= 0.00787402f;
   
   // -- mod="sto v_lpf" dstVar=out
   voice->var_v_lpf = out;
} /* end prepare */


#ifndef CYCLE_SKIP_UI
static unsigned int loc_copy_chars(char *_d, const unsigned int _dSize, const char *_s) {
   unsigned int r = 0u;
   if(NULL != _d)
   {
      // Write min(dSize,len(s)) characters to 'd'
      char *d = _d;
      if(NULL != _s)
      {
         const char *s = _s;
         for(;;)
         {
            if(r < _dSize)
            {
               char c = s[r++];
               *d++ = c;
               if(0 == c)
                  break;
            }
            else
            {
               if(r > 0u)
                  d[-1] = 0;  // force ASCIIZ
               break;
            }
         }
      }
      else
      {
         if(_dSize > 0u)
         {
            _d[0] = 0;
            r = 1u;
         }
      }
   }
   else
   {
      // Return total length of source string
      if(NULL != _s)
      {
         const char *s = _s;
         for(;;)
         {
            char c = s[r++];
            if(0 == c)
               break;
         }
      }
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static unsigned int loc_copy_floats(float              *_d,
                                    const unsigned int  _dSize,
                                    const float        *_s,
                                    const unsigned int  _sNum
                                    ) {
   unsigned int r = 0u;
   if(NULL != _d)
   {
      // Write min(dSize,sNum) characters to 'd'
      if(NULL != _s)
      {
         unsigned int num = (_sNum > _dSize) ? _dSize : _sNum;
         memcpy((void*)_d, (const void*)_s, num * sizeof(float));
         r = num;
      }
   }
   else
   {
      // Query total number of presets
      r = _sNum;
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static const char *ST_PLUGIN_API loc_get_param_name(st_plugin_info_t *_info,
                                                    unsigned int      _paramIdx
                                                    ) {
   (void)_info;
   return loc_param_names[_paramIdx];
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static const char *ST_PLUGIN_API loc_get_param_group_name(st_plugin_info_t *_info,
                                                          unsigned int      _paramGroupIdx
                                                          ) {
   (void)_info;
   const char *r = NULL;
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static unsigned int ST_PLUGIN_API loc_get_param_group_idx(st_plugin_info_t *_info,
                                                          unsigned int      _paramIdx
                                                          ) {
   (void)_info;
   unsigned int r = ~0u;
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static const char *ST_PLUGIN_API loc_get_param_section_name(st_plugin_info_t *_info,
                                                            unsigned int      _paramSectionIdx
                                                            ) {
   (void)_info;
   const char *r = NULL;
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static unsigned int ST_PLUGIN_API loc_get_param_section_idx(st_plugin_info_t *_info,
                                                            unsigned int      _paramIdx
                                                            ) {
   (void)_info;
   unsigned int r = ~0u;
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static float ST_PLUGIN_API loc_get_param_reset(st_plugin_info_t *_info,
                                               unsigned int      _paramIdx
                                               ) {
   (void)_info;
   return loc_param_resets[_paramIdx];
}
#endif // CYCLE_SKIP_UI

static float ST_PLUGIN_API loc_get_param_value(st_plugin_shared_t *_shared,
                                               unsigned int        _paramIdx
                                               ) {
   ST_PLUGIN_SHARED_CAST(clk_4_shared_t);
   return shared->params[_paramIdx];
}

static void ST_PLUGIN_API loc_set_param_value(st_plugin_shared_t *_shared,
                                              unsigned int        _paramIdx,
                                              float               _value
                                              ) {
   ST_PLUGIN_SHARED_CAST(clk_4_shared_t);
   shared->params[_paramIdx] = _value;
}

#ifndef CYCLE_SKIP_UI
static unsigned int ST_PLUGIN_API loc_query_dynamic_param_preset_values(st_plugin_shared_t *_shared,
                                                                        const unsigned int  _paramIdx,
                                                                        float              *_retValues,
                                                                        const unsigned int  _retValuesSize
                                                                        ) {
   ST_PLUGIN_SHARED_CAST(clk_4_shared_t);
   unsigned int r = 0u;
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static unsigned int ST_PLUGIN_API loc_query_dynamic_param_preset_name(st_plugin_shared_t *_shared,
                                                                      const unsigned int  _paramIdx,
                                                                      const unsigned int  _presetIdx,
                                                                      char               *_retBuf,
                                                                      const unsigned int  _retBufSize
                                                                      ) {
   ST_PLUGIN_SHARED_CAST(clk_4_shared_t);
   unsigned int r = 0u;
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static unsigned int ST_PLUGIN_API loc_get_array_param_size(st_plugin_info_t   *_info,
                                                           const unsigned int  _paramIdx
                                                           ) {
   unsigned int r = 0u;
   switch(_paramIdx)
   {
      default:
         break;
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static unsigned int ST_PLUGIN_API loc_get_array_param_num_variations(st_plugin_info_t   *_info,
                                                                     const unsigned int  _paramIdx
                                                                     ) {
   unsigned int r = 0u;
   switch(_paramIdx)
   {
      default:
         break;
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static float * ST_PLUGIN_API loc_get_array_param_variation_ptr(st_plugin_shared_t *_shared,
                                                               const unsigned int  _paramIdx,
                                                               const unsigned int  _variationIdx
                                                               ) {
   ST_PLUGIN_SHARED_CAST(clk_4_shared_t);
   float *r = NULL;
   switch(_paramIdx)
   {
      default:
         break;
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static void ST_PLUGIN_API loc_set_array_param_edit_variation_idx(st_plugin_shared_t *_shared,
                                                                 const unsigned int  _paramIdx,
                                                                 const int           _variationIdx
                                                                 ) {
   ST_PLUGIN_SHARED_CAST(clk_4_shared_t);
   switch(_paramIdx)
   {
      default:
         break;
   }
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static const char *ST_PLUGIN_API loc_get_array_param_element_name(st_plugin_info_t   *_info,
                                                                  const unsigned int  _paramIdx,
                                                                  const unsigned int  _elementIdx
                                                                  ) {
   ST_PLUGIN_INFO_CAST(clk_4_info_t);
   const char *r = NULL;
   switch(_paramIdx)
   {
      default:
         break;
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static int ST_PLUGIN_API loc_get_array_param_element_value_range(st_plugin_info_t   *_info,
                                                                 const unsigned int  _paramIdx,
                                                                 const unsigned int  _elementIdx,
                                                                 float              *_retStorageMin,
                                                                 float              *_retStorageMax,
                                                                 float              *_retDisplayMin,
                                                                 float              *_retDisplayMax,
                                                                 unsigned int       *_retDisplayPrecision
                                                                 ) {
   (void)_elementIdx;
   switch(_paramIdx)
   {
      default:
         break;
   }
   return 0;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static float ST_PLUGIN_API loc_get_array_param_element_reset(st_plugin_info_t   *_info,
                                                             const unsigned int  _paramIdx,
                                                             const unsigned int  _elementIdx
                                                             ) {
   ST_PLUGIN_INFO_CAST(clk_4_info_t);
   float r = -999999.0f/*INVALID_VALUE*/;
   switch(_paramIdx)
   {
      default:
         break;
   }
   return r;
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static void ST_PLUGIN_API loc_update_lut(st_plugin_info_t   *_info,
                                         unsigned int        _idx,
                                         const float        *_src,
                                         const unsigned int  _srcNumElements
                                         ) {
   (void)_info;
   unsigned int copySz = 0u;
   (void)copySz;
   switch(_idx)
   {
      default:
         break;
      case 0: // "lpf_dcy"
         copySz = Dstplugin_min(_srcNumElements, 256);
         for(unsigned int i = 0u; i < copySz; i++)
            curve_0[i] = (short)(_src[i] * 2048);
         break;
   }
}
#endif // CYCLE_SKIP_UI

#ifndef CYCLE_SKIP_UI
static const char *ST_PLUGIN_API loc_get_mod_name(st_plugin_info_t *_info,
                                                  unsigned int      _modIdx
                                                  ) {
   (void)_info;
   return loc_mod_names[_modIdx];
}
#endif // CYCLE_SKIP_UI

static void ST_PLUGIN_API loc_set_sample_rate(st_plugin_voice_t *_voice,
                                              float              _sampleRate
                                              ) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   if(_sampleRate != voice->sample_rate)
   {
      voice->sr_factor = 12000.0f / _sampleRate;
   }
   voice->sample_rate = _sampleRate;
}

static void ST_PLUGIN_API loc_set_bpm(st_plugin_voice_t *_voice,
                                      float              _bpm
                                      ) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   voice->bpm = _bpm;
}

static void ST_PLUGIN_API loc_note_on(st_plugin_voice_t  *_voice,
                                      int                 _bGlide,
                                      unsigned char       _note,
                                      float               _vel
                                      ) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   ST_PLUGIN_VOICE_SHARED_CAST(clk_4_shared_t);
   (void)_bGlide;
   (void)_note;
   (void)_vel;
   if(!_bGlide)
   {
      memset((void*)voice->mods, 0, sizeof(voice->mods));
#ifdef OVERSAMPLE_FACTOR
      voice->note_speed_fixed = (261.63f/*C-5*/ / (voice->sample_rate * OVERSAMPLE_FACTOR));
#else
      voice->note_speed_fixed = (261.63f/*C-5*/ / voice->sample_rate);
#endif // OVERSAMPLE_FACTOR
      voice->velocity = _vel;
   voice->tmp29_lfsr_state = 17545 * (voice->base.voice_idx + 1u);
      voice->tmp3_lfsr_state_signed = 0;
      voice->tmp10_clk_fr_left = 0.0f;
      voice->tmp11_clk_fr_duty_left = 0.0f;
      voice->tmp15_tsq_step = 0;
      voice->tmp16_tsq_fr_duty_left = 0.0f;
      voice->tmp17_tsq_last_gate = 0.0f;
      voice->tmp18_tsq_last_clk = 0.0f;
      voice->tmp19_tsq_last_clkgate = 0.0f;
      voice->tmp20_tsq_scl = 1.0f;
      voice->tmp21_nth_step = 0;
      voice->tmp22_nth_last = 0.0f;
      voice->tmp23_nth_out = 0.0f;
      voice->tmp30_lfsr_state_signed = 0;
      voice->tmp31_sin_phase = 0.0f;
      voice->tmp32_win_phase = 0.0f;
      voice->tmp33_adsrv2_last_gate = 0.0f;
      voice->tmp35_adsrv2_segidx = 4;
      voice->tmp39_svf_lp = 0.0f;
      voice->tmp40_svf_hp = 0.0f;
      voice->tmp41_svf_bp = 0.0f;
      voice->var_x = 0.0f;
      voice->var_v_clk = 0.0f;
      voice->var_v_trig = 0.0f;
      voice->var_v_rand_1 = 0.0f;
      voice->var_v_gate_pulse = 0.0f;
      voice->var_v_lpf_lin = 0.0f;
      voice->var_v_lpf = 0.0f;
      loc_init(&voice->base);
   }
}

static void ST_PLUGIN_API loc_set_mod_value(st_plugin_voice_t *_voice,
                                            unsigned int       _modIdx,
                                            float              _value,
                                            unsigned           _frameOffset
                                            ) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   (void)_frameOffset;
   voice->mods[_modIdx] = _value;
}

static void ST_PLUGIN_API loc_prepare_block(st_plugin_voice_t *_voice,
                                            unsigned int       _numFrames,
                                            float              _freqHz,
                                            float              _note,
                                            float              _vol,
                                            float              _pan
                                            ) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   ST_PLUGIN_VOICE_SHARED_CAST(clk_4_shared_t);
   (void)_note;
   (void)_vol;
   (void)_pan;

#ifdef OVERSAMPLE_FACTOR
   float noteSpeed = _freqHz / (voice->sample_rate * OVERSAMPLE_FACTOR);
#else
   float noteSpeed = _freqHz / voice->sample_rate;
#endif // OVERSAMPLE_FACTOR

   float modvel          = voice->mods[MOD_VEL          ];
   float modamp          = shared->params[PARAM_AMP         ]                       + voice->mods[MOD_AMP          ];
   float modshift        = shared->params[PARAM_SHIFT       ]                       + voice->mods[MOD_SHIFT        ];
   float modvsync        = shared->params[PARAM_VSYNC       ]                       + voice->mods[MOD_VSYNC        ];
   float modenv_scl      = shared->params[PARAM_ENV_SCL     ]                       + voice->mods[MOD_ENV_SCL      ];

   if(_numFrames > 0u)
   {
      // lerp
#ifdef OVERSAMPLE_FACTOR
      float recBlockSize = (1.0f / (_numFrames * OVERSAMPLE_FACTOR));
#else
      float recBlockSize = (1.0f / _numFrames);
#endif // OVERSAMPLE_FACTOR
      voice->note_speed_inc = (noteSpeed - voice->note_speed_cur) * recBlockSize;
      voice->note_inc       = (_note - voice->note_cur) * recBlockSize;
      voice->mod_vel_inc          = (modvel             - voice->mod_vel_cur           ) * recBlockSize;
      voice->mod_amp_inc          = (modamp             - voice->mod_amp_cur           ) * recBlockSize;
      voice->mod_shift_inc        = (modshift           - voice->mod_shift_cur         ) * recBlockSize;
      voice->mod_vsync_inc        = (modvsync           - voice->mod_vsync_cur         ) * recBlockSize;
      voice->mod_env_scl_inc      = (modenv_scl         - voice->mod_env_scl_cur       ) * recBlockSize;
      loc_prepare(&voice->base);
   }
   else
   {
      // initial params/modulation (first block, not rendered)
      voice->note_speed_cur = noteSpeed;
      voice->note_speed_inc = 0.0f;
      voice->note_cur       = _note;
      voice->note_inc       = 0.0f;
      voice->mod_vel_cur          = modvel;
      voice->mod_vel_inc          = 0.0f;
      voice->mod_amp_cur          = modamp;
      voice->mod_amp_inc          = 0.0f;
      voice->mod_shift_cur        = modshift;
      voice->mod_shift_inc        = 0.0f;
      voice->mod_vsync_cur        = modvsync;
      voice->mod_vsync_inc        = 0.0f;
      voice->mod_env_scl_cur      = modenv_scl;
      voice->mod_env_scl_inc      = 0.0f;
      loc_prepare(&voice->base);
   }

   // printf("xxx note_cur=%f\n", voice->note_cur);
   // printf("xxx prepare_block: numFrames=%u moda=%f\n", _numFrames, moda);
   // printf("xxx voice->voicebus_idx_0=%u voice->base.voice_bus_read_offset=%u\n", voice->voicebus_idx_0, voice->base.voice_bus_read_offset);
}

static void ST_PLUGIN_API loc_process_replace(st_plugin_voice_t  *_voice,
                                              int                 _bMonoIn,
                                              const float        *_samplesIn,
                                              float              *_samplesOut,
                                              unsigned int        _numFrames
                                              ) {
   (void)_bMonoIn;
   (void)_samplesIn;

   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);
   ST_PLUGIN_VOICE_SHARED_CAST(clk_4_shared_t);

   // Mono output (replicate left to right channel)
   unsigned int j = 0u;
#ifndef STEREO
   unsigned int jStep = _bMonoIn ? 1u : 2u;
#endif // STEREO
   unsigned int k = 0u;
   for(unsigned int i = 0u; i < _numFrames; i++)
   {
      const float inL = _samplesIn[j];
#ifdef STEREO
      float outL;
      float outR;
      const float inR = _samplesIn[j + 1u];
#endif // STEREO
      float out;
#ifdef OVERSAMPLE_FACTOR
#ifdef STEREO
      float outOSL = 0.0f;
      float outOSR = 0.0f;
#else
      float outOS = 0.0f;
#endif // STEREO
#endif // OVERSAMPLE_FACTOR

#ifdef OVERSAMPLE_FACTOR
      for(unsigned int osi = 0u; osi < OVERSAMPLE_FACTOR; osi++)
#endif // OVERSAMPLE_FACTOR
      {
#ifdef STEREO
         out = outL = inL;
         outR = inR;
#else
         out = inL;
#endif // STEREO
         float tmp_f;
         float tmp2_f;
         
         // ========
         // ======== lane "out" modIdx=0 class=clk
         // ========
         
         // -- mod="clk" dstVar=out
         if(voice->tmp10_clk_fr_left <= 0.0f)
         {
            voice->tmp12/*fr_duty_dur*/ = 5;
            #ifdef OVERSAMPLE_FACTOR
            voice->tmp12/*fr_duty_dur*/ = voice->tmp12/*fr_duty_dur*/ * ((voice->sample_rate * OVERSAMPLE_FACTOR) * (1.0f / 1000.0f));
            #else // OVERSAMPLE_FACTOR
            voice->tmp12/*fr_duty_dur*/ = voice->tmp12/*fr_duty_dur*/ * (voice->sample_rate * (1.0f / 1000.0f));
            #endif // OVERSAMPLE_FACTOR
            if(voice->tmp12/*fr_duty_dur*/ < 1.0f)
               voice->tmp12/*fr_duty_dur*/ = 1.0f;
            #ifdef OVERSAMPLE_FACTOR
            voice->tmp13/*fr_ival*/ = (voice->sample_rate * OVERSAMPLE_FACTOR) / 2.08333f;
            #else // OVERSAMPLE_FACTOR
            voice->tmp13/*fr_ival*/ = voice->sample_rate / 2.08333f;
            #endif // OVERSAMPLE_FACTOR
            
            // ---- mod="clk" input "div" seq 1/1
            
            // -- mod="0.0625" dstVar=voice->tmp14/*div*/
            voice->tmp14/*div*/ = 0.0625f;
            voice->tmp13/*fr_ival*/ *= voice->tmp14/*div*/;
            voice->tmp13/*fr_ival*/ -= 1.0f;
            if(voice->tmp13/*fr_ival*/ < 0.0f)
               voice->tmp13/*fr_ival*/ = 0.0f;
            voice->tmp10_clk_fr_left += voice->tmp13/*fr_ival*/;
            voice->tmp11_clk_fr_duty_left = voice->tmp12/*fr_duty_dur*/;
            out = 1.0f;
         }
         else
         {
            voice->tmp10_clk_fr_left -= 1.0f;
            if(voice->tmp11_clk_fr_duty_left > 0.0f)
            {
               if(voice->tmp10_clk_fr_left <= 0.0f)
                  out = 0.0f;
               else
                  out = 1.0f;
            }
            else
              out = 0.0f;
            voice->tmp11_clk_fr_duty_left -= 1.0f;
         }
         
         // -- mod="sto v_clk" dstVar=out
         voice->var_v_clk = out;
         
         // -- mod="seq" dstVar=out
         voice->tmp12/*tsq_gate*/ = 0.0f;
         voice->tmp13/*tsq_clk*/ = 0.0f;
         
         // ---- mod="seq" input "cg" seq 1/1
         
         // -- mod="$v_clk" dstVar=voice->tmp14/*tsq_clkgate*/
         voice->tmp14/*tsq_clkgate*/ = voice->var_v_clk;
         
         // -- mod="nth" dstVar=voice->tmp14/*tsq_clkgate*/
         if(voice->tmp14/*tsq_clkgate*/ > 0.001f && voice->tmp22_nth_last <= 0.001f)
         {
            if(0 == ((voice->tmp21_nth_step++ + 0) & 3))
            {
               voice->tmp23_nth_out = voice->tmp14/*tsq_clkgate*/;
            }
         }
         else if(voice->tmp14/*tsq_clkgate*/ <= 0.001f && voice->tmp22_nth_last > 0.001f)
         {
            voice->tmp23_nth_out = voice->tmp14/*tsq_clkgate*/;
         }
         voice->tmp22_nth_last = voice->tmp14/*tsq_clkgate*/;
         voice->tmp14/*tsq_clkgate*/ = voice->tmp23_nth_out;
         voice->tmp12/*tsq_gate*/ += voice->tmp14/*tsq_clkgate*/;
         voice->tmp13/*tsq_clk*/ += voice->tmp14/*tsq_clkgate*/;
         if(voice->tmp12/*tsq_gate*/ > 0.001f && voice->tmp17_tsq_last_gate <= 0.001f)
         {
            
            // ---- mod="seq" input "dur" seq 1/1
            
            // -- mod="50" dstVar=voice->tmp24/*tsq_fr_duty_dur*/
            voice->tmp24/*tsq_fr_duty_dur*/ = 50.0f;
            #ifdef OVERSAMPLE_FACTOR
            voice->tmp24/*tsq_fr_duty_dur*/ = voice->tmp24/*tsq_fr_duty_dur*/ * ((voice->sample_rate * OVERSAMPLE_FACTOR) * (1.0f / 1000.0f));
            #else // OVERSAMPLE_FACTOR
            voice->tmp24/*tsq_fr_duty_dur*/ = voice->tmp24/*tsq_fr_duty_dur*/ * (voice->sample_rate * (1.0f / 1000.0f));
            #endif // OVERSAMPLE_FACTOR
            if(voice->tmp24/*tsq_fr_duty_dur*/ < 1.0f)
               voice->tmp24/*tsq_fr_duty_dur*/ = 1.0f;
            
            // ---- mod="seq" input "shift" seq 1/1
            
            // -- mod="$m_shift" dstVar=voice->tmp26/*tsq_shift*/
            voice->tmp26/*tsq_shift*/ = voice->mod_shift_cur;
            
            // -- mod="$v_rand_1" dstVar=voice->tmp26/*tsq_shift*/
            voice->tmp27/*seq*/ = voice->tmp26/*tsq_shift*/;
            voice->tmp27/*seq*/ = voice->var_v_rand_1;
            
            // -- mod="fma" dstVar=voice->tmp27/*seq*/
            voice->tmp27/*seq*/ = (voice->tmp27/*seq*/ * 0.5f) + 0.5f;
            
            // -- mod="-0.3" dstVar=voice->tmp27/*seq*/
            voice->tmp27/*seq*/ *= -0.3f;
            // (ignore last output via '.')
            voice->tmp25/*tsq_idx*/ = (voice->tmp15_tsq_step + (int)(voice->tmp26/*tsq_shift*/ * 24)) % 24;
            if(voice->tmp15_tsq_step < 24)
            {
               if(3230025 & (1 << voice->tmp25/*tsq_idx*/))
               {
                  
                  // ---- mod="seq" input "ontrig" seq 1/1
                  
                  // -- mod="fsr" dstVar=voice->tmp20_tsq_scl
                  voice->tmp28/*i2f*/ = (int)(voice->tmp20_tsq_scl * 2048);  // IntFallback: F2I
                  voice->tmp29_lfsr_state ^= voice->tmp29_lfsr_state >> 7;
                  voice->tmp29_lfsr_state ^= voice->tmp29_lfsr_state << 9;
                  voice->tmp29_lfsr_state ^= voice->tmp29_lfsr_state >> 13;
                  voice->tmp30_lfsr_state_signed = (voice->tmp29_lfsr_state & 65520);
                  voice->tmp28/*i2f*/ = voice->tmp30_lfsr_state_signed >> 4;
                  voice->tmp20_tsq_scl = voice->tmp28/*i2f*/ / ((float)(2048));  // IntFallback: I2F
                  
                  // -- mod="fma" dstVar=voice->tmp20_tsq_scl
                  voice->tmp20_tsq_scl = (voice->tmp20_tsq_scl * 0.15f) + 0.85f;
                  voice->tmp16_tsq_fr_duty_left = voice->tmp24/*tsq_fr_duty_dur*/;
               }
            }
         }
         if(voice->tmp13/*tsq_clk*/ > 0.001f && voice->tmp18_tsq_last_clk <= 0.001f)
         {
            voice->tmp15_tsq_step = (voice->tmp15_tsq_step + 1) % 24;
         }
         voice->tmp17_tsq_last_gate = voice->tmp12/*tsq_gate*/;
         voice->tmp18_tsq_last_clk = voice->tmp13/*tsq_clk*/;
         out = ((float)(voice->tmp16_tsq_fr_duty_left > 0.0f)) * voice->tmp20_tsq_scl;
         voice->tmp16_tsq_fr_duty_left -= 1.0f;
         
         // -- mod="sto v_trig" dstVar=out
         voice->var_v_trig = out;
         
         // -- mod="sin" dstVar=out
         voice->tmp13/*sin_freq*/ = 1;
         voice->tmp12/*sin_speed*/ = voice->note_speed_cur * voice->tmp13/*sin_freq*/;
         
         // ---- mod="sin" input "vsync" seq 1/1
         
         // -- mod="$m_vsync" dstVar=voice->tmp26/*vsync*/
         voice->tmp26/*vsync*/ = voice->mod_vsync_cur;
         
         // -- mod="pow" dstVar=voice->tmp26/*vsync*/
         voice->tmp26/*vsync*/ = voice->tmp26/*vsync*/ * voice->tmp26/*vsync*/;
         
         // -- mod="fma" dstVar=voice->tmp26/*vsync*/
         voice->tmp26/*vsync*/ = (voice->tmp26/*vsync*/ * 7.0f) + 1.0f;
         
         // -- mod="$v_rand_1" dstVar=voice->tmp26/*vsync*/
         voice->tmp27/*seq*/ = voice->var_v_rand_1;
         
         // -- mod="0.15" dstVar=voice->tmp27/*seq*/
         voice->tmp27/*seq*/ *= 0.15f;
         voice->tmp26/*vsync*/ += voice->tmp27/*seq*/;
         voice->tmp13/*sin_tmp*/ = (voice->tmp31_sin_phase);
         voice->tmp13/*sin_tmp*/ = ffrac_s(voice->tmp13/*sin_tmp*/);
         out = cycle_sine_tbl_f[(unsigned short)(16384 * voice->tmp13/*sin_tmp*/)&16383u];
         voice->tmp27/*window*/ = voice->tmp32_win_phase * 2.0f;
         if(voice->tmp27/*window*/ > 1.0f) voice->tmp27/*window*/ = 2.0f - voice->tmp27/*window*/;
         out *= voice->tmp27/*window*/;
         voice->tmp31_sin_phase = ffrac_s(voice->tmp31_sin_phase + voice->tmp12/*sin_speed*/ * voice->tmp26/*vsync*/);
         tmp_f = voice->tmp32_win_phase;
         voice->tmp32_win_phase = ffrac_s(voice->tmp32_win_phase + voice->tmp12/*sin_speed*/);
         if(tmp_f > voice->tmp32_win_phase) voice->tmp31_sin_phase = voice->tmp32_win_phase * voice->tmp26/*vsync*/; 
         
         // -- mod="$v_trig" dstVar=out
         voice->tmp12/*seq*/ = voice->var_v_trig;
         
         // -- mod="adsr" dstVar=voice->tmp12/*seq*/
         
         // ---- mod="adsr" input "scl" seq 1/1
         
         // -- mod="0.001" dstVar=voice->tmp37/*addsr_scl*/
         voice->tmp37/*addsr_scl*/ = 0.001f;
         
         // -- mod="$m_env_scl" dstVar=voice->tmp37/*addsr_scl*/
         voice->tmp38/*seq*/ = voice->mod_env_scl_cur;
         
         // -- mod="fma" dstVar=voice->tmp38/*seq*/
         voice->tmp38/*seq*/ = (voice->tmp38/*seq*/ * 2.0f) + -1.0f;
         
         // -- mod="bts" dstVar=voice->tmp38/*seq*/
         voice->tmp38/*seq*/ = loc_bipolar_to_scale(voice->tmp38/*seq*/, 2.0f, 2.0f);
         voice->tmp37/*addsr_scl*/ *= voice->tmp38/*seq*/;
         voice->tmp37/*addsr_scl*/ *= voice->sr_factor;
         
         // ---- mod="adsr" input "s" seq 1/1
         
         // -- mod="0.4" dstVar=voice->tmp26/*adsrv2_suslvl*/
         voice->tmp26/*adsrv2_suslvl*/ = 0.4f;
         if(voice->tmp12/*seq*/ > 0.001f && voice->tmp33_adsrv2_last_gate <= 0.001f)
         {
            voice->tmp36_adsrv2_vel = voice->tmp12/*seq*/;
            voice->tmp35_adsrv2_segidx = 0;
            voice->tmp33_adsrv2_last_gate = 1.0f;
         }
         else if(voice->tmp12/*seq*/ <= 0.001f && voice->tmp33_adsrv2_last_gate > 0.001f)
         {
            voice->tmp35_adsrv2_segidx = 3;
            voice->tmp33_adsrv2_last_gate = 0.0f;
         }
         switch(voice->tmp35_adsrv2_segidx)
         {
            case 0: // a
               
               // ---- mod="adsr" input "a" seq 1/1
               
               // -- mod="0.5" dstVar=voice->tmp13/*adsrv2_rate*/
               voice->tmp13/*adsrv2_rate*/ = 0.5f;
               if(voice->tmp13/*adsrv2_rate*/ < 1.0f)
               {
                  if(-1 > 0.0f)
                  {
                     voice->tmp13/*adsrv2_rate*/ *= 0.001f + mathPowerf(voice->tmp34_adsrv2_level, -1);
                  }
                  else if(-1 < 0.0f)
                  {
                     voice->tmp13/*adsrv2_rate*/ *= 1.0f / (0.001f + mathPowerf(voice->tmp34_adsrv2_level, -(-1)));
                  }
                  voice->tmp34_adsrv2_level += voice->tmp13/*adsrv2_rate*/ * voice->tmp37/*addsr_scl*/;
                  if(voice->tmp34_adsrv2_level >= 1.0f)
                  {
                     voice->tmp34_adsrv2_level = 1.0f;
                     voice->tmp35_adsrv2_segidx++;
                  }
               }
               else
               {
                  voice->tmp34_adsrv2_level = 1.0f;
                  voice->tmp35_adsrv2_segidx++;
               }
               break;
         
            case 1: // d
               
               // ---- mod="adsr" input "d" seq 1/1
               
               // -- mod="0.6" dstVar=voice->tmp13/*adsrv2_rate*/
               voice->tmp13/*adsrv2_rate*/ = 0.6f;
               if(2 > 0.0f)
               {
                  voice->tmp13/*adsrv2_rate*/ *= 0.001f + mathPowerf(voice->tmp34_adsrv2_level, 2);
               }
               else if(2 < 0.0f)
               {
                  voice->tmp13/*adsrv2_rate*/ *= 1.0f / (0.001f + mathPowerf(voice->tmp34_adsrv2_level, -(2)));
               }
               voice->tmp34_adsrv2_level -= voice->tmp13/*adsrv2_rate*/ * voice->tmp37/*addsr_scl*/;
               if(voice->tmp34_adsrv2_level <= voice->tmp26/*adsrv2_suslvl*/)
               {
                  voice->tmp34_adsrv2_level = voice->tmp26/*adsrv2_suslvl*/;
                  voice->tmp35_adsrv2_segidx++;
               }
               break;
         
            case 2: // s
               voice->tmp34_adsrv2_level = voice->tmp26/*adsrv2_suslvl*/;
               break;
         
            case 3: // r
               
               // ---- mod="adsr" input "r" seq 1/1
               
               // -- mod="0.6" dstVar=voice->tmp13/*adsrv2_rate*/
               voice->tmp13/*adsrv2_rate*/ = 0.6f;
               if(2 > 0.0f)
               {
                  voice->tmp13/*adsrv2_rate*/ *= 0.001f + mathPowerf(voice->tmp34_adsrv2_level, 2);
               }
               else if(2 < 0.0f)
               {
                  voice->tmp13/*adsrv2_rate*/ *= 1.0f / (0.001f + mathPowerf(voice->tmp34_adsrv2_level, -(2)));
               }
               voice->tmp34_adsrv2_level -= voice->tmp13/*adsrv2_rate*/ * voice->tmp37/*addsr_scl*/;
               if(voice->tmp34_adsrv2_level <= 0.0f)
               {
                  voice->tmp34_adsrv2_level = 0.0f;
                  voice->tmp35_adsrv2_segidx++;
               }
               break;
         
            default:
            case 4: // <end>
               break;
         }
         voice->tmp12/*seq*/ = voice->tmp34_adsrv2_level * voice->tmp36_adsrv2_vel;
         
         // -- mod="pow" dstVar=voice->tmp12/*seq*/
         voice->tmp12/*seq*/ = voice->tmp12/*seq*/ * voice->tmp12/*seq*/;
         out *= voice->tmp12/*seq*/;
         
         // -- mod="$m_amp" dstVar=out
         voice->tmp12/*seq*/ = voice->mod_amp_cur;
         
         // -- mod="2" dstVar=voice->tmp12/*seq*/
         voice->tmp12/*seq*/ *= 2.0f;
         out *= voice->tmp12/*seq*/;
         
         // -- mod="svf" dstVar=out
         
         // ---- mod="svf" input "freq" seq 1/1
         
         // -- mod="$v_lpf" dstVar=voice->tmp12/*addsr_freq*/
         voice->tmp12/*addsr_freq*/ = voice->var_v_lpf;
         voice->tmp12/*addsr_freq*/ *= voice->sr_factor;
         voice->tmp39_svf_lp = voice->tmp39_svf_lp + (voice->tmp41_svf_bp * voice->tmp12/*addsr_freq*/);
         voice->tmp40_svf_hp = out - voice->tmp39_svf_lp - (voice->tmp41_svf_bp * 1.0f);
         voice->tmp41_svf_bp = voice->tmp41_svf_bp + (voice->tmp40_svf_hp * voice->tmp12/*addsr_freq*/);
         out = voice->tmp39_svf_lp;
         
         // -- mod="clp" dstVar=out
         if(out > 0.999f) out = 0.999f;
         else if(out < -0.999f) out = -0.999f;
         
         // -- mod="set v_gate_pulse" dstVar=out
         voice->var_v_gate_pulse = 0;
   
         /* end calc */

#ifdef OVERSAMPLE_FACTOR
#ifdef STEREO
         outOSL += outL;
         outOSR += outR;
#else
         outOS += out;
#endif // STEREO
#endif // OVERSAMPLE_FACTOR
         voice->note_speed_cur += voice->note_speed_inc;
         voice->note_cur       += voice->note_inc;
         voice->mod_vel_cur        += voice->mod_vel_inc;
         voice->mod_amp_cur        += voice->mod_amp_inc;
         voice->mod_shift_cur      += voice->mod_shift_inc;
         voice->mod_vsync_cur      += voice->mod_vsync_inc;
         voice->mod_env_scl_cur    += voice->mod_env_scl_inc;
      }
#ifdef OVERSAMPLE_FACTOR
      // Apply lowpass filter before downsampling
      //   (note) normalized Fc = F/Fs = 0.442947 / sqrt(oversample_factor^2 - 1)
#ifdef STEREO
      outL = outOSL * (1.0f / OVERSAMPLE_FACTOR);
      outR = outOSR * (1.0f / OVERSAMPLE_FACTOR);
#else
      out = outOS * (1.0f / OVERSAMPLE_FACTOR);
#endif // STEREO
#endif // OVERSAMPLE_FACTOR
#ifdef STEREO
      outL = Dstplugin_fix_denorm_32(outL);
      outR = Dstplugin_fix_denorm_32(outR);
#else
      out = Dstplugin_fix_denorm_32(out);
#endif // STEREO
#ifdef STEREO
      _samplesOut[k]      = outL;
      _samplesOut[k + 1u] = outR;
#else
      _samplesOut[k]      = out;
      _samplesOut[k + 1u] = out;
#endif // STEREO

      // Next frame
      k += 2u;
#ifdef STEREO
      j += 2u;
#else
      j += jStep;
#endif // STEREO
   } /* loop numFrames */
}

static st_plugin_shared_t *ST_PLUGIN_API loc_shared_new(st_plugin_info_t *_info) {
   clk_4_shared_t *ret = (clk_4_shared_t *)malloc(sizeof(clk_4_shared_t));
   if(NULL != ret)
   {
      memset((void*)ret, 0, sizeof(*ret));
      ret->base.info  = _info;
      memcpy((void*)ret->params, (void*)loc_param_resets, NUM_PARAMS * sizeof(float));
   }
   return &ret->base;
}

static void ST_PLUGIN_API loc_shared_delete(st_plugin_shared_t *_shared) {
   free((void*)_shared);
}

static st_plugin_voice_t *ST_PLUGIN_API loc_voice_new(st_plugin_info_t *_info, unsigned int _voiceIdx) {
   clk_4_voice_t *voice = (clk_4_voice_t *)malloc(sizeof(clk_4_voice_t));
   if(NULL != voice)
   {
      memset((void*)voice, 0, sizeof(*voice));
      voice->base.info = _info;
      voice->tmp2_lfsr_state = 6487 * (_voiceIdx + 1u);
      voice->tmp34_adsrv2_level = 0.0f;
      voice->tmp36_adsrv2_vel = 0.0f;
   }
   return &voice->base;
}

static void ST_PLUGIN_API loc_voice_delete(st_plugin_voice_t *_voice) {
   ST_PLUGIN_VOICE_CAST(clk_4_voice_t);

   free((void*)_voice);
   }

static void ST_PLUGIN_API loc_plugin_exit(st_plugin_info_t *_info) {
   free((void*)_info);
}

#ifdef USE_CYCLE_SINE_TBL
static void loc_calc_sine_tbl(void) {
#define QCOS_BITS 16
#define QCOS_ONE  (1 << QCOS_BITS)
#define QCOS_MASK (QCOS_ONE - 1)
   // (note) same as in TSR "C" implementation
   float *qcos = cycle_sine_tbl_f + 4096; // quarter cos tbl

   // calc quarter cos tbl
   int k = 0;
   for(int a = 0; a < (QCOS_ONE/4); a += (QCOS_ONE/16384)/*4*/)  // 4096 elements
   {
      int x = a - ((int)(QCOS_ONE * 0.25 + 0.5)) + ((a + ((int)(QCOS_ONE * 0.25 + 0.5)))&~QCOS_MASK);
      x = ((x * ((x<0?-x:x) - ((int)(QCOS_ONE * 0.5 + 0.5)))) >> QCOS_BITS) << 4;
      x += (((((int)(QCOS_ONE * 0.225 + 0.5)) * x) >> QCOS_BITS) * ((x<0?-x:x) - QCOS_ONE)) >> QCOS_BITS;
      qcos[k++] = x / float(QCOS_ONE);
   }

   // 0°..90° (rev qtbl)
   int j = 4096;
   k = 0;
   loop(4096)
      cycle_sine_tbl_f[k++] = qcos[--j];

   // 90..180° = cos tbl
   k += 4096;

   // 180°..360° (y-flip first half of tbl)
   j = 0;
   loop(8192)
      cycle_sine_tbl_f[k++] = -cycle_sine_tbl_f[j++];

#ifdef USE_CYCLE_SINE_TBL_I
   loop(16384)
      cycle_sine_tbl_i[i] = (short)(cycle_sine_tbl_f[i] * 2048.0f/*FX_ONE*/);
#endif // USE_CYCLE_SINE_TBL_I
}
#endif // USE_CYCLE_SINE_TBL

#ifdef USE_CYCLE_HSE_TBL
#endif // USE_CYCLE_HSE_TBL

extern "C" {
st_plugin_info_t *clk_4_init(void) {
   clk_4_info_t *ret = (clk_4_info_t *)malloc(sizeof(clk_4_info_t));

   if(NULL != ret)
   {
      memset(ret, 0, sizeof(*ret));

      ret->base.api_version = ST_PLUGIN_API_VERSION;
      ret->base.id          = "clk_4_0x3c51dfb5";  // unique id. don't change this in future builds.
      // (note) don't set author/name/short_name when CYCLE_SKIP_UI is defined ?
      ret->base.author      = "";
      ret->base.name        = "clk_4";
      ret->base.short_name  = "clk_4";
      ret->base.flags       = ST_PLUGIN_FLAG_OSC;
      ret->base.category    = ST_PLUGIN_CAT_UNKNOWN;
      ret->base.num_params  = NUM_PARAMS;
      ret->base.num_mods    = NUM_MODS;

      ret->base.shared_new                          = &loc_shared_new;
      ret->base.shared_delete                       = &loc_shared_delete;
      ret->base.voice_new                           = &loc_voice_new;
      ret->base.voice_delete                        = &loc_voice_delete;
#ifndef CYCLE_SKIP_UI
      ret->base.get_param_name                      = &loc_get_param_name;
      ret->base.get_param_group_name                = &loc_get_param_group_name;
      ret->base.get_param_group_idx                 = &loc_get_param_group_idx;
      ret->base.get_param_section_name              = &loc_get_param_section_name;
      ret->base.get_param_section_idx               = &loc_get_param_section_idx;
      ret->base.get_param_reset                     = &loc_get_param_reset;
      ret->base.query_dynamic_param_preset_values   = &loc_query_dynamic_param_preset_values;
      ret->base.query_dynamic_param_preset_name     = &loc_query_dynamic_param_preset_name;
      ret->base.get_array_param_size                = &loc_get_array_param_size;
      ret->base.get_array_param_num_variations      = &loc_get_array_param_num_variations;
      ret->base.get_array_param_variation_ptr       = &loc_get_array_param_variation_ptr;
      ret->base.set_array_param_edit_variation_idx  = &loc_set_array_param_edit_variation_idx;
      ret->base.get_array_param_element_name        = &loc_get_array_param_element_name;
      ret->base.get_array_param_element_value_range = &loc_get_array_param_element_value_range;
      ret->base.get_array_param_element_reset       = &loc_get_array_param_element_reset;
      ret->base.update_lut                          = &loc_update_lut;
#endif // CYCLE_SKIP_UI
      ret->base.get_param_value                     = &loc_get_param_value;
      ret->base.set_param_value                     = &loc_set_param_value;
#ifndef CYCLE_SKIP_UI
      ret->base.get_mod_name                        = &loc_get_mod_name;
#endif // CYCLE_SKIP_UI
      ret->base.set_sample_rate                     = &loc_set_sample_rate;
      ret->base.set_bpm                             = &loc_set_bpm;
      ret->base.note_on                             = &loc_note_on;
      ret->base.set_mod_value                       = &loc_set_mod_value;
      ret->base.prepare_block                       = &loc_prepare_block;
      ret->base.process_replace                     = &loc_process_replace;
      ret->base.plugin_exit                         = &loc_plugin_exit;

#ifdef USE_CYCLE_SINE_TBL
      loc_calc_sine_tbl();
#endif // USE_CYCLE_SINE_TBL
   }

   return &ret->base;
}

#ifndef STFX_SKIP_MAIN_INIT
ST_PLUGIN_APICALL st_plugin_info_t *ST_PLUGIN_API st_plugin_init(unsigned int _pluginIdx) {
   switch(_pluginIdx)
   {
      case 0u:
         return clk_4_init();
   }
   return NULL;
}
#endif // STFX_SKIP_MAIN_INIT

} // extern "C"
