Document: | Cycle Sample Generator / Soft Synth
Author: | bsp
Date: | 01-Jun-2025

# 1 Table of Contents

# 2 Preface

`Cycle` is a modular soft-synth which emits `TKS` or `C` sample generation code, or STFX voice plugins.
It operates either in hi-fi (32bit float), or lo-fi (8bit signed integer) mode:

- hi-fi uses 32bit floating point calculations while lo-fi uses 16bit fixed point calculations with signed 8bit sample output
- in lo-fi mode, `Cycle` can be used to calculate sample data for Amiga MOD or TSR (Tiny Synergy Replay) files.
- lo-fi sub-trees can be embedded in hi-fi patches (see int).

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.

Keyword | Description
--- | ---
arg | Declare argument (will be editable in UI) (shortcut form: `a`)
arg_values | Argument preset values (shortcut form: `av`)
var | Declare variable (shortcut form: `v`)
oversample_factor oversample | Oversample factor (0.03125..32, def=1) (also supports fractional values, e.g. "1/8")

| Oversampling is ignored when exporting to lo-fi 8bit "C" sample generator source

| 8 is usually a good factor when targeting ~16kHz samples (=128kHz internal rate)

oversample_quality | Oversample quality (0..10, def=4)

| Oversampling is ignored when exporting to lo-fi 8bit "C" sample generator source

dither | `y`, `n`, or mode (0=off, 2=dithering, 3=distortion shaping, ..8=adaptive noise shaping)
rate | Override zone sample-rate
freq | Output 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
oct | Octave shift (-4..4)
dur | Override sample duration (frames or, when followed by "ms", milliseconds)
wt_w | Override wavetable width (1..16 in 2D mode, 1..128 in 1D mode)
wt_h | Override wavetable height (1..16 in 2D mode, 1 in 1D mode)
wt_cyclelen | Override wavetable cycle length (number of frames. 1..1048576)
wt_freqs | Per-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`

| the current cycle freq is available via `$wt_freq` variable during synthesis

skip | Skip output of first "n" sample frames or, when followed by "ms", milliseconds
xfade | Crossfade end of loop with samples before loop start. Sample frames or loop-len percentage (when followed by %)
mirror | Calculate first half of waveform and mirror to second half (single cycle waveforms and wavetables)
curve | Declare curve lookup-table. Must be followed by curve index (0..15) and curve id (e.g. "amp") (shortcut form: `c`)
lut | Declare procedural lookup-table. Must be followed by LUT id, optional mode (voice/shared), optional table size (default=256), and optional "inv" (inverse) flag
zone | Declare external zone reference. Must be followed by ref idx (0..) and zone id (e.g. "myzone", or "myothersmp.myzone") (shortcut form: `z`)
macro | Declare macro parameter set(s). See macros.
inc | include global `.cy` file (the suffix is auto-appended) OR include patch-local buffer when include name starts with `.`

Global Keywords

## 3.1 Plugin keywords

Keywords used in plugin-generation mode:

Keyword | Description
--- | ---
id | Plugin id (mandatory)
name | Plugin name (optional. def=<id>)

| the plugin name (and dll / so / dylib) will be prefixed with `cycle_`

author | Plugin author (optional)
cat category | Plugin category (optional. def="osc")

| unknown | limiter | pan | transient
| ringmod | enhance | pitch | convolver
| waveshaper | dac | chorus | mixer
| filter | lofi | flanger | comparator
| eq | gate | delay | utility
| compressor | amp | reverb | osc

param | Declare plugin parameter (shortcut form: `p`)
param_values | Declare plugin preset values (shortcut form: `pv`)
mod | Declare plugin modulation target (shortcut form: `m`)
modparam | Declare plugin parameter and modulation target (param will be prefixed with `p_`, modulation with `m_`) (shortcut form: `mp`)
xinc | import native code (C / C++) extension module (e.g. `xinc xamp`). The file suffix `.h` is auto-appended.

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 Output lanes

Lanes divide the script into output sections:

    <out:
      sin

hello, world.

    <layer_1:
      sin

    <layer_2:
      sin f=1.5

hello, layered world.
Each lane consists of a list or tree of modules:

    <out:
      sin
      abs
      fma

lane module list

    <out:
      sin
        phase:
          sin
          * 0.1
      abs
      fma

lane module tree

| fma converts from unipolar (0..1) to bipolar (-1..1) values by default (`*2 - 1`)

| a patch should contain at least one output section ("lane") (else the output will be 0 / silence)

## 5.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).

## 5.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

| the `init` section must occur before the first audio lane

| in plugin-mode, the `init` section is evaluated each time a new note is played (note-on)

## 5.3 Prepare lane

The `prepare` section is evaluated before the main section(s), and after the (optional) `init` section.
Heavy 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)^3 .. 1 .. 4^3
      $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

| in plugin-mode, the `prepare` section is called at a rate of 1kHz while a voice is playing.

## 5.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

| the `wt_init` section must occur before the first audio lane, and after the (optional) `init` lane

# 6 Constants

Constants are typically used for static module input values, e.g.

    <out:
      sin freq=1.5

constant input value
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

constant note ratio

| Many modules have specialized / optimized code generation paths for constant inputs. Some inputs only accept constant values (marked by `const` in the module's Inputs documentation).

# 7 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

declare args
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

declare and read arg constant
Instead of `arg`, its shortcut form `a` can be used instead:

    a lvl 0.7

    <out:
      sin
      * $lvl
      clp

arg decl shortcut form

| args and variables share the same namespace

## 7.1 Arg Preset Values

Each arg can be assigned a list of preset values (shown when hold-clicking the value widget).
Example:

    arg        angle 0 0 1
    arg_values angle 0="0 deg" 0.25="90 deg" 0.5="180 deg" 0.75="270" 1="360 deg"

arg preset values
Instead of `arg_values`, its shortcut form `av` can be used instead:

    a  mode 0 0 3
    av mode 0=red 1=green 2=blue

arg preset values shortcut syntax

| arg preset values always refer to scaled values, i.e. within min..max range

# 8 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

declare and read var
Instead of `var`, its shortcut form `v` can be used instead:

    v amp

    <out:
      2.5
      sto amp
      sin
      * $amp
      clp

variable shortcut syntax
Variables declared within a lane (or a lane within a template instantiation) will be prefixed with the lane id (and a unique template instantiation id).

    <out:
      var myvar
      2.5
      sto myvar
      sin
      * $myvar
      clp

lane-local var

## 8.1 Declare and store var shortcut

    <out:
      tri phase=0.25
      vst tri
      + $tri
        hld 0.7
      * 0.5

vst example
see vst

## 8.2 Smooth interpolation

In plugin mode, variables can be interpolated smoothly over a processing block.
When `smooth` mode is enabled, the new variable value should be written within the `<prepare:` lane.
Reading from the variable within an audio lane will returns its interpolated value.
Example:

    id smooth_var_test_1

    param p_freq 0.5

    mod m_freq

    var v_freq smooth=1

    <prepare:
      $m_freq
      # 0..1 => -1..1
      fma 2 -1
      # -1..1 => 1/4..*4
      bts 4 4
      sto v_freq

    <out:
      sin freq=$v_freq

smoothly interpolated variable

| args and variables share the same namespace

## 8.3 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)

# 9 Operators

Each module may be prefixed by an operator that determines how its output is combined with the previous module's output.

Operator | Id | Description
--- | --- | ---
= | OP_REP | Replace
+ | OP_ADD | Add
+sr | OP_ADD_SR | Add (over)samplerate-independent value
- | OP_SUB | Subtract `(a-b)`
r- | OP_RSUB | Reverse-Subtract `(b-a)`
* | OP_MUL | Multiply
*sr | OP_MUL_SR | Multiply by (over)samplerate-independent value
& | OP_AND | Bitwise and
| | OP_OR | Bitwise or
^ | OP_EOR | Bitwise exclusive-or
&& | OP_LAND | Logical and `(a > 0) & (b > 0)`
|| | OP_LOR | Logical or `(a > 0) | (b > 0)`
^^ | OP_LEOR | Logical eor `(a > 0) ^ (b > 0)`
!& | OP_LNAND | Logical nand `!((a > 0) & (b > 0))`
!| | OP_LNOR | Logical nor `!((a > 0) | (b > 0))`
!^ | OP_LNEOR | Logical neor `!((a > 0) ^ (b > 0))`
m | OP_MIN | Minimum
x | OP_MAX | Maximum
M | OP_ABSMIN | Absolute minimum
X | OP_ABSMAX | Absolute maximum
. | OP_NONE | Emit but discard result of sub-tree
_ | OP_SKIP | Skip sub-tree (do not emit)
am | OP_AM | Arithmetic mean
qm | OP_QM | Quadratic mean sqrt(aa+bb)
QM | OP_QMU | Unipolar Quadratic mean rescaled to -1..1
hm | OP_HM | Harmonic mean `(2*a*b)/(a+b)`
HM | OP_HMU | Unipolar Harmonic mean rescaled to -1..1
gm | OP_GM | Geometric mean `sign(a*b)*sqrt(abs(a*b))`
GM | OP_GMU | Unipolar Geometric mean rescaled to -1..1

Operators

| The initial output value of a sub-tree is copied from the parent output value. While this will immediately be overwritten by a new value in most cases, it allows a sub-tree to mix in a processed version of the parent output, e.g. using a dly:
    <out:
      sin
      rmp 50 1 0
      pow 3
      + dly n=0.2
        * 0.25
process parent output

| The `_` operator can be used for (temporarily) skipping a sub-tree (e.g. for testing purposes):
    <out:
      sin
      + sin f=1.5
        * 0.25
sub-tree enabled
    <out:
      sin
      _ sin f=1.5
        * 0.25
sub-tree disabled

The default operator is `=` (`replace`):

    <out:
      = sin

default op

    <out:
      sin

default op short form

## 9.1 Truth table

Logical operator truth table:

    a b  &&   ||   ^^   !&   !|   !^
    0 0  0    0    0    1    1    1
    0 1  0    1    1    1    0    0
    1 0  0    1    1    1    0    0
    1 1  1    1    0    0    0    1

truth table

| can be used for pulse / gate signal math (sequencers)

# 10 Modules

Modules generate new sample frames or process the output of the previous module.
Static / constant module input values are set by `key=value` pairs, or simply by value (in internal parameter order)

    <out:
      saw
      lpf freq=0.4 res=0.5
      lpf 0.4 0.5

const module input example
Dynamic module input value sub-trees must be placed on separate, indented lines.
Modules thus become nested (stacked), i.e. module inputs can be controlled by the output of other modules:

    <out:
      sin
        freq:
          sin 5.0
          * 0.25   # could also be written as  fma 0.25 1
          + 1
      * 3.0
      clp

stacked / dynamic module input example
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.

| some module inputs have implicit previous output values, like the `pd` (phase-distortion) insert of the pha module.

| in the Cycle machine source editor, press `lctrl-i` to expand a basic `key=value` input to (an arbitrarily complex) input sub-tree

## 10.1 abs

Return absolute value of previous output.

### Example

    <out:
      -42
      abs

absolute
This is equivalent to full-wave rectification (fwr).
See hwr for half-wave rectification.

## 10.2 adsr

Analog-style Attack / Decay / Sustain / Release envelope generator.
The previous output should be a gate signal (rising edge restarts envelope, falling edge starts release phase).

### Inputs

| `gate` / `g` | gate input (when not connected, use previous output)
| `a` | (attack rate) range is 0..1
| `as` | (attack shape) range is -16..16
| `d` | (decay rate) range is 0..1
| `ds` | (decay shape) range is -16..16
| `s` | (sustain level) range is 0..1
| `r` | (release rate) range is 0..1
| `rs` | (decay shape) range is -16..16`
const | `reset` | 0=reset level only during voice allocation ("analog") 1=reset level when envelope restarts ("digital")
| `scl` | sets rate scaling (0..f) (e.g. 0.01=suitable for "prepare" lane (1000Hz rate), 0.001=suitable for audio rate) (default is 0.001)
const | `mode` / `m` | selects envelope flavour. 0=rate-shaping (v2/default, smoother). 1=level-shaping (v1, more percussive)
const | `vel` / `v` | enables velocity (gate level) scaling (1=on, 0=off)
| `onend` / `oe` | evaluated when end of envelope is reached

| it's often sufficient to calculate envelopes in a `<prepare:` lane (i.e. per 1kHz sample block) instead of per sample frame

## 10.3 apf

All-pass filter (1st order) previous output.

### Examples

    arg p_amp 0.7 0 8
    arg p_c   0.5 -1 1

    <out:
      saw
      apf c=$p_c
      * rmp 0 1
      * $p_amp
      clp

test apf 1 example patch

    arg p_amp 0.7 0 8
    arg p_f   1.0 0 1

    <out:
      saw
      apf f=$p_f
      * rmp 0 1
      * $p_amp
      clp

test apf 2 example patch

    arg p_amp 0.7 0 8
    arg p_f   1.0 0 1

    <out:
      saw
      apf ## dynamic frequency input (=> force float mode)
        f:
          $p_f
          * rmp 0 1
      * rmp 0 1
      * $p_amp
      clp

test apf 3 example patch

    id simple_phaser

    mp c 0.5
    mp ap_mix 0.5

    <out:
      $c
      fma #0..1=>-1..1
      vst v_c

      saw
      ipl t=$ap_mix
        b:
          rep 4
            apf c=$v_c
      clp

test apf 4 simple_phaser plugin

### Inputs

| `c` | sets filter coefficient (-1..1)
| `freq` / `f` | sets normalized filter frequency (0..1) (overrides coefficient input)

| when the `freq` input is connected and it is not a constant value, the module falls back to float mode (for tan())

## 10.4 bit

Bit-crush (shift) previous output.
Converts the input to a 16 bit integer and right-shifts it by `shift*15` bits.

### Examples

    <out:
      sin
      bit shift=0.5
      bit 0.5

8 bit reduction

    arg p_amp 0.7 0 8
    arg p_bit 8 0 15

    def bit_linear bit=8
      vst p
      $p
      bit
        shift:
          %bit%
          itg
          * 1/15
      ipl
        b:
          $p
          bit
            shift:
             %bit%
             itg
             fam 1 1/15
        t:
          %bit%
          frc

    <out:
      sin
      ~bit_linear bit=$p_bit
      * rmp 0 1
      * $p_amp
      clp

linear bit reduction template

    arg p_amp 0.7 0 8
    arg p_bit 8 0 15

    inc bit_linear

    <out:
      sin
      ~bit_linear bit=$p_bit
      * rmp 0 1
      * $p_amp
      clp

linear bit reduction template include

### Inputs

| `shift` / `s` | range is 0..1 (0..16 bits)

## 10.5 boo

Boost sample deltas of previous output.

### Example

    <out:
      sin
      boo amount=0.6
      boo 0.6

boost sample deltas

### Inputs

| `amount` / `amt` | range is 0..1 |
const | `sr` | 1=(over)samplerate-independent amount. | def=1

## 10.6 box

Box filter previous output.
Simple but computationally inexpensive.

### Example

    <out:
      saw
      box freq=0.5
      box 0.5

box lowpass filter

### Inputs

| `freq` / `f` | range is 0..1 |
const | `sr` | 1=(over)samplerate-independent filter freq. | def=1

## 10.7 bpf

reserved

| use svf in `mode=bpf` instead

## 10.8 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

bipolar to exponential scale

### Inputs

| `div` / `d` | range is 1..32 | def=8
| `mul` / `m` | range is 1..32 | def=8

| uses `div` input when `mul` is not connected

| only supported in float-mode (fall back to float in int mode)

## 10.9 buf

not implemented, yet

## 10.10 ceq

    var t
    <out:
      $t
      ceq 0
        1
      sto t

ceq example

### Inputs

| `rhs` | right-hand side value

see cmp

## 10.11 cge

    var t
    <init:
      set t 0

    <out:
      $t
      +sr 0.0001
      cge 1
        1
      sto t

cge example

### Inputs

| `rhs` | right-hand side value

see cmp

## 10.12 cgt

    var t
    <init:
      set t 0

    <out:
      $t
      +sr 0.0001
      cgt 1
        1
      sto t

cgt example

### Inputs

| `rhs` | right-hand side value

see cmp

## 10.13 cle

    var t
    <init:
      set t 0

    <out:
      $t
      cle 0.5
        +sr 0.001
      sto t

cle example

### Inputs

| `rhs` | right-hand side value

see cmp

## 10.14 clk

Generate clock pulse width configurable duty cycle duration.

### Example

    id clk_test
    <out:
      sin
      * clk div=1/4
        adsr

modulate sine level by adsr env which is retriggered every 16th note

### Inputs

| `hz` | range is 0..f (clock ticks per second) |
| `bpm` | range is 0..f (0=host bpm, beats per minute (quarter notes) otherwise. used when bpm is not connected) | def=0.0
| `div` | range is 0..f (clock divider. <1 speeds up clock, >1 slows it down) | def=1.0
| `dur` | duty cycle duration in milliseconds | def=5
const | `force` / `f` | 1=emit 0 at end of duty cycle when cycle duration is longer than interval | def=1
| `restart` / `rs` | restart clock counter on rising edge |
| `swing` / `sw` | <0 pull every 2nd pulse >0 push/delay every 2nd pulse | def=0.0
| `onclk` / `oc` | evaluated on each clock pulse. must return new pulse level (0..1, 0 discards pulse) |

## 10.15 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 ceil=0.6 floor=-0.75

separate boundaries

    <out:
      sin
      * 7
      # same as clp ceil=0.6 floor=-0.75
      clp 0.6 -0.75

separate boundaries (short form)

### Inputs

| `ceil` / `c` | range is 0..1 (default=1)
| `floor` / `f` | range is -1..1 (default=-1) (when not connected, floor = -ceil)

## 10.16 clt

    var t
    <init:
      set t 1

    <out:
      $t
      +sr -0.001
      clt 0
        0
      sto t

clt example

### Inputs

| `rhs` / `r` | right-hand side value

see cmp

## 10.17 cmp

Compare previous output value to right-hand side value and run statement sequence when result is true.
Supported operators: `<`, `<=`, `==`, `!=`, `>=`, `>`.

    var t
    <init:
      set t 1

    <out:
      $t
      +sr -0.001
      cmp < 0
        0
      sto t

cmp example

    id cmp_test_2
    var t
    <out:
      tri
      fma 0.6 0.4
      sto t
      sin
      clt
        rhs:
          $t
          neg
        -1
      cgt $t
        1
      box 0.8

cmp example plugin

    id test_cmp_else_1

    <prepare:
      tri
      clt 0
          -1
        else:
          1
      trc

    <out:
      sin

if / else example

### Inputs

| `rhs` / `r` | right-hand side value
| `else` | evaluated when comparison failed

| see clt, cle, ceq, cne, cge, cgt short cut modules

## 10.18 cne

    var t
    <out:
      $t
      cne 1
        1
      sto t

cne example

### Inputs

| `rhs` | right-hand side value

see cmp

## 10.19 con

reserved

## 10.20 dec

Decrement variable
If the `delta` input is not connected, decrement variable by previous output value.

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

decrement variable

    var f
    <out:
      set f 0
      0.1
      dec f

decrement by 0.1

### Inputs

| `delta` | when connected, decrement variable by given value. when not connected, decrement by previous output value.

## 10.21 div

Divide values.

### Example

    arg p_num 16 1 32
    <out:
      div 1.0 $p_num

output 1 / $p_num

### Inputs

| `a` | nominator input
| `b` | denominator input

| useful in conjunction with `rep` / `lop`, e.g. when indexing LUTs

| this is a rather expensive operation in integer / lo-fi mode (uses iterative approximation when inputs are not constant)

## 10.22 dly

Store previous output value in delay ring buffer read delayed value.
If the `freq` input is connected, the module is set to tuned-delay-line mode (key follow) and the input value determines the delay length scaling (e.g. 2.0 doubles the length and thus transposes the delay one octave down).

### Examples

    arg p_amp 0.7 0 2

    <out:
      sin
      * rmp 50 1 0
        pow 3
      + dly len=0.08 fb=0.5
        * 0.25
      * $p_amp
      clp

dly test 1

    # test feedback insert
    arg p_amp 0.7 0 2

    <out:
      saw
      * rmp 100 1 0
        pow 3
      svf r=1
        f:
          rmp 70 1 0
          pow 3
          p2s
          fma 0.8 0.2
      + dly size=32768 len=0.2 fb=0.5
          read:
            svf f=0.1 r=0.6
        * 0.25
      * $p_amp
      clp

dly test 2

    # tuned delay line test
    skip 180
    oct  0

    arg p_amp     0.7  0   2
    arg p_scl     1    0   4
    arg p_fb      0.67 0   1
    arg p_ins_flt 0.1  0   1
    arg p_ins_res 0.64 0.1 1
    arg p_mix     0.9  0   1

    <out:
      fsr
      * rmp 10 1 0
        pow 3
      * 1.0
      svf r=0.5
        f:
          rmp 50 1 0
          pow 3
          p2s
          fma 0.5 0.5
      ipl t=$p_mix
        b:
          dly s=1024 fb=$p_fb
            f:
              $p_scl
            r:
              svf r=$p_ins_res
                f:
                  $p_ins_flt
                  p2s
      * $p_amp
      #hbx f=0.7
      clp

dly test 4

    # tuned delay line voice plugin test
    id test_dly_stfx_4

    mp dly_scl  0.224
    mp dly_fb   0.889
    mp dly_mix  0.987
    mp osc_lpf  0.919
    mp osc_res  1.0
    mp dly_lpf  0.228
    mp dly_res  1.0
    mp osc_lvl  1.0

    <out:
      fsr reset=0
      * $m_osc_lvl

      svf r=$m_osc_res
        f:
          $m_osc_lpf
          kbd mode=1 amt=1

      ipl
        t:
          $m_dly_mix
          clp 1 0
        b:
          dly s=8192 fb=$m_dly_fb lin=1
            f:
              $m_dly_scl
              fma 2 -1
              bts 16 16
            read:
              svf
                r:
                  $m_dly_res
                f:
                  $m_dly_lpf
                  kbd mode=1 amt=1
      clp

dly test stfx 4

### Inputs

const | `size` / `s` | maximum delay buffer size in frames | def=32768
| `len` / `n` | current delay buffer length (0..1 ⇒ 0..size) | def=0.5
| `freq` / `f` | tuned delay line rate divider (1=note speed, 0.5=1 oct up, ..) (when connected) |
| `feedback` / `fb` | feedback amount (0..1) | def=0
const | `lin` / `l` | 1=linear interpolation 0=nearest neighbour) | def=0 (nearest)
| `read` / `r` | delay-read insert (pre feedback) |
| `write` / `w` | delay-write insert (post feedback) |

| in lo-fi / integer mode, the delay buffer size is limited to 32767 frames

| the delay size is multiplied by the oversample factor

| linear interpolation is only supported in hi-fi / float mode

see xdly

## 10.23 drv

reserved

## 10.24 end

Close current lane.

| optional. can be used for placing lut declarations in between lanes.

| when lane is a lut init lane, skip the output write (lane must write to lut via sta instead)

## 10.25 env

Generates a three-segment (Attack/Decay, Sustain, Release) envelope.
Previous output is gate signal (rising edge (re-)starts envelope, falling edge starts release phase).
The subtree takes the current normalized envelope position (0..1) as input and must return the envelope level for that position.

### Example

    id env_1
    # (note) please map "Gate" to plugin mod "gate" in the mod matrix or the envelope won't trigger
    mod m_gate
    # Bezier curve 0 stores the envelope shape
    curve 0 env
    <out:
      sin
      * $m_gate
        env ad=0.07 s=0.4 r=0.01 scl=0.001
          # subtree returns env level for normalized position input 0..1
          lut env lin=1 clamp=1

env example

### 10.25.1 loop

When `loop` mode is enabled, the gate-off signal is ignored and the envelope will repeat continously, switching `ad` / `r` rates at the `s`ustain position.
This can (also) be used for audio-rate waveform synthesis.

| scale the rates by `kbd mode=2 amt=1` for chromatic key tracking

### Example

    id env_3
    oversample 4

    modparam ad 0.5 0 1
    modparam s 0.5
    modparam r 0.5

    # Bezier curve 'env' stores the envelope shape
    curve env

    var v_ad  smooth=1
    var v_s   smooth=1
    var v_r   smooth=1

    <prepare:
      $m_ad
      clp 1 0
      pow 3
      kbd mode=2 amt=1
      sto v_ad

      $m_s
      sto v_s

      $m_r
      clp 1 0
      pow 3
      kbd mode=2 amt=1
      sto v_r

    <out:
      sin
      * 1 # gate
        env ad=$v_ad s=$v_s r=$v_r scl=0.01 loop=1
          lut env lin=1 clamp=1
        # slw 0.05 0.1

looping audio-rate envelope

### Inputs

| `ad` | attack/decay rate (0..1) | def=0.01
| `s` | sustain position (0..1) | def=0.5
| `r` | release rate (0..1) | def=0.01
const | `loop` / `lp` | 1=loop 0=oneshot / halt at sustain pos | def=0 (off)
| `restart` / `rs` | restart signal |
| `sr` | enable sample-rate independent envelope speed (must be const) (voice plugins only) | def=1 (on)
| `onend` / `oe` | evaluated when end of envelope is reached / envelope loops |
| `scl` | envelope rate scaling (0..1) | def=0.001
const | `tuned` / `tun` | normalize envelope rates and follow pitch (auto-enables loop mode) | def=0 (off)

## 10.26 eq3

A simple 3-band equalizer

    arg p_amp 0.7 0 8

    arg fl 0.1 0 1
    arg fh 0.2 0 1
    arg gl 0.5 -2 2
    arg gm 0.5 -2 2
    arg gh 0.5 -2 2
    arg eq3_amt 1 0 1
    arg p_amp_eq 1.5 0.5 4

    <out:
      saw
      * $p_amp
      ipl t=$eq3_amt
        b:
         eq3 freqlo=$fl freqhi=$fh gainlo=$gl gainmid=$gm gainhi=$gh
         * $p_amp_eq
      clp

eq3 test 1 example

### Inputs

| `freqlo` / `fl` | Low frequency (0..1) | def=0.5
| `freqhi` / `fh` | High frequency (0..1) | def=0.5
| `gainlo` / `gl` | Low Gain (negative values invert phase) | def=0.5
| `gainmid` / `gm` | Mid Gain | def=0.5
| `gainhi` / `gh` | High Gain | def=0.5
const | `sr` | 1=(over)samplerate-independent freqlo/freqhi. | def=1

## 10.27 fed

Detect logic signal falling edge and evaluate sub-tree.

    id edge_detect

    var v_out
    var v_dcy

    <out:
      pul
      red
        set v_dcy 0.999
        set v_out 1
      fed
        set v_dcy 0.95
      $v_out
      *sr $v_dcy
      sto v_out
      fma 2 -1

rising / falling edge detect osc

| this module has no inputs

see also: red

## 10.28 fix

Replace NaN (Not a Number) and Infinity values by 0.
This should not be necessary in regular patches but may be useful for debugging purposes.

## 10.29 fld

Fold previous output value at ceiling.

### Examples

    <out:
      sin
      * 2.0
      fld ceil=1.0

fold sine oscillator

| `ceil` sets min=-ceil, max=+ceil

    <out:
      sin
      * 2.0
      fld min=-1.0 max=1.0

fold sine oscillator (separate min / max)

| `min` / `max` override default ceil

### Inputs

| `ceil` / `c` | sets min=-val, max=+val | def=1.0
| `min` / `m` | minimum value |
| `max` / `x` | maximum value |
const | `hq` | 1=enable high-quality (float) mode (arbitrary number of folds). | def=1 (on)

see also: wrp

## 10.30 fam

Fused add multiply: `out = (out + add) * mul`

### Examples

    <out:
      sin
      # -1..+1 => 0..1
      fam

bipolar to unipolar example

    arg exp 1 0 16

    <out:
      sin
      fam # -1..+1 => 0..1
      pow $exp
      fma # 0..1 => -1..+1

test_fam_1 example patch

### Inputs

| `add` / `a` | input is in range -15..15 | def=1.0
| `mul` / `m` | input is in range -15..15 | def=0.5

## 10.31 fma

Fused multiply add: `out = out * mul + add`

### Example

    <out:
      sin
      # -1..+1 => 0..1
      fma 0.5 0.5
      # 0..1 => -1..+1
      fma

fused multiply add

| by default, fma converts unipolar values to bipolar (`0..1 ⇒ -1..1`) while fam converts bipolar to unipolar (`-1..1 ⇒ 0..1`)

    <out:
      sin
      fam ## to unipolar
      pow
      fma ## back to bipolar

bipolar ↔ unipolar

### Inputs

| `mul` / `m` | input is in range -15..15 | def=2.0
| `add` / `a` | input is in range -15..15 | def=-1.0

## 10.32 frc

Calculate fractional part of previous output (discard integer bits)

### Examples

    <out:
      1.67
      frc      # => 0.67
      trc

      -1.67
      frc      # => 0.33
      trc

      -1.67
      frc s=1  # => -0.67
      trc

unsigned and signed fractional parts

    <out:
      sin
      * 3.3
      frc s=1

test frc 1 example patch

### Inputs

const | `signed` / `s` | 0=unsigned mode 1=signed mode | def=0 (unsigned)

| the (default) unsigned mode is especially useful for oscillator phases (i.e. to wrap any value into the normalized 0..1 range)

| itg does the opposite and discards all fractional bits

## 10.33 fsr

Generate linear feedback shift register ("C64") noise (normalized to -1..1).

### Examples

    <out:
      fsr

noise

    id noise
    <out:
      fsr seed=$4489 bits=1.0 reset=0

noise voice plugin with custom seed and no note-on reset

### Inputs

const | `seed` / `s` | is the initial shift register state (16 bits). must not be 0. | def=$4489
const | `bits` / `b` | determines the number of output bits (1.0=all bits, 0=1 bit (actually two, sign+msb). | def=1.0
const | `cycle` / `cy` | determines whether the shift register state resets at each (wavetable) cycle. Ignored in plugin mode. | def=1 (true)
const | `shift1` / `sh1` | is the first feedback shift (negative=shift left). -15..15. | def=7
const | `shift2` / `sh2` | is the second feedback shift. -15..15. | def=-9
const | `shift3` / `sh3` | is the third feedback shift. -15..15. | def=13
const | `reset` / `rs` | range is 0..1 (0=disable plugin voice note on reset and eval only when voice is allocated) | def=1
const | `signed` / `sgn` | 1=generate signed values (-1..1) 0=generate unsigned values (0..1) | def=1

| changing the number of `bits` does not make an audible difference, except when the output is used as a controller value

| `fsr` noise contains less low frequencies than `nos` noise

## 10.34 fwr

Discard sign of previous output and optionally rescale to -1..1 range.

### Examples

    <out:
      -0.8
      fwr   # ==> 0.8
      0.9 b=1
      fwr   # ==> 0.8  (== 0.9*2-1)

uni- and bipolar full wave rectification

    id fwr_test_1

    mp amp 0.5

    <out:
      sin
      fwr b=1  ## bipolar mode scales 0..1 output to -1..1
      * $m_amp
        * 2
      clp

fwr_test_1 voice plugin example
See hwr for half-wave rectification.

### Inputs

const | `bipolar` / `b` | range is 0/1 (1=rescale to -1..1 range) | def=0 (unipolar)
const | `neg` / `n` | range is 0/1 (1=flip output sign) | def=0 (positive)

## 10.35 hbx

High-pass box filter previous output.
Simple but computationally inexpensive.

### Example

    <out:
      saw
      hbx freq=0.5
      hbx 0.5

high-pass box filter

### Inputs

| `freq` / `f` | range is 0..1 | def=0.5
const | `sr` | 1=(over)samplerate-independent freq. | def=1 (on)

## 10.36 hwr

Clip previous output to 0..f and optionally rescale to -1..1 range.

### Examples

    <out:
      -0.8
      hwr   # ==> 0
      0.9 b=1
      hwr   # ==> 0.8  (== 0.9*2-1)

half-wave rectification

    id hwr_test_2_max
    <out:
      sin
      vst a
      sin phase=0.1 f=2.01
      vst b
      $a   # max(a,b) = (b+hwr(a-b))
      - $b
      hwr
      + $b

max

    id hwr_test_3_min
    <out:
      sin
      vst a
      sin phase=0.1 f=2.01
      vst b
      # min(a,b) = (a-hwr(a-b))
      $a
      - $b
      hwr n=1   # -hwr
      + $a

min
See fwr / abs for full-wave rectification.

### Inputs

const | `bipolar` / `b` | range is 0/1 (1=rescale to -1..1 range) | def=0 (unipolar)
const | `neg` / `n` | range is 0/1 (1=flip output sign) | def=0 (positive)

## 10.37 hld

Sample and hold previous output.

### Example

    <out:
      hld rate=0.5

sample and hold

### Inputs

| `rate` / `r` | range is 0..1 (⇒ 0..samplerate) | def=1.0
const | `sr` | 1=(over)samplerate-independent freq. 0=fixed freq. | def=1 (on)
| `onsample` / `os` | evaluated when a new sample is taken. input is previous output, output is new sample&hold value. does not modify previous output. |

| when 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`

## 10.38 hpf

High-pass filter previous output. Uses preconfigured, static cutoff/resonance configurations (biquad coefficients).

### Example

    <out:
      saw
      hpf cfg=3

high-pass filter

### 10.38.1 Filter configurations

cfg | cutoff | resonance
--- | --- | ---
0 | 127 | 0
1 | 127 | 108
2 | 122 | 0
3 | 122 | 108
4 | 115 | 0
5 | 115 | 100
6 | 110 | 0
7 | 110 | 100
8 | 100 | 0
9 | 100 | 97
10 | 90 | 0
11 | 90 | 70
12 | 77 | 0
13 | 77 | 79

### Inputs

const | `cfg` / `c` | selects filter config (0..13) | def=0

## 10.39 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

compile time if / else cascade

### Inputs

| `then` | evaluated when expression is true
| `else` | evaluated when expression is false

## 10.40 inc

Increment variable
If the `delta` input is not connected, increment variable by previous output value.

### Examples

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

increment using delta input

    var f
    <out:
      set f 0
      0.1
      inc f

increment by 0.1

### Inputs

| `delta` | when connected, increment variable by given value. when not connected, increment by previous output value.

## 10.41 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.

### Example

    <out:
      int
        sin

integer sub-tree

| in integer mode, no conversions take place and the sub-tree is emitted as usual

## 10.42 ipl

Interpolate from `a` to `b` at time `t`.
The module behaves differently depending on which inputs are connected:

- when `a` is connected and `b` is not, the previous output is used as the destination
- when `b` is connected and `a` is not, the previous output is used as the source
- when `a` and `b` are connected and `t` is not, the previous output is used as the interpolation amount
- when `a`, `b` and `t` are connected, the previous output is ignored

### Examples

    arg a_blend 0 0 1
    <out:
      ipl win=smoothstep
        a:
          sin
        b:
          tri
        t:
          $a_blend

blend between sine and triangle using a smoothstep window

    id osc_vsync

    param p_vsync       0 0 1
    param p_window_amt  1 0 1
    param p_window_freq 0 0 1

    mod m_vsync
    mod m_window_amt
    mod m_window_freq

    <out:
      sin
        vsync:
          # 0..1 => 1..16
          $m_vsync
          fma 15 1
      # interpolate between sine wave and 0 using a triangle window
      #  (note) the 'a' input of the 'ipl' module is the previous output (sin)
      #          (since 'a' is not explicitily connected)
      ipl b=0
        t:
          1.0
          - tri
             freq:
               # 0..1 => 1..16
               $m_window_freq
               fma 15 1
               # to integer
               itg
            # -1..1 => 0..1
            fma 0.5 0.5
          * $m_window_amt

virtual oscillator sync voice plugin that uses a triangle window with customizable frequency

### Inputs

| `a` | source value | def=0.0
| `b` | destination value | def=1.0
| `t` | interpolation amount | def=1.0
const | `win` | interpolation window. 0=linear, 1=smoothstep, 2=in_quad, 3=out_quad, 4=in_cubic, 5=out_cubic, 6=in_quintic, 7=out_quintic | def=0 (linear)

| The initial input to `a`, `b`, `t` (mode-dependent) is the previous output. This can be used to crossfade between processed versions of the input signal.

## 10.43 itg

Calculate integral part of previous output (discard fractional bits)

### Example

    <out:
      sin
      * 16
      itg
      * 1/16

cast to integer

| frc does the opposite and discards all integer bits

## 10.44 kbd

`mode=0`:

- scale input by 127 (normalized float to MIDI note), add `(current_note - 64 + (off * 12))`, clamp to 0..127 (when `clamp=1`), then convert the note to its corresponding frequency and divides it by the sample rate (back to normalized 0..1 range).
- ignore `ctr` input (can use `off` instead)
- useful for filter cutoff frequencies (linear to exponential).
- factors in current samplerate and oversamplefactor
  - caution when passing the output to another module which also does this !

`mode=1`:

- equal tempered rate scaling
- multiply input by note rate. keyboard tracking is centered around `ctr` note (def=60=5*12=C-5).
- e.g. at center note and `off=0`, rate is 1.0. at one octave above it's 2.0, and at one octave below it's 0.5
- ignore `scl` and `clamp` inputs
- useful for scaling oscillator `freq` (set osc `fixed=1` to disable built-in keyboard tracking)

`mode=2`:

- linear(exponent) rate scaling
- multiply input by note rate. keyboard tracking is centered around `ctr` note (def=60=5*12=C-5).
- e.g. at center note and `off=0`, rate is 1.0. at one octave above it's 2.0, and at one octave below it's 0.5
- ignore `scl` and `clamp` inputs
- can also be used for scaling oscillator `freq` (set osc `fixed=1` to disable built-in keyboard tracking)
- the CPU load is lighter but the rate does not exactly follow the equal tempered scale

### Examples

    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

    id     pitch_kbd_tracking_test
    name   "pitch kbd tracking test"
    author bsp
    cat    osc

    param p_keyfollow 1 0 1
    param p_octave_off 0 -4 4

    <out:
      sin fixed=1
        freq:
          1.0
          kbd mode=1 amt=$p_keyfollow off=$p_octave_off ctr=60

oscillator pitch keyboard tracking example plugin

### Inputs

| `amt` / `a` | is in the range -16..16 (keyboard follow amount) | def=1.0
| `off` / `o` | is in the range -4..4 (octave offset) | def=0.0
| `scl` / `s` | is in the range 0..16 (post scaling) | def=4.0
const | `clamp` / `c` | toggles clamp-to-1.0 (should be set to 0 when using this for pitch tracking) | def=1 (on)
const | `mode` / `m` | selects mode (0=filter 1=eqtemp 2=lin_exp) | def=0 (filter)
const | `ctr` | sets keyboard center note (only used in `pitch=1` mode) | def=60 (C-5)

| only supported in plugin / float mode (fall back to float within int-subtree)

## 10.45 lle

Scale previous output by logarithmic(`c<0`)..linear(`c=0`)..exponential(`c>0`) factor

### Example

    <out:
      8
      lle c=1.4
      trc

log / lin / exp

### Inputs

| `c` | is in the range -f..f. | def=0.0

| only supported in float-mode (fall back to float in int mode)

## 10.46 log

Return logarithm of previous output.

### Example

    <out:
      8
      log base=2
      trc

calc log2(8)

### Inputs

| `base` / `b` | range is 1..f. When not connected, return the natural logarithm.

| only supported in float-mode (fall back to float in int mode)

## 10.47 lop

Loop statement sequence. Supports dynamic loop iterators.

    <out:
      2.0
      * 4.0
      vst num
      0.0
      vst i
      lop $num
        + 0.01

dynamic loop

### Inputs

| `num` / `n` | range is 0..1023 (0..15 in integer / lo-fi mode) | def=1

| 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.

| use `rep` to generate unrolled loops where each iteration will have its own state

## 10.48 lpf

1RC resonant low pass filter.
Simple but computationally inexpensive.

### Example

    <out:
      saw
      lpf freq=0.4 res=0.5
      lpf 0.4 0.5

low-pass filter

### Inputs

| `freq` / `f` | range is 0..1 | def=0.5
| `res` / `r` | range is 0..1 | def=0.5

## 10.49 lut

Use previous output as normalized 0..1 index for curve look-up table (LUT).
The first argument is either the name of a curve, or the name of a plugin array param that is associated with a curve.

### Example

    curve amp

    <out:
      sin
      * rmp 0 0 1
        lut amp clamp=0 lin=0

cubic bezier curve lookup-table

### Inputs

const | `clamp` / `c` | must be either 0 (repeat lut) or 1 (clamp to last index) | def=0(wrap-around)
const | `lin` / `l` | must be either 0 (nearest neighbour) or 1 (linear interpolation) | def=0(nearest)
const | `scl` / `s` | scales the index by a float number (hifi mode) or clamps to the index before last (lofi mode, when scl != 1.0) (at compile time). This can be used to avoid (costly) index clamping in linear filtered mode by using wrap mode instead (`clamp=0`) and scaling the index by e.g. `scl=31/32` (when LUT / curve has 32 elements) to avoid filtering the last with the first element when the index approaches 1.0. | def=1.0
const | `bipolar` / `b` | enable bipolar index mode (flip sign of index+output as required) | def=0(unipolar)
const | `in` / `i` | 1=scale index input (0..1) by 0..size-1 instead of 0..size | def=0(*0..size)

| the `lut` module is often combined with a rmp or pha module that generates the look-up index

| when using a lut as a replacement table for e.g. oscillator phases (0..1), `in` should be set to 1

## 10.50 mac

The `mac` module updates the current, interpolated values of a macro's fields.
See macros for further information and examples.

### Inputs

| `pos` / `p` | range is 0..1 (last setting is lerped back to first setting) | def=0.0

## 10.51 map

Remap previous output value using a non-contiguous table.

### Inputs

const | `mode` / `m` | 0=`nearest` 1=`match` 2=`up` 3=`down` 4=`ipl`/`lin` | def=0 (nearest)
| `def` | when connected, emit default value when value could not be mapped |
| `ipld` | when connected, distort interpolation `t` (when mode=`ipl`) (in=0..1, out=0..1) |

See Maps for further info and example code.

## 10.52 mkv

A dual state, probability driven generator that is not just useful for weather forecasts but also for sound synthesis.
Two probabilities `ab` and `ba` determine the chances of transitioning from state a to b (`ab`) or from state b to a (`ba`).
See `markov_bd_1`, `markov_sd_1`, `markov_hh_1` example patches.

### Inputs

| `a` | input A | def=0.0
| `b` | input B | def=1.0
| `r` | random input. use previous output value when not connected. |
| `ab` | A => B probability (0..1) | def=0.5
| `ba` | B => A probability (0..1) | def=0.5
| `onb` | evaluated when entering "b" state |
| `ona` | evaluated when entering "a" state |
| `act` | 1=always evaluate `a` and `b` input. 2=evaluate only during state transitions (initial state is floating) | def=1 (always eval a + b)

## 10.53 mul

Multiply variable
If the `factor`/`f` input is not connected, multiply variable by previous output value.

### Examples

    var f
    <out:
      set f 1.0
      rep 16
        mul f 0.5
        mul f factor=0.5
        mul f
          factor:
            0.5

multiply variable by input

    var f
    <out:
      set f 2.0
      0.5
      mul f

multiply variable by previous output

### Inputs

| `factor` | when connected, multiply by input instead of previous output value

## 10.54 neg

Negate previous output

### Example

    <out:
      saw
      neg

flip phase

## 10.55 nos

Render white noise via xor-shift pseudo random number generator

### Example

    <out:
      nos seed=0x44894489 cycle=0

xor-shift noise

### Inputs

const | `seed` / `s` | is the random seed (a 32bit integer). | the default seed is 0x3d9fb971
const | `cycle` / `cy` | range is 0/1 (0=disable wavetable cycle reset) | def=1 (on)
const | `reset` / `rs` | range is 0/1 (0=disable plugin voice note on reset) | def=1 (on)

## 10.56 not

Invert previous logic signal output

### Examples

    <out:
      1
      # 1 => 0
      not
      # 0 => 1
      not

invert previous output

    var v_state

    <out:
      # invert the boolean state of the given variable
      not v_state

invert boolean variable

## 10.57 note

Returns the current voice note number (0..127).

### Example

    id note_test_1
    <out:
      sin
        freq:
          0.1
          + note
            * 1/32

note example
`note` can also be used in short form value assignments to (dynamic) inputs.

| only available in voice plugin mode. in sample synth mode, this simply returns 60.0 (middle-C).

## 10.58 nth

Discards all clock signals except for every nth one.

### Examples

    <out:
      clk div=1/4
      nth 4

pass every 4th 16th note clock signal (e.g. a base drum)

    <out:
      clk div=1/4
      nth 8 shift=-4

pass every 8th 16th note clock signal, starting with the 4th one (e.g. a snare drum)

### Inputs

const | `every` / `e` | pass every nth pulse (const integer) | def=1
const | `shift` / `s` | shift pulse counter (<0 delay first passed pulse) | def=0
| `restart` / `rs` | reset pulse counter on rising edge |
| `onclk` / `oc` | run statement block when duty cycle begins. must return pulse level scaling (0..1, 0 discards pulse) |

## 10.59 pal

Calc left channel pan factor

### Example

    id pan_test
    modparam vol 1
    modparam pan 0.5

    var v_vol_l  smooth=1
    var v_vol_r  smooth=1

    <prepare:
      $pan
      fma 2 -1
      vst vpan

      $vol
      . pre
        * pal $vpan
        sto v_vol_l
      * par $vpan
      sto v_vol_r

    <out:
      sin
      wrl a=$v_vol_l
      wrr a=$v_vol_r

stereo-panned sine

### Inputs

| `pos` / `p` | pan position (-1..1) | def=0(center)

see also: wrl

## 10.60 par

Calc right channel pan factor

### Inputs

| `pos` / `p` | pan position (-1..1) | def=0(center)

see also: wrr

## 10.61 pha

Generate phase output (0..1 normalized saw-wave).

### Example

    curve mywaveshape

    <out:
      pha freq=1.5 phase=0.25
      lut mywaveshape

phase table lookup (single-cycle bezier wave)

### Inputs

| `freq` / `f` | range is 0.25..4 (1.0=middle C in fixed frequency mode) | def=1.0
| `phase` / `ph` | range is 0..1 (start phase) | def=0.0
| `vsync` / `vs` | range is 0.01..4 (virtual oscillator sync) | def=1.0 (off)
const | `cycle` / `cy` | range is 0/1 (0=disable wavetable cycle reset) | def=1 (on)
const | `reset` / `rs` | range is 0/1 (0=disable plugin voice note on reset | def=1 (on)
const | `fixed` / `fx` | range is 0..1 (1=enable fixed frequency mode) | def=0 (follow kbd)
| `phasemod` / `pm` | range is 0..1 (post vsync phase modulation) | def=0.0 (off)
| `pd` | phase distortion insert (subtree). input is phase (0..1), output is new phase (0..1). only supported in float/hi-fi mode. |

## 10.62 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).

### Examples

    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.7   0 1
    param p_keyfollow  1.0   0 1
    param p_octave_off 0.0  -4 4

    <out:
      saw
      svf sr=0
        freq:
          $p_cutoff
          p2s kbd=$p_keyfollow off=$p_octave_off

filter cutoff keyboard tracking example plugin

### Inputs

| `exp` / `e` | range is 0..15 | def=7.0
const | `scl` / `s` | range is 0..1 (0=>scl=(pow(2,exp)-1)) | def=0.0
| `kbd` / `k` | range is -16..16 (keyboard follow amount) | def=0.0
| `off` / `o` | range is -4..4 (octave offset) | def=0.0

| when `kbd` is connected (in plugin mode), this factors in the current samplerate and oversample factor. caution when passing the output to another module which also does this !

| only supported in float-mode (fall back to float in int mode)

| for keyboard tracking purposes, the kbd module is usually the preferred solution

## 10.63 pow

Return previous output raised to the power of 2 or 3 (fast-path), or an arbitrary exponent (float-mode only).

### Examples

    <out:
      sin
      pow 3

pow fastpath

    <out:
      pow
        base:
          2.71828182845904523 # e
        exp:
          sin

generic pow with base input connected

### Inputs

| `exp` / `e` | range is [2, 3] (int) or any float value. when not connected (and `base` is), use previous output as exponent | def=2.0
| `base` / `b` | range is any float val. when not connected, use previous output as base. | def=2.0

| useful for bending rmp envelopes into non-linear shapes

| constant exponents 2 and 3 behave differently than arbitrary (or dynamic) exponents, i.e. -2²=+4 while e.g. -2^4=-16

| arbitrary or dynamic exponents are only supported in float-mode (fall back to float in int mode)

## 10.64 pre

This is a dummy (no-op) module that simply returns the previous output (no extra code generated).

| `pre` must be the first module in a template or module input that reads the previous output via an operator and a template call, e.g.
    def myrev
      + ~mydly
not working ..must be written as:
    def myrev
      pre
      + ~mydly
working This is currently required due to a parser-issue which may be proper-fixed later.

see also: Templates / pre

## 10.65 pul

Generate pulse wave

### Example

    <out:
      pul freq=1 phase=0 width=0.5 vsync=2.5

vsynced pulse (rectangle) oscillator

### Inputs

| `freq` / `f` | range is 0.25..4 (1.0=middle C in fixed frequency mode) | def=1.0
| `phase` / `ph` | range is 0..1 (start phase) | def=0.0
| `width` / `w` | range is 0..1 (length of duty cycle) | def=0.5
| `vsync` / `vs` | range is 0.01..4 (virtual oscillator sync) | def=1.0 (off)
const | `cycle` / `cy` | range is 0/1 (0=disable wavetable cycle reset) | def=1 (on)
const | `reset` / `rs` | range is 0/1 (0=disable plugin voice note on reset) | def=1 (on)
const | `fixed` / `fx` | range is 0/1 (1=enable fixed frequency mode) | def=0 (follow kbd)
| `window` / `win` | range is 0..8 (0=`off`/`none`(windowed vsync off), 1=`sin`(sine window), 2=`hs`(half-sine window), 3=`tri`(triangle window), 4=`hse1`(hse1_36), 5=`hse2`, 6=`hse3`, 7=`hse4`, 8=`hse5`) (must be const OR return amp factor (see below)) |
| `phasemod` / `pm` | range is 0..1 (post vsync phase modulation) | def=0.0 (off)
| `pd` | phase distortion insert (subtree). input is phase (0..1), output is new phase (0..1). only supported in float/hi-fi mode. |

| windowed vsync is only supported in float-mode (ignored in integer mode)

| when `window` input is not constant, it is treated as an insert which receives the window phase and returns the window amplification factor (0..1). I.e. this can be used to implement custom window functions.

## 10.66 qua

Quantize float value to n fractional steps per integer.

### Example

    <out:
      sin
      # reduce 32bit -1..1 float to ~4.32 bits
      #   (.., -0.2, -0.1, 0.0, 0.1, 0.2, ..)
      qua num=10

quantize

### Inputs

| `num` / `n` | range is 1..n | def=32

| useful for controllers, e.g. FM ratios

| when num=1, simply discard fractional bits (cast to integer)

| only supported in float-mode (fall back to float in int mode) (except when num=1)

## 10.67 rbl

Read sample from (cross-zone) voicebus left channel.

### Examples

    id vb_read_prev
    param lvl 1 0 1
    <out:
       sin
         phase:
           rbl 0
           * $lvl

phase-modulate sine wave by previous zone output

    id vb_read_1
    param lvl 1 0 1
    <out:
       sin
         phase:
           rbl 1
           * $lvl

phase-modulate sine wave by voice bus 1

    id vb_read_nri
    # (note) useful range is 0..32%
    param bus 0 0  100
    param lvl 1 0    1
    <out:
       sin
         phase:
           rbl nri=$bus
           * $lvl

phase-modulate sine wave by previous zone output (integer bus nr)

    id vb_read_nrf
    # (note) useful range is 0..32%
    param bus 0 0 1
    param lvl 1 0 1
    <out:
       sin
         phase:
           rbl nrf=$bus
           * $lvl

phase-modulate sine wave by previous zone output (float bus nr)

### Inputs

| `nri` | range is 0..32 (bus 1..32, 0=previous zone nr) | def=1
| `nrf` | range is 0..0.32 (bus 1..32, 0=previous zone nr) | def=0.1

| only supported in STFX plugin mode (returns 0 otherwise)

## 10.68 rbr

Read sample from (cross-zone) voicebus right channel.
see rbl

## 10.69 rcp

Calculate reciprocal (`1/x`) of previous output.

### Example

    arg p_sin_scl 1.5 1 4

    <out:
      sin
      * $p_sin_scl
      rcp
      clp
      * tri ph=0.25 ## fade-out infinity
        abs
      * $p_sin_scl

test rcp 1 example patch

## 10.70 rdl

Read left channel input sample.

| available only in plugin mode.

## 10.71 rdr

Read right channel input sample.

| available only in plugin mode.

## 10.72 red

Detect logic signal rising edge and evaluate sub-tree.

### Example

    id edge_detect

    var v_out
    var v_dcy

    <out:
      pul
      red
        set v_dcy 0.999
        set v_out 1
      fed
        set v_dcy 0.95
      $v_out
      *sr $v_dcy
      sto v_out
      fma 2 -1

rising / falling edge detect osc

| this module has no inputs

see also: fed

## 10.73 rep

Loop / unroll statement sequence.

| The number of iterations must be constant.

### Example

    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

unrolled loop

### Inputs

const | `num` / `n` | range is 0..1023 | def=1

## 10.74 rev

reserved

## 10.75 rmp

Generate linear ramp

### Examples

    <out:
      rmp millisec=8 start=0 end=1 cycle=1

linear 8ms ramp

    <out:
      rmp 0 0 1

linear ramp from 0..1 that spans entire sample

### Inputs

const | `millisec` / `ms` | range is 0..4000. 0 auto-adjusts the duration to match the target zone sample length | def=200
const | `start` / `s` | range is 0..15 (start value) | def=1.0
const | `end` / `e` | range is 0..15 (end value) | def=0.0
const | `cycle` / `cy` | determines whether the ramp position is reset at each new wavetable cycle (0 or 1) | def=1 (on)

## 10.76 rms

Calc root mean square (RMS).

### Example

    <out:
      sin
      rms
      trc

rms

### Inputs

const | `size` / `s` | range is 1..4096 (history size) | def=16
const | `win` / `w` | 1=enable window mode | def=0(off)
const | `exp` / `e` | window exponent (⇐1 disable) | def=0

## 10.77 saw

Generate sawtooth wave

### Example

    <out:
      saw

sawtooth oscillator

    oversample 2
    <out:
      saw freq=2 phase=0 vsync=2.5

vsynced sawtooth oscillator (second harmonic)

### Inputs

| `freq` / `f` | range is 0.25..4 (1.0=middle C in fixed frequency mode) | def=1.0
| `phase` / `ph` | range is 0..1 (start phase) | def=0.0
| `vsync` / `vs` | range is 0.01..4 (virtual oscillator sync) | def=0.0 (off)
| `cycle` / `cy` | range is 0/1 (0=disable wavetable cycle reset) | def=1 (on)
const | `reset` / `rs` | range is 0/1 (0=disable plugin voice note on reset) | def=1 (on)
const | `fixed` / `fx` | range is 0/1 (1=enable fixed frequency mode) | def=0 (follow kbd)
| `window` / `win` | range is 0..8 (0=`off`/`none`(windowed vsync off), 1=`sin`(sine window), 2=`hs`(half-sine window), 3=`tri`(triangle window), 4=`hse1`(hse1_36), 5=`hse2`, 6=`hse3`, 7=`hse4`, 8=`hse5`) (must be const OR return amp factor (see below)) |
| `phasemod` / `pm` | range is 0..1 (post vsync phase modulation) | def=0.0 (off)
| `pd` | phase distortion insert (subtree). input is phase (0..1), output is new phase (0..1). only supported in float/hi-fi mode. |

| windowed vsync is only supported in float-mode (ignored in integer mode)

| when `window` input is not constant, it is treated as an insert which receives the window phase and returns the window amplification factor (0..1). I.e. this can be used to implement custom window functions.

## 10.78 sat

Saturate previous output.
Processes previous output value 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
      sat  # same as tanh

tanh saturation

    arg drive 0 0 2

    lut tanh shared 4096

    <lut_tanh:
      # 0..1 => -100..+100
      fma 200 -100
      sat  # same as 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 saturation with lookup-table
See tanh (deprecated name in earlier releases)

| useful for saturation-like non-linear distortion (converges to -1..1)

| only supported in float-mode (fall back to float in int mode)

## 10.79 set

Set variable
If the `value`/`v` input is not connected, set variable to previous output value.

### Examples

    var f
    <out:
      1.23
      set f

set f to 1.23

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

increment f 16 times

### Inputs

| `value` / `v` | when connected, set variable to input value instead of previous output value

## 10.80 sin

Generate sine wave

### Examples

    <out:
      sin

sine oscillator

    <out:
      sin freq=1 phase=0 vsync=2.5 window=sin

windowed / vsynced sine oscillator

    <out:
      sin
        pd:
          tri freq=1

sine osc phase distortion

### Inputs

| `freq` / `f` | range is 0.25..4 (1.0=middle C in fixed frequency mode) | def=1.0
| `phase` / `ph` | range is 0..1 (start phase) | def=0.0
| `vsync` / `vs` | range is 0.01..4 (virtual oscillator sync) | def=1.0 (off)
const | `cycle` / `cy` | range is 0/1 (0=disable wavetable cycle reset) | def=1 (on)
const | `reset` / `rs` | range is 0/1 (0=disable plugin voice note on reset) | def=1 (on)
const | `fixed` / `fx` | range is 0/1 (1=enable fixed frequency mode) | def=0 (follow kbd)
| `window` / `win` | range is 0..8 (0=`off`/`none`(windowed vsync off), 1=`sin`(sine window), 2=`hs`(half-sine window), 3=`tri`(triangle window), 4=`hse1`(hse1_36), 5=`hse2`, 6=`hse3`, 7=`hse4`, 8=`hse5`) (must be const OR return amp factor (see below)) |
| `phasemod`/`pm` | range is 0..1 (post vsync phase modulation) | def=0.0 (off)
| `pd` | phase distortion insert (subtree). input is phase (0..1), output is new phase (0..1). only supported in float/hi-fi mode. |

| windowed vsync is only supported in float-mode (ignored in integer mode)

| when `window` input is not constant, it is treated as an insert which receives the window phase and returns the window amplification factor (0..1). I.e. this can be used to implement custom window functions.

| all oscillators can be used as wavefolders by setting their speed to 0, then modulating their phase input

## 10.81 slf

Read past sample output ("self" feedback)

### Example

    <out:
      pul
      + slf rate=0.98
      clp

mix past output

### Inputs

| `rate` / `r` | range is 0..1 | def=0.985

## 10.82 slw

Slew from `a` to `b` with configurable up / down amounts.
The module behaves differently depending on which inputs are connected:

- when `a` is connected and `b` is not, the previous output is used as the destination
- when `b` is connected and `a` is not, the previous output is used as the source
- when neither `a` and `b` are connected, the previous output is used as the destination and an implicit (source) state variable is allocated
  (initially 0, then continously updated to the `slw` output value)

### Example

    arg amp    0.7
    arg slw_up 0.15
    arg slw_dn 0.11

    <out:
      pul
      slw up=$slw_up down=$slw_dn
      * rmp 0 1
      * $amp
      clp

cycle int slw example patch

### Inputs

| `up` / `u` | (b > a) slew amount (1=instant .. 0=never) | def=0.3
| `down` / `d` | (b < a) slew amount (1=instant .. 0=never) | def=0.1
| `a` | source value (optional) |
| `b` | destination value (optional) |
const | `sr` | 1=(over)samplerate-independent slew amounts. 0=fixed slew amounts. (float/hi-fi mode only) | def=1

## 10.83 spd

Returns current note speed (including oversampling factor).
Can be used to accumulate normalized (0..1) phases.

### Example

    id spd_test

    var v_phase

    <out:
      $v_phase
      + spd
      sto v_phase
      # wrap-around (keep only the fractional bits)
      frc
      # 0..1 => -1..1
      fma 2 -1

a simple saw oscillator (pitch follows keyboard)

| in sample calculator (instead of voice plugin) mode, this simply returns `(1.0 / oversampling_factor)`

## 10.84 spr

Apply dampened spring oscillation to previous output value.

### Example

    id spring_time_1
    <out:
      pul
      * 0.5
      spr spd=0.1 dcy=0.8
      clp

dampened spring example plugin

### Inputs

| `spd` / `s` | speed (0..1) | def=0.1
| `dcy` / `d` | decay (0..1) | def=0.8
| `sr` | 1=(over-)samplerate independency (float/hi-fi mode only) | def=1 (on)

## 10.85 ssl

Sine slice oscillator.
This is a rather experimental oscillator that mainly generates "chiptune"-ish sounds.
It 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) via the slice sequence `seq` (first parameter).
The `zeromask` input determines which slices are silenced while `modmask` selects which ones are affected by the modulation `mode` and modulation amount `mod`.
`seqmod` is multiplied by 16384, converted to a 16bit integer, replicated to 32 bit and added to the base sequence (where each nibble (4 bits) represents a slice, e.g. `$01234567`). In other words: it (dynamically) shifts the sequence's slice selection with wildy varying results.

### Example

    <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).
The sequence is repeated if it has less than 8 entries.

### Inputs

| `freq` / `f` | range is 0.25..4 (1.0=middle C in fixed frequency mode) | def=1.0
| `phase` / `ph` | range is 0..1 | def=0.0
| `mod` | range is -1..1 (slice modulation) | def=0.0
const | `seq` / `s` | range is 0..1 (0=base slice sequence, 00000001..7777777) |
| `modmask` / `mm` | range is 0..1 (0=no slices, 1=all slices) | def=1.0 (all)
| `zeromask` / `zm` | range is 0..1 (0=no slices cleared, 1=all slices cleared) | def=0.0 (none)
const | `mode` / `m` | range is 0..5

Mode | Description
--- | ---
0 point | ignore fractional (mod) bits, simply recombine waveforms (fastest)
1 phase | fractional (mod) bits determine source region start offset shift ("phase" mod 1/8 sin)
2 lerp | fractional (mod) bits are used to lerp sine source region to cosine wave
3 shift | fractional (mod) bits are used to shift source region (dc offset + reflect/fold)
4 ang | fractional (mod) bits determine source waveform start offset shift ("phase" +- 2PI)
5 amp | fractional (mod) bits are used to amplify source region

SSL mode settings
def=0 (point)
| `seqmod` / `sm` | range is 0..3.9999 (per-sample sequence modulation) | def=0 (no modulation)

## 10.86 sta

Store variable in LUT at normalized index (0..1 ⇒ 0..lut_size) 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

    curve 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

| 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

## 10.87 sto

Store previous output in variable

### Examples

    var t

    <out:
      sin
      sto t
      * $t
      * $t

sin*sin oscillator
The variable is initialized with 0 but retains its content over multiple sample frame iterations.

    var sin_val

    <out:
      $sin_val
      + sin
      sto sin_val
      clp

accumulate sine

## 10.88 svf

Process previous output with two-pole state-variable filter.

### Example

    <out:
      saw
      svf freq=0.4 res=0.9 mode=lpf
      svf 0.4 0.5 0

resonant four-pole (-24dB/oct) low-pass filter

### Inputs

| `freq` / `f` | range is 0..1 | def=0.5
| `res` / `r` | range is 0(full res)..1(no res) | def=1.0 (no resonance)
const | `mode` / `m` | 0=`lpf`, 1=`hpf`, 2=`bpf`, 3=`notch` | def=0=lpf
| `postlpf` / `pl` | post-LPF / pre-HPF insert |
| `posthpf` / `ph` | post-HPF / pre-BPF insert |
| `postbpf` / `pb` | post-BPF / pre-LPF insert |
const | `sr` | 1=enable (over-)samplerate independent frequency (voice-plugins) | def=1 (on)

## 10.89 tan

Calculate the tangens of the previous output value.

| tan(0.125*2PI) = tan(0.785398163398) = 1.0, i.e. scale normalized -1..1 input by 0.78539 to create a tan waveshaper (see `test tan 1` example patch)

| forces fallback to hifi / float mode

## 10.90 tanh

| this is an alias for sat

## 10.91 tmp

This is the (deprecated) long-form of the `~` template module instantiation command.
see templates

## 10.92 trc

Debug-trace last output value (print to console).

### Example

    lut mylut shared 16

    <lut_mylut:
      # print current $x
      trc

    <out:
      sin

debug-print normalized LUT index

### Inputs

const | `c` | 1=trace value changes only, 0=trace all values | def=1 (on)
const | `z` | 1=trace 0 values, 0=skip | def=1 (on)
const | `e` | >1=trace every nth value, 0=trace all | def=0 (all)

## 10.93 tri

Generate triangle wave

### Examples

    <out:
      tri

triangle oscillator

    <out:
      tri freq=1 phase=0 vsync=2.5

vsynced triangle oscillator

### Inputs

| `freq` / `f` | range is 0.25..4 (1.0=middle C in fixed frequency mode) | def=1.0
| `phase` / `ph` | range is 0..1 (start phase) | def=0.0
| `vsync` / `vs` | range is 0.01..4 (virtual oscillator sync) | def=1.0 (off)
const | `cycle` / `cy` | range is 0/1 (0=disable wavetable cycle reset) | def=1 (on)
const | `reset` / `rs` | range is 0/1 (0=disable plugin voice note on reset) | def=1 (on)
const | `fixed` / `fx` | range is 0/1 (1=enable fixed frequency mode) | def=0 (follow kbd)
| `window` / `win` | range is 0..8 (0=`off`/`none`(windowed vsync off), 1=`sin`(sine window), 2=`hs`(half-sine window), 3=`tri`(triangle window), 4=`hse1`(hse1_36), 5=`hse2`, 6=`hse3`, 7=`hse4`, 8=`hse5`) (must be const OR return amp factor (see below)) |
| `phasemod` / `pm` | range is 0..1 (post vsync phase modulation) | def=0.0 (off)
| `pd` | phase distortion insert (subtree). input is phase (0..1), output is new phase (0..1). only supported in float/hi-fi mode. |

| windowed vsync is only supported in float-mode (ignored in integer mode)

| when `window` input is not constant, it is treated as an insert which receives the window phase and returns the window amplification factor (0..1). I.e. this can be used to implement custom window functions.

## 10.94 tsq

Generate trigger sequence with configurable duty cycle duration.

### Example

    var v_clk
    <out:
      clk div=1/4
      sto v_clk
      sin
      * tsq x.x..x...x....x.....x......x.......x cg=$v_clk
        adsr

envelope restarted by trigger pattern

    var v_clk
    <out:
      clk div=1/4
      sto v_clk
      sin
      * tsq x.x..x...x....x.....x......x.......x cg=$v_clk
        adsr

envelope restarted by trigger pattern

    id arrayparam_seq_test_1

    param p_seq

    curve 0 steps param=p_seq size=16 var=4

    var v_env_lvl

    <out:
      clk div=1/4
      # (note) 'x' is a dummy pattern
      tsq x len=16 stepth=0.5
        step:
          # input is step index (0..15)
          * 1/16
          lut steps
        ontrig:
          set v_env_lvl 1

      tri
      * $v_env_lvl
        *sr 0.9993
        sto v_env_lvl
        slw 0.05 0.05

read trigger pattern from array param

### Inputs

| `dur` / `d` | range is 0..f (trigger pulse duty cycle duration in milliseconds) | def=5
| `restart` / `rs` | range is 0/1 (restart sequence on rising edge) |
| `shift` / `sh` | range is 0..1 (shift play position by normalized position (0..1 ⇒ 0..pat_len)) | def=0
| `gate` / `g` | range is 0/1 (play current step on rising edge) |
| `clk` / `c` | range is 0/1 (advance to next step on rising edge) |
| `cg` | range is 0/1 (clock+gate, play current step and advance to next step on rising edge) |
const | `loop` / `lp` | range is 0/1 (1=repeat pattern, 0=oneshot/stop at end of pattern) | def=1 (on)
const | `len` / `l` | sequence length (1..32(float), 1..15(integer/lo-fi)). default=0=use xx.xx pattern length. | def=0 (use seq len)
| `step` / `s` | when connected, eval subtree when value is needed for step 'x'. input is step index (0..n), output is step value. |
const | `stepth` / `sth` | threshold for `step` output (value=1 when output is `> stepth`, 0 otherwise). | def=0.0
| `ontrig` / `ot` | evaluated each time a step is triggered/gated. must return new pulse level (0..1, 0 discards pulse) |
| `onend` / `oe` | evaluated when end of pattern (or first step in loop mode) is reached during clock tick |

| when neither `clk`, `gate` or `cg` are connected, the clock+gate input is read from the previous output value

## 10.95 vel

Returns the current voice velocity (0..1).

### Examples

    id vel_test_1
    <out:
      sin
      * vel  # linear velocity curve

velocity example 1
`vel` can also be used in short form value assignments to (dynamic) inputs:

    id vel_test_2
    <out:
      sin
      * ipl a=0.1 b=0.9 t=vel win=smoothstep  # non-linear, biased velocity curve

velocity example 2

| only available in voice plugin mode. in sample synth mode, this simply returns 1.0.

## 10.96 vpl

Process previous output via stfx voice plugin.

### Examples

    <out:
      sin 5.0
      vpl "bsp ws quintic" "Dry / Wet"=0.5 Drive=0.39

single line voice plugin invocation

    arg drive 0.39 min=0 max=1

    <out:
      sin 5.0
      vpl "bsp ws quintic" "Dry / Wet"=0.5 "Drive"=$drive

single line voice plugin invocation 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

multi line voice plugin invocation with arg slider
In Lo-Fi (integer) mode, the input/output values are temporarily converted to / from float.

| See Eureka sampler "FX" tab for a list of available plugins.

| Plugin setups can be exported to single- or multi-line Cycle scripts (clipboard) via the Eureka sampler "FX" tab context menu

| `vpl` modules cannot be exported to "C" source

## 10.97 vsq

Generate value sequence.

| please use `step`/`s` input to return sequence value for given sequence step index (initial value when evaluating `step` input)

See tsq module

## 10.98 vst

Declare variable and store previous output.

| the variable will be declared within the scope of the current lane

### Example

    <out:
      tri phase=0.25
      vst tri
      + $tri
        hld 0.7
      * 0.5

sample&hold triangle osc mix

## 10.99 wbl

Write sample to (cross-zone) voicebus left channel.

### Examples

    id vb_out_test
    param lvl 1 0 1
    <out:
      sin
      wbl 1

write sine output to voice bus 1

### Inputs

| `nri` | range is 1..32 (bus 1..32) | def=1
| `nrf` | range is 0.1..0.32 (bus 1..32) | def=0.1

| only supported in STFX plugin mode

## 10.100 wbr

Write sample to (cross-zone) voicebus right channel.
see wbl

## 10.101 wrl

Write left channel output sample.

### Inputs

| `value` / `v` | when connected, read sample from input instead of previous output value
| `amp` / `a` | when connected, amplify value (e.g. by pan factor)

| available only in plugin mode.

| implicitely enables STEREO plugin mode

see also: pal

## 10.102 wrp

Wrap previous output value at ceiling.

### Examples

    <out:
      sin
      * 2.0
      wrp

wrap into -1..1 range

    <out:
      sin
      * 2.0
      wrp ceil=0.8

wrap into -0.8..0.8 range

| `ceil` sets min=-ceil, max=+ceil

    <out:
      sin
      * 2.0
      wrp min=-0.5 max=0.9

wrap into -0.5..0.9 range

| `min` / `max` override default ceil

    oversample 4

    arg amp_pre 0.99 0 8
    arg wrp_min -1 -1 1
    arg wrp_max  1 -1 1
    arg p_amp 0.7 0 8

    <out:
      sin
      * $amp_pre
      wrp min=$wrp_min max=$wrp_max
      * rmp 0 1
      * $p_amp
      clp

test wrp 1 example

### Inputs

| `ceil` / `c` | sets min=-val, max=+val | def=1.0
| `min` / `m` | minimum value (override -ceil) |
| `max` / `x` | maximum value (override ceil) |
const | `hq` | 1=enable high-quality (float) mode (arbitary number of wrap-arounds). | def=1

see also: fld

## 10.103 wrr

Write right channel output sample.

### Inputs

| `value` / `v` | when connected, read sample from input instead of previous output value
| `amp` / `a` | when connected, amplify value (e.g. by pan factor)

| available only in plugin mode.

| implicitely enables STEREO plugin mode

see also: par

## 10.104 xfd

Crossfade between previous output and input `b`. At the center position, the two inputs are summed.

### Example

    arg mod_waveform 0 -1 1
    <out:
      tri
      xfd amount=$mod_waveform
        b:
          saw

crossfade between triangle and saw

### Inputs

| `amount` / `amt` | crossfade-to-b amount (-1=previous input .. 0=previous input and b .. 1=b)

See also: ipl

## 10.105 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:
      zlp 0 rate=0.3 phase=0.5
      + zlp 1 0.9
      clp

mix looped sample zones

### Inputs

| `rate` / `r` | range is 0..15 | def=1.0
| `phase` / `ph` | range is 0..1 | def=0.0
const | `div` / `d` | range is 0..256 (set repeatlen to zonelen / div) |
| `sub` / `s` | range is 0..256 (shifts start offset by sub*repeatplen, e.g. for selecting sub-wave from wavetable. requires `div` to be connected) |
const | `lin` / `l` | enables bilinear filtering | def=0 (off)

| sample len is determined by last loop length of the specified zone

| sample len should be a power of two (read offset is ANDed with (sampleLen-1))

## 10.106 zon

Read sample frame from other zone. Can be used to mix samples or create chords.

### Examples

    zone 0 otherzone
    zone 1 othersmp.otherzone

    <out:
      zon 0 rate=0.3
      + zon 1 0.9
      clp

Mix two zones at different rates

    zone 0 loopzone

    <out:
      # 'and=255' causes zone frames 0..255 to be repeated
      zon 0 rate=0.5 lin=1 and=255

Upsampled loop

    zone 0 singlenote_g3

    <out:
      zon 0
      + zon 0 d#3/g-3
      + zon 0 c-3/g-3
      * 0.5
      clp

Minor chord

### Inputs

const | `rate` / `r` | range is 0..15 | def=1.0
const | `lin` / `l` | enables bilinear filtering | def=0 (nearest)
const | `and` / `a` | enables power-of-two wrap-around (0=off, 1..65535 otherwise) | def=0 (off)
| `andf` / `af` | logic-and read position with float converted to fixpoint | def=0.0 (off)
| `restart` / `rs` | rising edge restarts sample |
| `pos` / `p` | normalized restart position (0..1) | def=0.0
| `freq` / `f` | range is 0..15 (effectiverate = `rate * currentnote_speed * freq`) |
const | `clamp` / `c` | clamp source offset (always in float mode, def=1 in int mode) | def=1 (on)

| 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

See also: zlp (supports arbitrary loops)

## 10.107 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

Kick / snare drum-sample sequence

### Inputs

const | `note` | range is 1..256 (step note duration) | def=16
const | `bpm` | range is 0..1000 (<10 use project bpm) | def=0.0
const | `swing` | range is -0.5..0.5 | def=0.0
const | `rate` | range is 0.125..8 | def=1.0
const | `lin` | range is 0..1 (>=0.5 enable bilinear filtering) | def=0/nearest
const | `div` | range is 0..256 (divide source zone into n regions) |
| `off` | range is 0..1 (select sub-divided source region, e.g. `$wt_x`) |

| 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.

| `rate` can (like any constant value) also be expressed as a note ratio, e.g. `c-3/d#3`

# 11 Curve Lookup Tables (LUTs)

Cycle supports up to 16 cubic-bezier curve tables which can be used as amp or filter envelopes, single-cycle waveforms, or sample waveshapers.
Curve tables are internally converted (sampled) to short, power-of-two sized arrays. The array sizes are 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.95   # millisec=0 (auto-fit sample size) start=0 end=0.95
        lut

Using a curve LUT as an amp envelope (no interpolation)

    curve 0 amp

    <out:
      sin
      * rmp 0 0 1   # millisec=0 (auto-fit sample size) start=0 end=1
        lut amp lin=1 clamp=1

Amp envelope with linear interpolation (clamp to end of curve)

## 11.1 Auto enumeration

The curve index (first arg after `curve` keyword) may be skipped or set to -1. This will cause the curve to be auto-assigned to slot 0..15.

| The curve index allocator attempts to retain the previous curve assignment based on the curve's alias / id.

| When removing an auto-enumerated curve declaration from a script (by deleting it or commenting it out), the curve data will most likely be lost since the curve index optimizer will reuse the curve when remapping other auto-enumerated curves

    arg p_amp     0.7 0 8
    arg p_feg_amt 0.2 0 4

    curve freq
    curve amp

    <out:
      sin
        freq:
          rmp 0 0 1
          lut freq
          pow 2
          fma $p_feg_amt 1
      * rmp 0 0 1
        lut amp
      * $p_amp
      clp

curve auto enum test 1

# 12 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 are 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

| lookup tables can greatly improve the rendering performance by precalculating recurring values

| the default table size is 64

| the default table mode is `voice`

| LUT init sections must be named `lut_<id>` (e.g. `<lut_pow:`)

| if no init section has been declared for a LUT used by a "lut" module, compilation will fail

| the shared LUTs are calculated after the `<init:` section

| the per-voice (or per-wavetable cycle) LUTs are updated after the `<prepare:` / `<wt_init:` section

## 12.1 Inverse Lookup Tables

See sta module documentation for examples.

# 13 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)

| the maximum number of (auto-generated) permutations is limited to 16384

| alternatively to macros, the `<init:` and `<prepare:` sections may also contain arbitrarily complex, procedural pre-calculations

# 14 Maps

Maps are used to remap values using non-contiguous tables.
Applications include microtuning scales, drum maps or envelopes.
A map is divided into n regions:

    id map_test_4

    map mymap
           freq vol cutoff   # map field names
      0.0  1    0.2   0.8    ## map region 1 starts at 0.0
      4.0  1.5  0.5   0.2    ## map region 2 starts at 4.0
      5.0  2.5  1.0   0.1    ## map region 3 starts at 5.0

    <out:
      note
      & 7
      vst mapkey  # store (note modulo 8) in var 'mapkey'
      saw
        freq:
          $mapkey
          map mymap.freq def=1 mode=lin  # remap previous output value using linear interpolation
      svf
        freq:
          $mapkey
          map mymap.cutoff def=0.5 mode=up  # remap previous output value (round up to nearest map key)
      * $mapkey
        map mymap.vol def=0 mode=down  # remap previous output value (round down to nearest map key)

map example
See map module.

# 15 Templates

Templates are parametrizable code snippets. Each template supports an arbitrary number of variables which will be replaced by either their default values or the values assigned in the template instantiation.
The special var `%%` is replaced by a unique template instantiation id (used when generating global variables, lanes, or parameter / modulation names).

| variables declared within a template are automatically prefixed with a unique id during instatiation

Template applications:

- act as a namespace for variables (when instantiated in- or outside of a lane)
- generate args, params, mods (when instantiated outside of a lane or when using the global section)
- generate audio layers (when instantiated outside of a lane, see Audio lanes)
- insert parametrizable code into a lane (when instantiated inside a lane or when inserting a lane of an explicitily named global template instance (see init, prepare, calc lanes))

## 15.1 Example

A simple, dual oscillator additive synthesis patch plugin.
Each macro oscillator renders the fundamental plus three harmonics with adjustable level decay.
The oscillators levels and harmonic decays can be changed in the sample voiceplugin parameter editor and can also be modulated in the sample's modmatrix.

    id test_template_2

    modparam lvl1  0.75
    modparam hdcy1 0.25
    modparam lvl2  0.25
    modparam hdcy2 0.125

    def osc_sin_hdcy_1 freq=1 hdcy=0.25
      vst myosc_harm 1
      sin
        freq:
          %freq%
      * $myosc_harm
      mul myosc_harm %hdcy%
      + sin
          freq:
            %freq%
            + 1
        * $myosc_harm
        mul myosc_harm %hdcy%
      + sin
          freq:
            %freq%
            + 2
        * $myosc_harm
        mul myosc_harm %hdcy%
      + sin
          freq:
            %freq%
            + 3
        * $myosc_harm
        mul myosc_harm %hdcy%

    <out:
      ~osc_sin_hdcy_1 1 $m_hdcy1
      * $m_lvl1
      + ~osc_sin_hdcy_1 freq=2.5 hdcy=$m_hdcy2
        * $m_lvl2

additive synthesis template example

## 15.2 Instance id

Template instances are usually auto-enumerated (1..n) but the instance name can be overridden using the `id` attribute:

    def myclass freq=1
      var freq = %freq%
      var out
      arg a_%%_amp  0.125  # a_mc_amp for id=mc
      arg a_%id%_fb 0      # a_mc_fb for id=mc

    ~myclass id=mc

    <out:
      mc.out
        * $a_mc_fb
      + tri freq=mc.freq
        * $a_mc_amp
          * 8
      sto mc.out
      * rmp 0 1
      clp

explicit instance id ("template instance id" example patch)

## 15.3 Audio lanes

Template declarations may also include audio lanes.

| all audio lanes are summed to generate the final output, see layers

    id test_template_4

    def myosc f1=1 f2=1.5 f3=2 dcy=0.5
      modparam myosc%%_lvl 1

      <out_myosc%%:
        sin freq=%f1%
        vst lvl %dcy%
        + sin freq=%f2%
          * $lvl
        mul lvl %dcy%
        + sin freq=%f3%
          * $lvl
        * $m_myosc%%_lvl
        end # (note) end lane so the second ~myosc does not end up in the first myosc lane

    ~myosc 1 1.5 2 dcy=0.5
    ~myosc 1.015 1.5025 2.015 dcy=0.25

lanes and instance id example

### 15.3.1 init, prepare, calc lanes

The `init`, `prepare` and `calc` lanes are not treated as audio lanes but can rather be inserted at any point in the patch.

    arg p_amp 0.5

    def myclass
      arg a_%%_freq 0 -1 1  # a_mc_freq for id=mc1
      arg a_%%_amp 1        # a_mc_amp for id=mc1
      var v_rate
      init:
        var myinitvar   # test vardecl (internally prefixed by templ class+instance id+lane id)
        42
        trc             # debug: print 42 to see if init was called
      prepare:
        $a_%%_freq
        bts 4 4         # -1..1 => 1/4..*4
        trc             # debug: print rate
        sto v_rate
      calc:
        tri freq=$v_rate
        * $a_%%_amp

    ~myclass id=mc1
    ~myclass id=mc2

    <init:      # note-on
      init mc1  # insert init lanes
      init mc2

    <prepare:      # prepare next block (update internal vars)
      prepare mc1  # insert prepare lanes
      prepare mc2

    <out:          # calculate output
      calc mc1     # insert calc lanes
      + calc mc2
      * rmp 0 1
      * $p_amp
      clp

init, prepare, calc template lanes ("template instance id 2" example patch)

## 15.4 global

Lines inside the `global:` section are placed outside the current lane.

    arg p_amp 0.7 0 8

    def sineosc
      global:
        arg p_%%_freq 1 1 4
        arg p_%%_amp 0.7

      sin freq=$p_%%_freq
      * $p_%%_amp

    <out:
      ~sineosc
      * rmp 0 1
      * $p_amp
      clp

test_template_6

## 15.5 luts

Templates may also contain procedural look-up-tables (LUTs). See `test_template_5` example patch (and `osc_sin_tri_1_lut.cy` machine include file) for a LUT based oscillator template.

    id test_template_5

    inc osc_sin_tri_1_lut

    <out:
      ~osc_sin_tri_1_lut
      clp

lut based template oscillator plugin

## 15.6 pre

If the first module in an input or template declaration is a template call and an operator other than the default (`=` / `A`) is used, the first module must be pre.
Example:

    def tmp_mul
      * 0.1

    def tmp_abs
      abs

    def mytmp
      pre
      + ~tmp_mul
        #trc
        ~tmp_abs

    <out:
      sin
      * 0.5
      ~mytmp
      clp

pre
This is currently required due to a parser-issue which may be proper-fixed later.

## 15.7 structs

Templates can also be used as variable containers (structures):

    # example for a struct-like template
    arg p_amp 0.7 0 8

    def mystruct
      var x
      var y

      <init:  ## (note) init is evaluated before prepare
        set x 1.0
        set y 0.2

    ~mystruct id=s
    ~mystruct id=t

    <prepare:
      s.x          ## print default x=1.0
      trc
      set t.y 0.7  ## override default y=0.3

    <out:
      sin
      * s.x  ## => *1.0
      * t.y  ## => *0.7
      * rmp 0 1
      * $p_amp
      clp

test template struct 1

# 16 Includes

Re-usable code snippets can be placed in `STConfig.cycle_machine_inc_path` (`eureka/machine_inc/` by default) and then inserted into patches:

    id test_template_3

    modparam m_lvl1   1
    modparam m_lvl2   0
    modparam m_trimix 0

    # the (global) include file defines the template "osc_sin_tri_1"
    inc osc_sin_tri_1

    <out:
      ~osc_sin_tri_1 freq=1
      * $m_lvl1
      + ~osc_sin_tri_1 1.5
        * $m_lvl2
      ipl t=$m_trimix
        b:
          tri
          * $m_lvl1
          + tri freq=1.5
            * $m_lvl2

include file example

| in the Cycle machine source editor, press `lctrl-g` to edit the include file under the cursor, or `lctrl-h` to copy its native path name (for opening it in an external editor)

## 16.1 Patch-buffers

Patches may contain an arbitrary number of source buffers.
This can be used to split complex patches into multiple chunks (e.g. template definitions, lanes, ..).
Patch-buffer names must commence with a `.` character.

    def mytemplate det=0
      sin
        freq:
          1
          + %det%
      + sin
          freq:
           1
           + %det%
           * 2

.mytemplateinc buffer

    arg p_amp 0.7 0 8
    arg p_det 0 -1 1

    inc .mytemplateinc

    <out:
      ~mytemplate
      + ~mytemplate det=$p_det
      * rmp 0 1
      * 0.25
      * $p_amp
      clp

main source

| see `multibuf test 3` example patch

Just like global includes, patch buffers are selected for editing by pressing `lctrl-g` while the cursor is on an `inc` or `~` template instantiation line.
A patch buffer is deleted by removing all its content.

| the last seen cursor position is stored per buffer but it is not persistent across project file saves

# 17 Plugins

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

- slf and zsq modules may not be used
- no references to voice plugins (`vpl`)
- `dur`, `wt_w`, `wt_h`, `wt_cyclelen`, `wt_freqs`, `skip`, `xfade`, `mirror`, `zone` keywords are not allowed

| sample zones referenced by modules will be embedded in the plugin (e.g. zon, zlp, ssl)

Example:

    id fm
    oversample 4
    param p_mod_freq 0.5 min=0 max=1
    param p_mod_amt  0       0     1
    param p_vsync    0       1    16
    mod m_mod_freq
    mod m_mod_amt
    <out:
      sin
        phase:
          sin vsync=$p_vsync window=sin
            freq:
              # 0..1 => 1/16 .. 0 .. *16
              $m_mod_freq
              fma 2 -1
              bts 16
          * $m_mod_amt

simple 2-op FM plugin

## 17.1 Plugin header

Voice plugin patches must declare a unique plugin id:

    id     myplugin_v1
    name   "my plugin v1"
    author bsp
    cat    osc

| the `id` keyword enables voice-plugin mode. `name`, `author` and `cat` are optional.

## 17.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.

| parameter names may be prefixed with `p_` (prefix will be removed during export)

| 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.

### 17.2.1 Params

Parameters are always in the (normalized) range `0..1`.
Parameters may have default (UI reset) values, which will be exported to the plugin:

    param p_cutoff 0.9

default reset value
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 1+0.5*(16-1)=8.5
Instead of `param`, its shortcut form `p` can be used instead:

    p drv 0.5

param shortcut syntax

| when not in plugin mode, `param` falls back to `arg` (no arrays supported in that case)

#### 17.2.1.1 Param Preset Values

Each parameter can be assigned a list of preset values (shown when hold-clicking the param value widget).

### Examples

    param p_angle 0 0 1

    param_values p_angle 0="0 deg" 0.25="90 deg" 0.5="180 deg" 0.75="270" 1="360 deg"

param preset values example 1

    param p_mode 0 0 3

    pv p_mode 0=red 1=green 2=blue

param preset values example 2 (shortcut syntax)

| parameter preset values always refer to unscaled, normalize 0..1 values

| when not in plugin mode, `param_values` falls back to `arg_values`

#### 17.2.1.2 Param Groups

Parameters can be assigned to a group (in order to classify them).
Grouped parameters can, for example, be randomized all at once in the UI.

    param p_tune_1  0.5 -1 1  group=tune
    param p_tune_2  0.5 -1 1  group=tune
    param p_tune_3  0.5 -1 1  group=tune
    param p_tune_4  0.5 -1 1  group=tune
    param p_tune_5  0.5 -1 1  group=tune

parameter groups (excerpt from osc_hh_v1.cy)

#### 17.2.1.3 Param Sections

Parameters can (also) be assigned to sections (in order to group them in the UI).

    id more_than_8_params

    param p_amp 1 0 1

    def myosc id=0
      param p_myosc%id%_freq   0   0 1  section=myosc%id% group=tune
      param p_myosc%id%_amp    0.5 0 1  section=myosc%id% group=amp
      param p_myosc%id%_vsync  0   0 1  section=myosc%id% group=vsync
      sin
        freq:
          $p_myosc%id%_freq
          fma 2 -1
          bts 4 4
        vsync:
          $p_myosc%id%_vsync
          bts 1 16
      * $p_myosc%id%_amp

    <out:
      + ~myosc id=0
      + ~myosc id=1
      + ~myosc id=2
      + ~myosc id=3

      * $p_amp
      clp

parameter sections ("more than 8 params" example patch plugin)

#### 17.2.1.4 Array Params

Each parameter can be associated with an array (lut). Arrays have up to 32 variations, which are selected by the (normalized) parameter value.
The maximum array size is 256.
Each array param may also be associated with a curve, which defines the initial element values for a new plugin instance.
Example:

    # additive synthesis using 16 sine partials
    #  the 'level' array defines the partial magnitudes
    #
    id     array_test_1
    name   array_test_1
    cat    osc
    author bsp

    # selects 'level' array variation 'level' (0..1 => variation 1..7..0)
    param p_level 0 0 1

    # modulates 'level' array variation (per block)
    mod   m_level

    # connect curve 'level' to param 'p_level'
    #   - array size = 16 elements
    #   - num array variations = 8
    #   - initial (variation 1) values are defined by curve 0
    curve 0 level param=p_level size=16 var=8

    var v_index
    var v_out

    <out:
      set v_index 0
      set v_freq 1
      set v_out 0
      rep 16
        $v_out
        + sin freq=$v_freq
            * $v_index
              lut level clamp=0 lin=0
              * 1/16
        sto v_out
        inc v_index 1/16
        inc v_freq
      $v_out

additive synthesis using 16 sine partials

##### 17.2.1.4.1 array keyword and per-element names and ranges

The following alternative syntax for an array declaration allows to specify names, reset values, display min / max ranges, and editing precision (number of fractional digits) per element:

    id array_param

    param p_amp 0 0 1

    # "0" is the index of the curve (0..15) which defines the initial array element values for new plugin instances
    #   (note) this parameter can be set to -1, or skipped entirely
    array 0 p_amp dpyMin=0 dpyMax=1 size=4 var=4
      0 fundamental 1
      1 partial2    0
      2 partial3    0
      3 partial4    0

    mod m_amp

    <out:
      0
      + sin
        * 0/4
          lut p_amp
      + sin freq=2
        * 1/4
          lut p_amp
      + sin freq=3
        * 2/4
          lut p_amp
      + sin freq=4
        * 3/4
          lut p_amp

four-partial additive array param oscillator
Plugin param arrays can also be addressed like structs:

    id array_param_elname_test_2

    param p_amp 0 0 1

    array p_amp dpyMin=0 dpyMax=1 size=4 var=4
      0 fundamental 1
      1 partial2    0
      2 partial3    0
      3 partial4    0

    mod m_amp

    <out:
      0
      + sin
        * p_amp.fundamental
      + sin freq=2
        * p_amp.partial2
      + sin freq=3
        * p_amp.partial3
      + sin freq=4
        * p_amp.partial4

four-partial additive array param oscillator (struct-like element access)
Instead of `array`, its shortcut form `ap` (array parameter) can be used instead:

    id array_shortcut_test_1
    param cfg 0
    ap cfg s=3 v=4  # == array 0 param=cfg size=3 var=4
      0 freq   0.5 0    1  3  # def=1 min=0 max=1  prec=3
      1 amp    1              # def=1 min=0 max=1  prec=5
      2 vsync  1   1    32 2  # def=1 min=1 max=32 prec=2
    <out:
      sin window=hse4
        freq:
          cfg.freq
          fma 2 -1  #  0..1 => -1..1
          bts 4 16  # -1..1 => /4..*16
        vsync:
          cfg.vsync
          fma 31 1  # 0..1 => 1..32
      * cfg.amp

array parameter shortcut syntax

| while each array element has its own reset / default value, display value range, and editing precision, accessing it in a patch will always return the storage value, i.e. it is up to the patch to actually scale it to the display range

The following attributes are supported in an array declaration:

Name | Shortcut name | Description
--- | --- | ---
param | p | associated parameter name (can be omitted when it's the third word)
size | s | array size (1..256)
var | v | num variations (1..32)
min | - | minimum storage value (-1..1, def=0)
max | - | maximum storage value (-1..1, def=1)
dpyMin | dmin | minimum display value (arbitrary float) (def=storage min)
dpyMax | dmax | maximum display value (arbitrary float) (def=storage max)
dpyPrec | dprec | display value editing precision (num fractional digits, 0..7) (def=5)

Array parameter attributes

| the initial values of the first variation of an array are defined by the associated (bezier) curve

| the curve resolution should equal the array size (a power of two), otherwise the initial (variation 1) array values will either be a partial copy of the curve (when the curve size is larger than the array size), or the remaining elements will be filled with 0 (when the curve size is smaller than the array size).

| when wrap-around mode is used (`clamp=0`), the array size should be a power of two

| the effective, interpolated (per-voice) array values are updated at the beginning of a block whenever the curve selection (array control parameter and / or modulation) has changed, or when the voice has just been started.

| while array param floating point elements are stored in signed 16bit format in Eureka sampler patches (`.esa`), from a plugin's point of view they are always 32bit IEEE floats.

| in the editor, array params have an 'a' button next to them which opens the array / variation editor dialog

### 17.2.2 Mods

Mods have an implicit reset value of 0 and are typically in the range `-1..1`.

    mod m_cutoff

declare modmatrix destination

| the actual range is unlimited (float value range), i.e. clipping it (if necessary) is up to the patch

### 17.2.3 modparam shortcut

In plugin mode, patches usually declare a `mod` for each `param` so it can be modulated via the modulation matrix.
The `modparam` and `mp` keywords declare both a `param` and a `mod`, and auto-prefixes the names with `p_` and `m_`:

    id myplugin

    modparam amp   # declare both param p_amp and mod m_amp

    <out:
      sin
      * $m_amp  # read ($p_amp + $m_amp)

modparam example 1

    id myplugin

    mp amp   # declare both param p_amp and mod m_amp

    <out:
      sin
      * $m_amp  # read ($p_amp + $m_amp)  (recommended syntax)
      * $amp    # read ($p_amp + $m_amp)  (easier to read but might be ambiguous)

modparam example 2 (shortcut syntax)

### 17.2.4 Args and envelopes

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

### 17.2.5 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

### 17.2.6 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.

### 17.2.7 prepare

The `<prepare:` section is evaluated before the main section(s), and after the (optional) `<init:` section.
Heavy 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.

### 17.2.8 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

## 17.3 Stereo processing

By default, the audio input and output is monophonic. However, when (at least one of) the rdl, rdr, wrl, wrr modules is used, the audio signal path becomes stereo.
In stereo mode, the default output variable (`out` in the generated code) is still available but will not be written to the audio input.
Instead, the wrl / wrr modules take the previous output value and write it to the left / right channel output.
Vice versa, rdl / rdr read the current left / channel input.
Example:

    id stereo_test_1

    # declare param p_amp and mod m_amp
    modparam detune 0.5 0 1

    var v_freq_l smooth=1
    var v_freq_r smooth=1

    <prepare:
      # calc detuned left / right channel oscillator frequencies
      $m_detune
      fma 0.1 -0.05
      . neg
        + 1
        sto v_freq_l
      +1
      sto v_freq_r

    <out:
      sin freq=$v_freq_l
      * 0.7
      wrl # write left channel output

      sin freq=$v_freq_r
      * 0.7
      wrr # write right channel output

stereo sine oscillator plugin

## 17.4 Native code extension modules (C / C++)

In plugin mode, new Cycle modules can be implemented via native code (C / C++) extensions.
The `xinc` keyword is used to import a native code header file.
The `eureka_config.tks:stfx_inc` variable sets the default search path for native code extension files (usually `$(PROGRAM_DIR)/include/xinc/`).
Example:

    id ext_mod_test_1
    xinc "xamp.h"
    modparam drive 0.5 0 1
    <out:
      sin
      xamp sat=1
        drive:
          $drive
          fma 2 -1  #  0..1 => -1..1
          bts 4 16  # -1..1 => /4..*16

xamp example

| the `xinc` files are read everytime the script is compiled, i.e. changes to the C / C++ code take effect immediately and do not require an application restart

### 17.4.1 Header file interface definition keywords

During import, the header file is scanned to find the module name, statically compiled configuration items, dynamic inputs, module vars, voice struct fields, and init / prepare / render code to be inserted for each module instance:

    /*
     * Native code extension module definition (parsed by Cycle):
     *
     *  @cycle_mod      xamp
     *  @cycle_pre      // %cymod% pre-include code line
     *  @cycle_static   // %cymod% module-static test code line
     *  @cycle_init     // %cymod% init function test code line
     *  @cycle_new      // %cyid% voice_new() test code line
     *  @cycle_delete   // %cyid% voice_delete() test code line
     *  @cycle_voice    // %cyid% voice_t field struct test code line
     *  @cycle_noteon   // %cyid% noteon function test code line
     *  @cycle_input    drive 0.5
     *  @cycle_config   sat 0  0=off 1=on
     *  @cycle_emit     %cyio% *= %cyin_drive%;
     *  @cycle_emit     if(%cycf_sat% > 0) %cyio% = tanhf(%cyio%);
     *
     */

xamp example extension module
In this (very simple) example module, the entire native code implementation is written using the `@cycle_emit` keywords (each one adds a line of code for each module instantation).
In a more complex module, the header file will contain additional `C` structs or `C++` classes. See the `x303.h`, `xldf.h`, `xrlf.h` and `xrm.h` example files.

| while the main extension file is just a `.h` (or `.hpp`) header, this may use all C / C++ features and may include additional `.c` / `.cpp` / `.cxx` / `.h` / `.hpp` files.

### 17.4.2 Module input and output

Extension modules may read the output of the previous module from `%cyio%` (float) and also write new output to it.

### 17.4.3 Static configuration items

Configuration options declared via `@cycle_config` will be turned into `#define`s and can be inserted anywhere in a (module-instance specific) `@cycle_` code line via the `%cycf_<config_item_id>%` syntax.
In a Cycle script, these will look like regular inputs (e.g. the `sat` input in the xamp example above).

| When static configuration items are used in conditional statements (`if`, `switch`, ..), the C/C++ compiler will not generate runtime checks and instead optimize the code for the current configuration

| an (optional) list of constant names can be assigned to each config item (e.g. `0=off 1=on`)

### 17.4.4 Dynamic inputs

Inputs declared via `@cycle_input` will be evaluated at runtime. The current input value for a module instance is stored in a variable which can be refered to via `%cyin_<input_id>` anywhere in a (module-instance specific) `@cycle_` code line.

| Unless the module is very simple, the input values are usually passed to a user-defined `calc()` / `process()` function or method.

### 17.4.5 Voice fields

The native code implementation for a `calc()` method is usually placed in a class which is instantiated per voice, and per module instance (see `include/xinc/xrm.h`):

     [..]
     * @cycle_voice  je_ringmod_state_t %cyid%;
     [..]

je ringmod voice field instance
The field can (optionally) be reset at note on (see `include/xinc/x303.h`):

     [..]
     * @cycle_noteon voice->%cyid%.reset(voice->sample_rate, voice->sr_factor);
     [..]

303 filter voice field note on reset

| `voice→sample_rate` stores the host's output sample rate (e.g. 32000, 44100, 48000, 96000). `voice→sr_factor` stores the ratio between the patches reference sample rate (def=48kHz) and patch-internal oversampling factor and the host sample rate (e.g. when both host and reference sample rate are set to 48kHz and the oversampling factor is 4, `sr_factor` is set to `1/4`). A properly written extension module behaves the same at all sample rates and oversampling factors so make sure to incorporate `sr_factor` in your code (e.g. when calculating envelopes or filter cutoffs).

### 17.4.6 Extension modules

#### 17.4.6.1 x303

A 303-ish filter (written in 1998 by Andy Sloane).

### Inputs

| `freq` | cutoff frequency (0..1)
| `q` | resonance (0..1)
| `trig` | envelope trigger (set to 1 to trigger at note-on)
| `envmod` | filter envelope modulation amount
| `envdcy` | filter envelope decay

#### 17.4.6.2 xdi

A diode wave shaper (based on JE's ringmod which is based on Julian Parker's A simple model of diode-based ring-modulator).

### Inputs

const | `vb` | forward voltage-bias
const | `vbvl` | linear voltage threshold
const | `slope` | linear voltage slope

#### 17.4.6.3 xdly

A delay.

### Inputs

| `t` | normalized delay time (0..1 ⇒ 0..delaybuffersize-1) | def=0.5
| `fb` | feedback amount (0..1) | def=0.0
| `wet` | dry / wet mix | def=0.5

#### 17.4.6.4 xfs

A frequency shifter based on Squinky Lab's sql_bootyshifter.

### Inputs

| `freq` | frequency shift (-f..f)
| `range` | range in Hz

#### 17.4.6.5 xfsxy

A frequency shifter based on Squinky Lab's sql_bootyshifter.
Dedicated `x` / `y` inputs allow for more fine-grained control over the sine / cosine inputs.

### Inputs

| `x` | sine input
| `y` | cosine input

#### 17.4.6.6 xldf

A Moog-ish ladder filter (adapted from a musicdsp.org post in 2010)

### Inputs

| `freq` | cutoff frequency (0..1)
| `q` | resonance (0..1)
const | `mode` | 0=`lpf` 1=`hpf` 2=`bpf`

#### 17.4.6.7 xlrf

    The sum of the Linkwitz-Riley (Butterworth squared) HP and LP outputs, will result an
     all-pass filter at Fc and flat magnitude response - close to ideal for crossovers.

Lubomir I. Ivanov
based on an implementation by `moc.snoitcudorpnrec@mij`

### Inputs

| `freq` | cutoff frequency (0..1)
| `mode` | 0=lpf 1=hpf

#### 17.4.6.8 xrm

A four quadrant diode ring modulator (based on JE's ringmod which is based on Julian Parker's A simple model of diode-based ring-modulator.

### Inputs

| `a` | carrier input
| `b` | modulator input
| `alvl` | carrier level
| `blvl` | modulator level
| `off` | offset
| `vb` | forward voltage-bias
| `vbvl` | linear voltage threshold
| `slope` | linear voltage slope
const | `apol` | carrier polarity (0..2)
const | `bpol` | modulator polarity (0..2)
const | `mode` | 0=ring 1=sum 2=diff 3=min 4=max

## 17.5 Export

Press `lctrl-p` or `lctrl-w` 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).

## 17.6 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.

### 17.6.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.

| In case MSYS2 was not installed to its default directory, edit `eureka_config.tks` and update the `stfx_cc_msys2_dir` variable accordingly.

### 17.6.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)

| XCode updates are installed through the macOS `Software Update` and their availability appears to depend on the currently installed macOS version

### 17.6.3 Linux

On Debian or Ubuntu, type

    $ sudo apt update
    $ sudo apt install build-essential

Document created in 336ms on 31-May-2025 14:16:34