Document:Cycle Sample Generator / Soft Synth
Author:bsp
Date:19-Aug-2023

1 Table of Contents

2 Preface

Cycle is a modular soft-synth which emits TKS or C sample generation code, or STFX plugins.
It operates either in hi-fi (32bit float), or lo-fi (8bit signed integer) mode:
While the processing blocks (modules) in Cycle are rather simple, their combination can yield quite complex sounds, even with (by today's standards, slow) CPUs like the Motorola M68000.
In STFX plugin-mode, Cycle generates native-code C voice plugins via the GCC / Clang compiler.
STFX plugins and float sample generation code can also be used in the standalone Synergy replay.

3 Global Keywords

Global keywords may appear at the beginning of a Cycle script.
KeywordDescription
argDeclare argument (will be editable in UI)
varDeclare variable
oversample_factor
oversample
Oversample factor (0.03125..32, def=1) (also supports fractional values, e.g. "1/8")
caution_s
Oversampling is ignored when exporting to lo-fi 8bit "C" sample generator source

note_s
8 is usually a good factor when targeting ~16kHz samples (=128kHz internal rate)
oversample_qualityOversample quality (0..10, def=4)
caution_s
Oversampling is ignored when exporting to lo-fi 8bit "C" sample generator source
rateOverride zone sample-rate
freqOutput frequency. Default is 261.63 (middle C, MIDI note 60 / C-5).
alternatively: MIDI note C-1..G-A (followed by finetune, e.g. G-4 -0.2)
alternatively: float ratio, e.g. 16574/64
octOctave shift (-4..4)
durOverride sample duration (frames or, when followed by "ms", milliseconds)
wt_wOverride wavetable width (1..16 in 2D mode, 1..128 in 1D mode)
wt_hOverride wavetable height (1..16 in 2D mode, 1 in 1D mode)
wt_cyclelenOverride wavetable cycle length (number of frames. 1..1048576)
wt_freqsPer-cycle note frequencies/rates (multisampled keymaps)
e.g. wt_freqs c-3 g-3 c-4 "g-4+0.11" c-5 g-5 c-6 g-6 c-7 g-7
note_s
the current cycle freq is available via $wt_freq variable during synthesis
skipSkip output of first "n" sample frames or, when followed by "ms", milliseconds
xfadeCrossfade end of loop with samples before loop start. Sample frames or loop-len percentage (when followed by %)
mirrorCalc first half waveform and mirror to second half (single cycle waveforms and wavetables)
curveDeclare curve lookup-table. Must be followed by curve index (0..15) and curve id (e.g. "amp")
lutDeclare procedural lookup-table. Must be followed by LUT id, optional mode (voice/shared), optional table size (default=256), and optional "inv" (inverse) flag
zoneDeclare external zone reference. Must be followed by ref idx (0..) and zone id (e.g. "myzone", or "myothersmp.myzone")
macroDeclare macro parameter set(s). See macros.
Global Keywords

3.1 Plugin keywords

Keywords used in plugin-generation mode:
KeywordDescription
name
Plugin name
note_s
the plugin name (and dll / so / dylib) will be prefixed with cycle_
authorPlugin author
cat
category

Plugin category
unknownlimiterpantransient
ringmodenhancepitchconvolver
waveshaperdacchorusmixer
filterlofiflangercomparator
eqgatedelayutility
compressorampreverbosc
Plugin Keywords

4 Values and sample output

The same value type is used for module parameters, args, variables, and intermediate sample data.
In hi-fi mode, values are represented by 32 bit floating point numbers. The final sample output is also a float.
In lo-fi mode, values are represented by signed twos-complement, 16 bit fixed point numbers (4:11), i.e. the value range is -15..15.
The final sample output is a signed 8 bit sample.

5 Curve Lookup Tables (LUTs)

Cycle supports up to 16 cubic-bezier curve tables that can be used as amp or filter envelopes, or sample waveshapers.
Curve tables are internally converted (sampled) to short, power-of-two sized arrays. The array sizes can be configured in the UI.
Before a curve table can be used with a lut module, it must first be declared with a curve statement:
curve 0 amp 
 
<out: 
  sin 
  * rmp 0 0 0.99 
    lut amp 
Using a curve LUT as an amp envelope

6 Procedural Lookup Tables (LUTs)

Cycle supports an arbitrary number of procedural look up tables which share the same namespace with curve lookup tables.
The table size must be a power of two.
Tables are calculated either per-voice (at note-on, using the then-current parameters and modulation), or statically (shared among all voices).
lut pow shared 256 
 
<lut_pow: 
  # (note) implicit var "x" stores normalized table position (0..1=last table element) 
  # (note) the default out value is $x 
  fma 2 -1 
  pow 1.44 
   
<out: 
  sin 
  fma 0.5 0.5 
  lut pow lin=1 
shared LUT example
param p_exp 1 1 16 
 
mod m_exp 
 
lut pow voice 32 
 
<lut_pow: 
  # (note) implicit var "x" stores normalized table position (0..1=last table element) 
  # (note) the default out value is $x 
  fma 2 -1 
  pow exp=$m_exp 
 
<out: 
  sin 
  fma 0.5 0.5 
  lut pow lin=1 
per-voice modulated LUT example
tip_s
lookup tables can greatly improve the rendering performance by pre-calculating recurring values
note_s
the default table size is 64
note_s
the default table mode is "voice"
note_s
LUT init sections must be named "lut_<id>"
note_s
if no init section has been declared for a LUT used by a "lut" module, compilation will fail
note_s
the shared LUTs are calculated after the "<init:" section
note_s
the per-voice (or per-WT cycle) LUTs are updated after the "<prepare:" / "<wt_init:" section

6.1 Inverse Lookup Tables

See sta module documentation for examples.

7 Macros

Macros are used to define smoothly interpolated value sets, controlled by a single parameter (or other value).
They are mostly useful to hide the internal complexity of a Cycle patch, and reduce the number of (plugin-)parameters.
The mac module updates the current, interpolated values of the macro fields.
The current value of a macro field is accessed (read) using the <macro_id>.<field_name> syntax.
Examples:
id     osc_mix_macro_manual 
name   "osc mix macro manual" 
author bsp 
cat    osc 
 
# Plugin parameter 
param p_osc_mix 0 0 1 
 
# 4 manually configured settings 
macro osc_mix 
  sin   tri  saw   pul 
  1     0     0    0 
  0     1     0    0 
  0     0     1    0 
  0     0     0    1 
 
<prepare: 
  # calculate interpolated macro values 
  #  (updated 1000 times per second) 
  mac osc_mix pos=$p_osc_mix 
 
<out: 
  # calculate current output sample 
  sin 
  * osc_mix.sin 
  + tri 
    * osc_mix.tri 
  + saw 
    * osc_mix.saw 
  + pul 
    * osc_mix.pul 
macro example (manual settings)
id     osc_mix_macro_permutate 
name   "osc mix macro permutate" 
author bsp 
cat    osc 
 
# Plugin parameter 
param p_osc_mix 0 0 1 
 
# auto-generated settings 
#  (permutations of values 0/1/-1 => 3*3*3*3=81 settings) 
macro osc_mix 
  sin   tri  saw   pul 
  permutate 0 1 -1 
 
<prepare: 
  # calculate interpolated macro values 
  #  (updated 1000 times per second) 
  mac osc_mix $p_osc_mix 
 
<out: 
  # calculate current output sample 
  sin 
  * osc_mix.sin 
  + tri 
    * osc_mix.tri 
  + saw 
    * osc_mix.saw 
  + pul 
    * osc_mix.pul 
macro example (permutated settings)
pos is in the range 0..1 (last setting is lerped back to first setting)
note_s
the maximum number of (auto-generated) permutations is limited to 16384
tip_s
alternative to macros, the <init: and <prepare: sections may also contain arbitrarily complex, procedural pre-calculations

8 Output lanes

Lanes divide the script into multiple output sections.
Each script must contain the main section (named out):
<out: 
  sin 

8.1 Layers

When a script contains multiple lanes, they will be summed to create the final sample output.
This can be used for layered synthesis (lane == layer).

8.2 Init lane

A script may contain an optional init section that is evaluated before the main section.
It can be used to implement procedural macro parameters that read args and write one or many variables.
arg angle 0 0 1 
var v_sin 
var v_cos 
<init: 
  sin freq=0 phase=$angle 
  sto v_sin 
  sin freq=0 
    phase: 
      $angle 
      + 0.25 
  sto v_cos 
sin / cos init example
note_s
the init section must occur before the first audio lane
note_s
in plugin-mode, the init section is evaluated each time a new note is played (note-on)

8.3 Prepare lane

The prepare section is evaluated before the main section(s), and after the (optional) init section.
Heavy calculations, or calculations which do not need to be evaluated per sample frame should be placed in the prepare section.
id     simple_2op_fm_osc 
name   "simple_2op_fm_osc" 
author bsp 
cat    osc 
 
param p_op2_ratio 0.5 0 1 
param p_op2_level 0 0 16 
 
mod m_op2_ratio 
mod m_op2_level 
 
var v_op2_freq 
 
<prepare: 
  # ratio 0..1 => (1/4096) .. 1 .. 4 
  $m_op2_ratio 
  # 0..1 => -1..1 
  fma 2 -1 
  # -1=>div 4096, +1=>mul 4 
  bts 4096 4 
  pow 3 
  # quantize to 100 fractional divisions per integer 
  qua 100 
  sto v_op2_freq 
 
<out: 
  sin 
    phase: 
      sin freq=$v_op2_freq 
      * $m_op2_level 
simple 2 operator FM / phase modulation oscillator plugin
note_s
in plugin-mode, the prepare section is called at a rate of 1kHz while a voice is playing.

8.4 Wavetable Cycle Init lane

The optional wt_init section is evaluated at the beginning of each wavetable cycle.
It can be used to implement procedural macro parameters that read the built-in $wt_x / wt_y vars and write one or many variables.
arg angle_scl 1 0 15 
arg angle_off 0 -1 1 
var v_sin 
var v_cos 
<wt_init: 
  sin freq=0 
    phase: 
      $wt_x 
      * $angle_scl 
      + $angle_off 
  sto v_sin 
  sin freq=0 
    phase: 
      $wt_x 
      * $angle_scl 
      + $angle_off 
      + 0.25 
  sto v_cos 
sin / cos wavetable cycle init example
note_s
the wt_init section must occur before the first audio lane, and after the (optional) init lane

9 Constants

Constants are typically used for static module parameters, e.g.
<out: 
  sin freq=1.5 
A constant is either a floating point number (in the range -15..15), or a note ratio in the form of d#3/g-3:
<out: 
  sin freq=d#4/c-3 

10 Args

Args are named constants that are declared by the global keyword arg (before any output section).
Arg values are editable in the (auto-generated) script UI (slider + value widgets).
Example:
arg lpf1_ms 30 min=0 max=500 
arg lpf1_ms 30 0 500 
arg lpf_rng 0.3 
The default (implicit) min / max range is 0..1
Script code can reference args by prefixing the arg id with a $ sign, e.g.
arg amp_lvl 1.5 
 
<out: 
  sin 
  * $amp_lvl 
  clp 
caution_s
args and variables share the same namespace

11 Variables

Variables are very similar to args.
While they do not generate UI widgets, their content may be updated at any time via the sto module.
Script code can read variables by prefixing the var id with a $ sign, e.g.
var myvar 
 
<out: 
  2.5 
  sto myvar 
  sin 
  * $myvar 
  clp 
caution_s
args and variables share the same namespace

11.1 Built-in Variables

$x stores the current, normalized position in procedural look-up tables (lut)
$wt_x stores the current, normalized horizontal (or 1D) wavetable position (0..1)
$wt_y stores the current, normalized vertical (2D) wavetable position (0..1)

12 Operators

Each module may be prefixed by an operator that determines how its output is combined with the previous module's output.
OperatorIdDescription
=OP_REPReplace
+OP_ADDAdd
-OP_SUBSubtract (a-b)
rOP_RSUBReverse-Subtract (b-a)
*OP_MULMultiply
&OP_ANDBitwise and
|OP_ORBitwise or
^OP_EORBitwise exclusive-or
mOP_MINMinimum
xOP_MAXMaximum
MOP_ABSMINAbsolute minimum
XOP_ABSMAXAbsolute maximum
.OP_NONEDiscard / Ignore
Operators
The default operator is replace.

13 Modules

Modules generate new sample frames or process the output of the previous module.
Modules may be nested (stacked), i.e. module inputs can be controlled by the output of other modules.
Example:
<out: 
  sin 
    freq: 
      sin 5.0 
      * 0.25 
      + 1 
  * 3.0 
  clp 
Static module parameters are set by key=value pairs, or simply by value (in internal parameter order)
  lpf freq=0.4 res=0.5 
  lpf 0.4 0.5 
Dynamic module parameters must be placed on separate, indented lines.
Please note that some module parameters cannot be dynamic (e.g. hpf filter configuration).
Also keep in mind that dynamic parameters require more complex code that will lengthen the synthesis duration.

13.1 abs

Return absolute value of previous output.
Example:
<out: 
  -42 
  abs 

13.2 bit

Bit-crush (shift) previous output.
Example:
<out: 
  sin 
  bit shift=0.5 
  bit 0.5 
shift range is 0..1

13.3 boo

Boost sample deltas of previous output.
Example:
<out: 
  sin 
  boo amount=0.6 
  boo 0.6 
amount range is 0..1

13.4 box

Box filter previous output.
Example:
<out: 
  saw 
  box freq=0.5 
  box 0.5 
freq range is 0..1

13.5 bpf

not implemented, yet
tip_s
use svf in mode=2 instead

13.6 bts

Bipolar to scale.
Example:
arg $freq 
<out: 
  sin 
    freq: 
      $freq 
      # 0..1 => -1..1 
      fma 2 -1 
      # -1..1 => 1/16 .. *16 
      bts 16 
div range is 1..32 [def=8]
mul range is 1..32 [def=8]
note_s
uses div input when mul is not connected
caution_s
only supported in float-mode (fall back to float in int mode)

13.7 buf

not implemented, yet

13.8 bus

Read sample from (cross-zone) voicebus.
Example:
arg bus 0 0 0.32 
arg lvl 
<out: 
   sin 
     phase: 
       bus nr=$bus 
       * $lvl 
phase-modulate sine wave by previous zone output
nr is in the range 0..0.32 (bus 1..32, 0=previous zone nr)
caution_s
only supported in STFX plugin mode (returns 0 otherwise)

13.9 clp

Clip previous output
Examples:
<out: 
  sin 
  * 7 
  clp 
clip to -1..+1
<out: 
  sin 
  * 7 
  clp 0.5 
clip to -0.5..+0.5
<out: 
  sin 
  * 7 
  clp floor=-0.75 ceil=0.6 
separate boundaries
ceil range is 0..1 (default=1)
floor range is -1..1 (default=-1) (when not connected, floor=-ceil)

13.10 dec

Decrement variable
var f 
<out: 
  set f 0 
  rep 16 
    dec f 0.1 
    dec f delta=0.1 
    dec f 
      delta: 
        0.05 
        * 2 

13.11 div

Divide constant values.
Example:
arg p_num 16 1 32 
<out: 
  div 1.0 $p_num 
tip_s
useful in conjunction with rep / lop, e.g. when indexing LUTs

13.12 dly

not implemented, yet

13.13 drv

not implemented, yet

13.14 end

Close current lane.
note_s
optional. can be used for placing lut declarations in between lanes.
note_s
when lane is a lut init lane, skip the output write (lane must write to lut via instead)

13.15 env

not implemented, yet, may be removed
(use rmp and lut instead)

13.16 fld

Fold previous output value at ceiling.
Example:
<out: 
  sin 
  * 2.0 
  fld ceil=1.0 
note_s
ceil sets min=-ceil, max=+ceil
Example:
<out: 
  sin 
  * 2.0 
  fld min=-1.0 max=1.0 
note_s
min / max override default ceil
tip_s
all oscillators can be used as wavefolders by setting their speed to 0, then modulating their phase input

13.17 fma

Fused multiply add: out = out * mul + add
Example:
<out: 
  sin 
  # -1..+1 => 0..1 
  fma 0.5 0.5 
mul input is in range -15..15 [def=2]
add input is in range -15..15 [def=-1]

13.18 frc

Calc fractional part of previous output (discard integer bits)
Example:
<out: 
  sin 
  * 3.3 
  frc 
note_s
int does the opposite and discards all fractional bits

13.19 fsr

Generate linear feedback shift register ("C64") noise (normalized to -1..1).
Example 1:
<out: 
  fsr 
Example 2:
<out: 
  fsr seed=$4489 bits=1.0 cycle=1 
seed is the initial shift register state (16 bits). must not be 0. Default is $4489
bits determines the number of output bits (1.0=all bits, 0=1 bit (actually two, sign+msb). Default is 1.0.
cycle determines whether the shift register state resets at each (wavetable) cycle. Default is 1 (true).
shift1 is the first feedback shift (negative=shift left). -15..15. def=7
shift2 is the second feedback shift. -15..15. def=-9
shift3 is the third feedback shift. -15..15. def=13
reset range is 0..1 (0=disable plugin voice note on reset, default=1)
note_s
changing the number of bits does not make an audible difference, except when the output is used as a controller value
note_s
fsr noise contains less low frequencies than nos noise

13.20 hbx

High-pass Box filter previous output.
Example:
<out: 
  saw 
  hbx freq=0.5 
  hbx 0.5 
freq range is 0..1

13.21 hld

Sample and hold previous output.
Example:
<out: 
  hld rate=0.5 
hld range is 0..1
note_s
if the previous output uses complex calculations, it is more efficient to render these into a separate zone at a lower rate, then read the precalced values via zon

13.22 hpf

High-pass filter previous output. Uses preconfigured, static cutoff/resonance configurations (biquad coefficients).
Example:
<out: 
  saw 
  hpf cfg=3 

13.22.1 Filter configurations

cfgcutoffresonance
01270
1127108
21220
3122108
41150
5115100
61100
7110100
81000
910097
10900
119070
12770
137779

13.23 if

Conditional code generation (compile time).
Syntax:
  if <arg> [<,<=,==,!=,>=,>] <constant> 
    then: 
       .. 
    else: 
       .. 
Example:
arg mod_waveform; 
 
<out: 
  if mod_waveform == 0 
    then: 
      nos 
    else: 
      if mod_waveform < 0.33 
        then: 
          sin 
        else: 
          if mod_waveform < 0.66 
            then: 
              tri 
            else: 
              saw 

13.24 inc

Increment variable
var f 
<out: 
  set f 0 
  rep 16 
    inc f 0.1 
    inc f delta=0.1 
    inc f 
      delta: 
        0.05 
        * 2 

13.25 int

Evaluate sub-tree in integer mode.
The previous output value is converted to 16bit fixpoint (1:4:11) before the sub-tree is emitted.
Afterwards the integer output value is converted back to float.
<out: 
  int 
    sin 
note_s
in integer mode, no conversions take place and the sub-tree is emitted as usual

13.26 kbd

Scales the input by 127 (normalized float to MIDI note), adds (current_note - 64 + (off * 12)), clamps to 0..127, then converts the note to its corresponding frequency and divides it by the sample rate (back to normalized 0..1 range).
Useful for scaling filter cutoff frequencies (linear to exponential).
id     cutoff_kbd_tracking_test 
name   "cutoff kbd tracking test" 
author bsp 
cat    filter 
 
param p_cutoff 0.5 0 1 
param p_keyfollow 1 0 1 
param p_octave_off 0 -4 4 
 
<out: 
  svf 
    freq: 
      $p_cutoff 
      kbd $p_keyfollow $p_octave_off 
filter cutoff keyboard tracking example plugin
kbd is in the range -16..16 (keyboard follow amount)
off is in the range -4..4 (octave offset)
scl is in the range 0..16 (post scaling)
caution_s
only supported in plugin / float mode (fall back to float within int-subtree)

13.27 lle

Scale previous output by logarithmic(c<0)..linear(c=0)..exponential(c>0) factor
8 
lle c=1.4 
c is in the range -f..f.
caution_s
only supported in float-mode (fall back to float in int mode)

13.28 log

Return logarithm of previous output.
8 
log base=2 
base is in the range 1..f (must be constant). When input is not connected, return the natural logarithm.
caution_s
only supported in float-mode (fall back to float in int mode)

13.29 lop

Loop statement sequence. Supports dynamic loop iterators.
var i 
var num 
<out: 
  2.0 
  * 4.0 
  sto num 
  0.0 
  sto i 
  lop $num 
    + 0.01 
num range is 0..1023
caution_l
some modules (e.g. sin) use variables to store their state. evaluating such modules in a loop will cause all iterations to use the same state.
tip_l
use rep to generate unrolled loops where each iteration will have its own state

13.30 lpf

1RC low pass filter
<out: 
  saw 
  lpf freq=0.4 res=0.5 
  lpf 0.4 0.5 
freq range is 0..1
res range is 0..1

13.31 lut

Use previous output as index for curve look-up table (LUT)
Example:
curve 0 amp 
 
<out: 
  sin 
  * rmp 0 0 0.99 
    lut amp clamp=0 lin=0 
clamp must be either 0 (repeat lut) or 1 (clamp to last index)
lin must be either 0 (nearest neighbour) or 1 (linear interpolation)
note_s
the lut module is often combined with a rmp module that generates the look-up index

13.32 mac

The mac module updates the current, interpolated values of a macro's fields.
See macros for further information and examples.
pos is in the range 0..1 (last setting is lerped back to first setting)

13.33 neg

Negate previous output
Example:
<out: 
  saw 
  neg 

13.34 nos

Render white noise via xor-shift pseudo random number generator
Example:
<out: 
  nos seed=0x44894489 cycle=0 
seed is the random seed (a 32bit integer). the default seed is 0x3d9fb971
cycle range is 0..1 (0=disable wavetable cycle reset, default=1)
reset range is 0..1 (0=disable plugin voice note on reset, default=1)

13.35 pha

Generate phase output (0..1 normalized saw-wave).
Example:
curve 0 mywaveshape 
 
<out: 
  pha freq=1.5 phase=0.25 
  lut mywaveshape 
freq range is 0.25..4
phase range is 0..1 (start phase)
cycle range is 0..1 (0=disable wavetable cycle reset, default=1)
reset range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed range is 0..1 (1=enable fixed frequency mode, default=0)

13.36 p2s

Return (pow(2, exp * prev_out) - 1.0) / (pow(2, exp) - 1.0).
Can be used for e.g. scaling filter cutoff frequencies (linear to exponential).
Example:
arg cutoff 
 
<out: 
  svf 
    freq: 
      $cutoff 
      p2s 7 
(non-kbd-tracked) exponential cutoff scaling
When the kbd (and optionally the off) input is connected and Cycle is in STFX plugin-generation mode, the module input follows the keyboard.
id     cutoff_kbd_tracking_test_p2s 
name   "cutoff kbd tracking test p2s" 
author bsp 
cat    filter 
 
param p_cutoff 0.5 0 1 
param p_keyfollow 1 0 1 
param p_octave_off 0 -4 4 
 
<out: 
  svf 
    freq: 
      $p_cutoff 
      p2s kbd=$p_keyfollow off=$p_octave_off 
filter cutoff keyboard tracking example plugin
exp is in the range 0..15
scl is in the range 0..1 0⇒scl=(pow(2,exp-1)) (must be const)
kbd is in the range -16..16 (keyboard follow amount)
off is in the range -4..4 (octave offset)
caution_s
only supported in float-mode (fall back to float in int mode)
tip_s
for keyboard tracking purposes, the kbd module is usually the preferred solution

13.37 pow

Return previous output raised to the power of 2 or 3 (fast-path), or an arbitrary exponent (float-mode only).
Example:
<out: 
  sin 
  pow 3 
exp range is [2, 3] (int) or any float value
tip_s
useful for bending rmp envelopes into non-linear shapes
caution_s
constant exponents 2 and 3 behave differently than arbitrary (or dynamic) exponents, i.e. -2=+4 while e.g. -2^4=-16
caution_s
arbitrary or dynamic exponents are only supported in float-mode (fall back to float in int mode)

13.38 pul

Generate pulse wave
Example:
<out: 
  pul freq=1 phase=0 width=0.5 vsync=2.5 
freq range is 0.25..4
phase range is 0..1 (start phase)
width range is 0..1 (length of duty cycle)
vsync range is 0.01..4 (virtual oscillator sync)
cycle range is 0..1 (0=disable wavetable cycle reset, default=1)
reset range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed range is 0..1 (1=enable fixed frequency mode, default=0)
window range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
caution_s
windowed vsync is only supported in float-mode (ignored in integer mode)

13.39 qua

Quantize float value to n fractional steps per integer.
<out: 
  sin 
  qua num=10 
num is in the range 1..n
tip_s
useful for controllers, e.g. FM ratios
tip_s
when num=1, simply discard fractional bits (cast to integer)
caution_s
only supported in float-mode (fall back to float in int mode) (except when num=1)

13.40 rep

Loop / unroll statement sequence. The number of iterations must be constant.
arg p_num 8 1 32 
var i 
<out: 
  1.0 
  sto i 
  0.0 
  rep $p_num 
    + sin 
        freq: 
          $i 
          * 0.5 
      * 0.1 
  clp 
num range is 0..1023

13.41 rev

not implemented, yet

13.42 rmp

Generate linear ramp
Example:
<out: 
  rmp millisec=8 start=0 end=1 cycle=1 
millisec range is 0..4000. 0 auto-adjusts the duration to match the target zone sample length
start range is 0..15 (start value)
end range is 0..15 (end value)
cycle determines whether the ramp position is reset at each new wavetable cycle (0 or 1)

13.43 saw

Generate sawtooth wave
Example:
<out: 
  saw freq=1 phase=0 vsync=2.5 
freq range is 0.25..4
phase range is 0..1 (start phase)
vsync range is 0.01..4 (virtual oscillator sync)
cycle range is 0..1 (0=disable wavetable cycle reset, default=1)
reset range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed range is 0..1 (1=enable fixed frequency mode, default=0)
window range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
caution_s
windowed vsync is only supported in float-mode (ignored in integer mode)

13.44 set

Set variable
var f 
<out: 
  set f 1.23 
  set f value=1.23 
  rep 16 
    inc f 0.1 

13.45 sin

Generate sine wave
Example:
<out: 
  sin freq=1 phase=0 vsync=2.5 window=1 
freq range is 0.25..4
phase range is 0..1 (start phase)
vsync range is 0.01..4 (virtual oscillator sync)
cycle range is 0..1 (0=disable wavetable cycle reset, default=1)
reset range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed range is 0..1 (1=enable fixed frequency mode, default=0)
window range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
caution_s
windowed vsync is only supported in float-mode (ignored in integer mode)

13.46 slf

Read past sample output ("self" feedback)
<out: 
  pul 
  + slf rate=0.98 
  clp 
rate range is 0..1

13.47 ssl

Sine slice oscillator.
Works similar to how the additional 7 waveforms on the Yamaha TX81Z were created back in the 1980ies.
The (built-in) sine table as well as the destination waveform(-cycle) is divided into 8 regions / slices.
Each target slice is assigned one of the 8 sine regions (0..7).
<out: 
  ssl 01234567 freq=1.0 phase=0.0 mode=4 mod=0 seq=0 seqmod=0 modmask=1.0 zeromask=0.0 
The first argument is the base slice sequence (0=region 0, 7=region 7).
When the sequence has less than 8 entries, it will be repeated.
freq is in the range 0.25..4
phase is in the range 0..1
mod is in the range -1..1 (slice modulation)
seq is in the range 0..1 (0=base slice sequence, 00000001..7777777). Must be const.
modmask is in the range 0..1 (0=no slices, 1=all slices)
zeromask is in the range 0..1 (0=no slices cleared, 1=all slices cleared)
mode is in the range 0..5 (must be const):
ModeDescription
0 pointignore fractional (mod) bits, simply recombine waveforms (fastest)
1 phasefractional (mod) bits determine source region start offset shift ("phase" mod 1/8 sin)
2 lerpfractional (mod) bits are used to lerp sine source region to cosine wave
3 shiftfractional (mod) bits are used to shift source region (dc offset + reflect/fold)
4 angfractional (mod) bits determine source waveform start offset shift ("phase" +- 2PI)
5 ampfractional (mod) bits are used to amplify source region
SLL mode settings
seqmod is in the range 0..3 (per-sample sequence modulation)

13.48 sta

Store variable in LUT at normalized index determined by previous output value.
Syntax: sta <lut_id> <index_mul> <index_add> <src_var> <var_mul> <var_add>
Example:
lut ws shared 4096 
lut ws_inv shared 4096 inv 
 
<lut_ws: 
  # transform implicit input x={0..1} => {-1..1} 
  fma 2 -1 
  pow 3 
  # store in inverse LUT 'ws_inv' 
  #  - transform 'pow' result (-1..1) into normalized index (0..1) 
  #  - transform x from 0..1 to -1..1 and store in LUT 
  sta ws_inv 0.5 0.5 x 2 -1 
  # store 'pow' result in LUT 'ws' (done by auto-generated code) 
  #  (note) holes in the ws_inv LUT will be auto-filled afterwards 
   
<out: 
   sin 
   # -1..1 => 0..1 
   fma 0.5 0.5 
   lut ws 
   lpf freq=0.3 res=0.5 
   clp 
   # -1..1 => 0..1 
   fma 0.5 0.5 
   lut ws_inv 
inverse exponential lookup table
Example 2:
curve 0 ws 
lut ws_inv shared 2048 inv 
 
<lut_ws_inv: 
  # use implicit input x (0..1) as index into bezier curve 'ws' 
  lut ws 
  clp 
  # store in inverse LUT 'ws_inv' 
  #  - transform 'clp' result (-1..1) into normalized index (0..1) 
  #  - transform x from 0..1 to -1..1 and store in LUT 
  sta ws_inv 0.5 0.5 x 2 -1 
  # don't write output value to lut (already written)  
  end 
 
<out: 
   sin 
   # -1..1 => 0..1 
   fma 0.5 0.5 
   lut ws 
   lpf freq=0.3 res=0.5 
   clp 
   # -1..1 => 0..1 
   fma 0.5 0.5 
   lut ws_inv 
inverse bezier curve lookup table
note_s
referencing an inverse LUT in another LUT's init lane via sta causes the inverse LUT to be filled before the init lane is executed, and potential holes (unwritten elements) in the inverse LUT to be closed afterwards

13.49 sto

Store previous output in variable
Example:
var t 
 
<out: 
  sin 
  sto t 
  * $t 
  * $t 
The variable is initialized with 0 but retains its content over multiple sample frame iterations.
Example:
var sin_val 
 
<out: 
  $sin_val 
  + sin 
  sto $sin_val 
  clp 

13.50 svf

Process previous output with state-variable filter
Example:
<out: 
  saw 
  svf freq=0.4 res=0.5 mode=0 
  svf 0.4 0.5 0 
freq range is 0..1
res range is 0..1
mode 0=lpf, 1=hpf, 2=bpf, 3=notch

13.51 tanh

Process previous output through tangens hyperbolicus (tanh) function.
Example:
arg drive 0 -1 1 
 
var v_amp 
 
<prepare: 
  # amp=/10..*10 
  10 
  pow $drive 
  sto v_amp 
 
<out: 
  sin 
  * $v_amp 
  tanh 
tanh
arg drive 0 0 2 
 
lut tanh shared 4096 
 
<lut_tanh: 
  # 0..1 => -100..+100 
  fma 200 -100 
  tanh 
 
var v_amp 
 
<prepare: 
  # amp=*1..*100 
  10 
  pow $drive 
  sto v_amp 
 
<out: 
  sin 
  * $v_amp 
  # -100..+100 => 0..1 
  fma 1/200 0.5 
  lut tanh lin=1 
tanh lookup-table
note_s
useful for saturation-like non-linear distortion (converges to -1..1)
caution_s
only supported in float-mode (fall back to float in int mode)

13.52 trc

Debug-trace last output value (print to console).
Example:
lut mylut shared 16 
 
<lut_mylut: 
  # print current $x 
  trc 
   
<out: 
  sin 

13.53 tri

Generate triangle wave
Example:
<out: 
  tri freq=1 phase=0 vsync=2.5 
freq range is 0.25..4
phase range is 0..1 (start phase)
vsync range is 0.01..4 (virtual oscillator sync)
cycle range is 0..1 (0=disable wavetable cycle reset, default=1)
reset range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed range is 0..1 (1=enable fixed frequency mode, default=0)
window range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
caution_s
windowed vsync is only supported in float-mode (ignored in integer mode)

13.54 vpl

Process previous output via stfx voice plugin.
<out: 
  sin 5.0 
  vpl "bsp ws quintic" "Dry / Wet"=0.5 Drive=0.39 
Example (single line)
arg drive 0.39 min=0 max=1 
 
<out: 
  sin 5.0 
  vpl "bsp ws quintic" "Dry / Wet"=0.5 "Drive"=$drive 
Example (singe line with arg slider)
arg drive 0.39 min=0 max=1 
 
<out: 
  sin 5.0 
  vpl "bsp ws quintic" 
    "Dry / Wet" = 1 
    "X^0"       = 0 
    "X^1"       = 0.5 
    "X^2"       = 0.74 
    "X^3"       = 0.8 
    "X^4"       = 0.62 
    "X^5"       = 0.5 
    "Drive"     = $drive 
Example (multi line with arg slider)
In Lo-Fi (integer) mode, the input/output values are temporarily converted to / from float.
note_s
See Eureka sampler "FX" tab for a list of available plugins.
note_s
Plugin setup can be exported to single- or multi-line Cycle script (clipboard) via Eureka sampler "FX" tab context menu
caution_s
vpl modules cannot be exported to "C" source

13.55 xfd

Crossfade between previous output and input b.
arg mod_waveform 0 -1 1 
<out: 
  tri 
  xfd amount=$mod_waveform 
    b: 
      saw 
Example (crossfade between triangle and saw)

13.56 zlp

Read looped sample frame from other zone. Can be used to mix samples or create chords.
Example:
zone 0 otherzone 
zone 1 othersmp.otherzone 
 
<out: 
  zon 0 rate=0.3 phase=0.5 
  + zon 1 0.9 
  clp 
rate range is 0..1
phase range is 0..1
note_s
sample len is determined by last loop length of the specified zone
note_s
sample len should be a power of two (read offset is ANDed with (sampleLen-1))

13.57 zon

Read sample frame from other zone. Can be used to mix samples or create chords.
Example:
zone 0 otherzone 
zone 1 othersmp.otherzone 
 
<out: 
  zon 0 rate=0.3 
  + zon 1 0.9 
  clp 
Upsampled loop example:
zone 0 loopzone 
 
<out: 
  zon 0 rate=0.5 lin=1 and=255 
rate range is 0..1 (must be const)
lin enables bilinear filtering (def=off) (must be const)
and enables power-of-two wrap-around (def=off, 1..65535 otherwise) (must be const)
tip_s
in order to create chords, synthesize the highest note to a zone, then in a following zone read and mix the previously rendered zone at different speeds / rates
Minor chord example:
zone 0 singlenote_g3 
 
<out: 
  zon 0 
  + zon 0 d#3/g-3 
  + zon 0 c-3/g-3 
  * 0.5 
  clp 

13.58 zsq

Sequence (other) zone sample.
Can be used to create (multi-channel) drum loops. Example:
zone 0 bd 
zone 1 sd 
 
<out: 
  #       0   1   2   3   4   5   6   7 
  = zsq 0 xx......x....x.. note=16 bpm=125 rate=1 
  + zsq 1 ....x.......x... bpm=125 rate=1.1 lin=1 
  clp 
note range is 1..256 (step note duration) (must be const)
bpm range is 0..1000 (<10: use project bpm) (must be const)
swing range is -0.5..0.5 (must be const)
rate range is 0.125..8 (must be const)
lin range is 0..1 (>=0.5: enable bilinear filtering, def=0) (must be const)
div range is 0..256 (divide source zone into n regions) (must be const)
off range is 0..1 (select sub-divided source region, e.g. $wt_x)
tip_s
When the input zones (one-shot samples) are placed in the same samplebank as the sequenced loop, ticking the Recalc All checkbox (next to the F11: Synth button at the bottom of the screen) will cause all procedural zones to be recalculated when any synth patch changes. This makes it possible to tweak the input sounds and immediately hear the edits in the context of the loop.
tip_s
rate can (as usual) also be expressed as a note ratio, e.g. c-3/d#3

14 Plugins

Cycle patches can also be exported as native code (C/C++) voice plugins.
The following restrictions apply:

14.1 Plugin header

Patches must declare a unique plugin id. Example:
id     myplugin_v1 
name   "my plugin v1" 
author bsp 
cat    osc 

14.2 Parameters and modulations

Plugins have "n" parameters (shared among all voices), and "n" mods (per-voice modulation).
It is recommended to not exceed 8 parameters and mods.
note_s
parameter names may be prefixed with p_ (prefix will be removed during export)
note_s
mod names may be prefixed with m_ (prefix will be removed during export)
If a mod has a parameter of the same name (ignoring the optional prefix), the effective mod value is the sum of the shared and mod values.
float modcutoff = shared->params[PARAM_CUTOFF] + voice->mods[MOD_CUTOFF]; 
auto-generated code example
Frequency, parameters, and modulation will be interpolated over processing blocks. The block update rate (in Eureka) is 1000Hz, i.e. a processing block consists of e.g. 48 sample frames at a sample rate of 48kHz.

14.2.1 Params

Parameters are always in the (normalized) range 0..1.
Parameter may have default (reset) values, which will be exported to the plugin:
param p_cutoff 0.9 
Normalized parameters are scaled to their (optional) min / max value range when they are accessed.
param p 0.5 1 16 
p will be scaled to range 1..16. the effective default value is 0.5*(16-1)+1=8.5

14.2.2 Mods

Mods are (usually) in the range -1..1.
Mods have an implicit default (reset) value of 0.
mod m_cutoff 

14.2.3 Args and envelopes

(Regular) args and curves (spline envelopes) will be baked into the plugin source.

14.2.4 Audio input

The initial value is the output of the previous sound module (voice plugin, sample, VST effect, ..).
id     my_amp 
name   "my amp" 
author bsp 
cat    amp 
 
param p_level 1 
mod   m_level 
 
<out: 
  * $m_level 
simple "amp" plugin

14.2.5 init

The <init: section can be used to implement macro parameters that calculate additional internal (voice state) variables from user defined parameters. Code placed in this section will be executed everytime a new note is played.

14.2.6 prepare

The <prepare: section is evaluated before the main section(s), and after the (optional) <init: section.
Heavy calculations, or calculations which do not need to be evaluated per sample frame should be placed in the prepare section.
Code placed in this section will be executed once per processed block. This can be used to implement macro modulation that reads the current modulation variables and writes one or many (internal voice-state) variables.

14.2.7 Free runnning oscillators

By default, the phase and noise generator states reset when a new note is played.
The sin, tri, saw, pul, pha, fsr, nos modules have a (constant) reset input that determines whether the reset occurs at note on (reset=1, default), or just once when the voice is allocated (reset=0). The cycle input should be set to 0 (cycle=0).
Example:
<out: 
  fsr cycle=0 reset=0 
free-running linear feedback shift register noise oscillator

14.3 Export

Press lctrl-p in the Cycle dialog to export the current patch as a plugin (and (re)-load it).
Press lctrl-lshift-p to toggle auto-export when patch is saved / edited (lctrl-s or after arg changes).

14.4 Compiler installation

Cycle uses Clang / GCC to compile the plugins.
No knowledge of C/C++ is required to create new plugins but the following (free) build tools have to be installed so the application can invoke them in the background.

14.4.1 Windows

Please install MSYS2 https://www.msys2.org/, then (in MSYS2 bash console) type:
$ pacman -Syu 
$ pacman -S base-devel gcc 
to update the package database and install the compiler environment.
note_s
In case MSYS2 was not installed to its default directory, edit eureka_config.tks and update the stfx_cc_msys2_dir variable accordingly.

14.4.2 macOS

Please install brew https://brew.sh/ (which will also install the XCode commandline tools):
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 
(see https://mac.install.guide/commandlinetools/3.html)

14.4.3 Linux

On Debian or Ubuntu, type
$ sudo apt update 
$ sudo apt install build-essential 

Valid HTML 4.01 Transitional

Document created on 19-Aug-2023 10:18:05