Document: | Cycle Sample Generator / Soft Synth
Author: | bsp
Date: | 10-Nov-2024

# 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 | Calc first half 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")
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")
macro | Declare macro parameter set(s). See macros.
inc | include `.cy` file (the suffix is auto-appended)

Global Keywords

## 3.1 Plugin keywords

Keywords used in plugin-generation mode:

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

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

author | Plugin author
cat category | Plugin category (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 multiple output sections.
Each script must contain the main section (named `out`):

    <out:
      sin

| variables declared within a lane are automatically prefixed with the lane id

## 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, or calculations which do not need to be evaluated per sample frame should be placed in the `prepare` section.

    id     simple_2op_fm_osc
    name   "simple_2op_fm_osc"
    author bsp
    cat    osc

    param p_op2_ratio 0.5 0 1
    param p_op2_level 0 0 16

    mod m_op2_ratio
    mod m_op2_level

    var v_op2_freq

    <prepare:
      # ratio 0..1 => (1/4096)³ .. 1 .. 4³
      $m_op2_ratio
      # 0..1 => -1..1
      fma 2 -1
      # -1=>div 4096, +1=>mul 4
      bts 4096 4
      pow 3
      # quantize to 100 fractional divisions per integer
      qua 100
      sto v_op2_freq

    <out:
      sin
        phase:
          sin freq=$v_op2_freq
          * $m_op2_level

simple 2 operator FM / phase modulation oscillator plugin

| 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

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

# 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

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

Instead of `arg`, its shortcut form `a` can be used instead:

    a lvl 0.7

    <out:
      sin
      * $lvl
      clp

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

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

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

## 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 | OPADDSR | Add (over)samplerate-independent value
- | OP_SUB | Subtract (a-b)
r | OP_RSUB | Reverse-Subtract (b-a)
* | OP_MUL | Multiply
*sr | OPMULSR | 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 | Discard / Ignore
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 (2ab)/(a+b)
HM | OP_HMU | Unipolar Harmonic mean rescaled to -1..1
gm | OP_GM | Geometric mean sign(ab)sqrt(abs(a*b))
GM | OP_GMU | Unipolar Geometric mean rescaled to -1..1

Operators
The default operator is `replace`.

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

static 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

This is equivalent to full-wave rectification.
See hwf for half-wave rectification.

## 10.2 adsr

Analog-style Attack / Decay / Sustain / Release envelope generator.
Previous output is gate signal (rising edge restarts envelope, falling edge starts release phase).
`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`
`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)
`mode`/`m` selects envelope flavour. 0=rate-shaping (v2/default, smoother). 1=level-shaping (v1, more percussive)
`vel`/`v` enables velocity (gate level) scaling (1=on, 0=off) (must be const)
`onend`/`oe` evaluated when end of envelope is reached

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

## 10.3 bit

Bit-crush (shift) previous output.
Example:

    <out:
      sin
      bit shift=0.5
      bit 0.5

`shift`/`s` range is 0..1

## 10.4 boo

Boost sample deltas of previous output.
Example:

    <out:
      sin
      boo amount=0.6
      boo 0.6

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

## 10.5 box

Box filter previous output.
Example:

    <out:
      saw
      box freq=0.5
      box 0.5

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

## 10.6 bpf

reserved

| use svf in `mode=bpf` instead

## 10.7 bts

Bipolar to scale.
Example:

    arg $freq
    <out:
      sin
        freq:
          $freq
          # 0..1 => -1..1
          fma 2 -1
          # -1..1 => 1/16 .. *16
          bts 16

`div`/`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.8 buf

not implemented, yet

## 10.9 bus

Read sample from (cross-zone) voicebus.
Examples:

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

phase-modulate sine wave by previous zone output

    id vb_read_1
    param lvl 1 0 1
    <out:
       sin
         phase:
           bus 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:
           bus 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:
           bus nrf=$bus
           * $lvl

phase-modulate sine wave by previous zone output (float bus nr)
`nri` is in the range 0..32 (bus 1..32, 0=previous zone nr)
`nrf` is in the range 0..0.32 (bus 1..32, 0=previous zone nr)

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

## 10.10 ceq

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

ceq example
`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
`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
`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
`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
`hz` is in range 0..f (clock ticks per second.
`bpm` is in range 0..f (0=host bpm, beats per minute (quarter notes) otherwise. used when bpm is not connected) (default=0)
`div` is in range 0..f (clock divider. <1 speeds up clock, >1 slows it down)
`dur` duty cycle duration in milliseconds (def=5)
`force`/`f` 1=emit 0 at end of duty cycle when cycle duration is longer than interval (def=1) (must be const)
`restart`/`rs` restart clock counter on rising edge
`swing`/`sw` <0: pull every 2nd pulse >0: push/delay every 2nd pulse
`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 floor=-0.75 ceil=0.6

separate boundaries
`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
`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
`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
`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

    var f
    <out:
      set f 0
      0.1
      dec f

## 10.21 div

Divide values.
Example:

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

`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

reserved
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
`ad` attack/decay rate (0..1)
`s` sustain position (0..1)
`loop`/`lp` loop enable (0 or 1) (must be const)
`restart`/`rs` restart signal
`sr` enable sample-rate independent envelope speed (must be const) (voice plugins only)
`onend`/`oe` evaluated when end of envelope is reached / envelope loops
`scl` envelope rate scaling (0..1)
`tuned`/`tun` normalize envelope rates (auto-enables loop mode) (must be const)

### 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 0 stores the envelope shape
    curve 0 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
`ad` Attack/Decay rate is in the range 0..1 (def=0.01)
`s` Sustain position is in the range 0..1 (def=0.5)
`r` Release rate is in the range 0..1 (def=0.01)
`loop` 1=loop envelope (don't halt at sustain position), 0=oneshot (def=0)
`sr` 1=(over-)samplerate independent rates (def=1)
`onend` evaluated when envelope release phase finishes (or envelope loops)
`scl` overall (ad+r) rate-scaling (def=0.001)
`tuned` 1=pitch-independent AD/S/R (auto-enables loop mode) (def=0) (must be const)

## 10.26 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.27 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.28 fld

Fold previous output value at ceiling.
Example:

    <out:
      sin
      * 2.0
      fld ceil=1.0

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

Example:

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

| `min` / `max` override default ceil

`ceil`/`c` sets min=-val, max=+val
`min`/`m` minimum value
`max`/´x` maximum value
'hq' 1=enable high-quality (float) mode. def=1. (must be const)

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

## 10.29 fma

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

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

`mul`/`m` input is in range -15..15 [def=2]
`add`/`a` input is in range -15..15 [def=-1]

## 10.30 frc

Calc fractional part of previous output (discard integer bits)
Example:

    <out:
      sin
      * 3.3
      frc

| itg does the opposite and discards all fractional bits

## 10.31 fsr

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

    <out:
      fsr

Example 2:

    <out:
      fsr seed=$4489 bits=1.0 cycle=1

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

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

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

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

See hwr for half-wave rectification.
`bipolar`/`b` range is 0/1 (1=rescale to -1..1 range) (must be const)
`neg`/`n` range is 0/1 (1=flip output sign) (must be const)

## 10.33 hbx

High-pass Box filter previous output.
Example:

    <out:
      saw
      hbx freq=0.5
      hbx 0.5

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

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

~~ hwrtest2_max
id hwrtest2_max
<out:
sin
vst a
sin phase=0.1 f=2.01
vst b

# 11 max(a,b) = (b+hwr(a-b))

$a

- $b

hwr
+ $b

    ] max

id hwrtest3_min
<out:
sin
vst a
sin phase=0.1 f=2.01
vst b

# 12 min(a,b) = (a-hwr(a-b))

$a

- $b

hwr n=1 # -hwr
+ $a

    ] min

    See [#fwr][] / [#abs][] for full-wave rectification.

    `bipolar`/`b` range is 0/1 (1=rescale to -1..1 range) (must be const)
    `neg`/`n` range is 0/1 (1=flip output sign) (must be const)

    ## hld
    ] sample and hold previous output

    Sample and hold previous output.

    Example:

<out:
hld rate=0.5

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

    note: 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`

    ## hpf
    ] high-pass filter previous output

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

    Example:

<out:
saw
hpf cfg=3

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

    `cfg`/`c` sekects filter config (0..13)

    ## if
    ] conditional code generation

    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

    ## inc
    ] increment variable

    Increment variable

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

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

- 2

var f
<out:
set f 0
0.1
inc f

    ## int
    ] emit sub-tree in integer mode

    Evaluate sub-tree in integer mode.

    The previous output value is converted to 16bit fixpoint (1:4:11) before the sub-tree is emitted.
    Afterwards the integer output value is converted back to float.

<out:
int
sin

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

    ## ipl
    ] Interpolate from a to b at time t

    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

    `a` source value (def=0)
    `b` destination value (def=1)
    `t` interpolation amount
    `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

    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 pwindowamt 1 0 1
param pwindowfreq 0 0 1
mod m_vsync
mod mwindowamt
mod mwindowfreq
<out:
sin
vsync:

# 13 0..1 ⇒ 1..16

$m_vsync
fma 15 1

# 14 interpolate between sine wave and 0 using a triangle window

# 15 (note) the 'a' input of the 'ipl' module is the previous output (sin)

# 16 (since 'a' is not explicitily connected)

ipl b=0
t:
1.0

- tri
  freq:

# 17 0..1 ⇒ 1..16

$mwindowfreq
fma 15 1

# 18 to integer

itg

# 19 -1..1 ⇒ 0..1

fma 0.5 0.5

- $mwindowamt

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

    tip: The initial input to `a`, `b` and `t` is the previous output. This can be used to interpolate between warped versions of the input signal.

    ## itg
    ] integral part of previous output

    Calc integral part of previous output (discard fractional bits)

    Example:

<out:
sin

- 16

itg

- 1/16

    note: [#frc][] does the opposite and discards all integer bits

    ## kbd
    ] MIDI note keyboard tracking

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

    `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

id cutoffkbdtracking_test
name "cutoff kbd tracking test"
author bsp
cat filter
param p_cutoff 0.5 0 1
param p_keyfollow 1 0 1
param poctaveoff 0 -4 4
<out:
svf
freq:
$p_cutoff
kbd $pkeyfollow $poctave_off

    ] filter cutoff keyboard tracking example plugin

id pitchkbdtracking_test
name "pitch kbd tracking test"
author bsp
cat osc
param p_keyfollow 1 0 1
param poctaveoff 0 -4 4
<out:
sin fixed=1
freq:
1.0
kbd mode=1 amt=$pkeyfollow off=$poctave_off ctr=60

    ] oscillator pitch keyboard tracking example plugin

    `amt`/`a` is in the range -16..16 (keyboard follow amount) [def=1]
    `off`/`o` is in the range -4..4 (octave offset) [def=0]
    `scl`/`s` is in the range 0..16 (post scaling) [def=4]
    `clamp`/`c` toggles clamping to 1.0 (def=true) (should be set to 0 when using this for pitch tracking) (must be const)
    `mode`/`m` selects mode (0 or 1) (must be const) [def=0]
    `ctr` sets keyboard center note (only used in `pitch=1` mode) (must be const) [def=60]

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

    ## lle
    ] log / lin / exp scaling

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

8
lle c=1.4

    `c` is in the range -f..f.

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

    ## log
    ] calc logarithm of previous output

    Return logarithm of previous output.

8
log base=2

    `base`/`b` is in the range 1..f (must be constant). When input is not connected, return the natural logarithm.

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

    ## lop
    ] loop statement sequence

    Loop statement sequence. Supports dynamic loop iterators.

var i
var num
<out:
2.0

- 4.0

sto num
0.0
sto i
lop $num
+ 0.01

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

    CAUTION: some modules (e.g. `sin`) use variables to store their state. evaluating such modules in a loop will cause all iterations to use the same state.
    TIP: use `rep` to generate unrolled loops where each iteration will have its own state

    ## lpf
    ] 1RC low pass filter

    1RC low pass filter

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

    `freq`/`f` range is 0..1
    `res`/`r` range is 0..1

    ## lut
    ] read from look-up table

    Use previous output as index for curve look-up table (LUT)

    Example:

curve 0 amp
<out:
sin

- rmp 0 0 1
  lut amp clamp=0 lin=0

    `clamp`/`c` must be either 0 (repeat lut) or 1 (clamp to last index)
    `lin`/`l` must be either 0 (nearest neighbour) or 1 (linear interpolation)
    `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.
    `bipolar`/`s` enable bipolar index mode (flip sign of index+output as required) (must be const)

    note: the `lut` module is often combined with a [#rmp][] or [#pha][] module that generates the look-up index

    ## mac
    ] update interpolated macro fields

    The `mac` module updates the current, interpolated values of a macro's fields.

    See [#macros][] for further information and examples.

    `pos`/`p` is in the range 0..1 (last setting is lerped back to first setting)

    ## map
    ] remap value

    Remap previous output value using a non-contiguous table.

    See [#Maps][] for further info and example code.

    `mode`/`m` 0=nearest (default) 1=match 2=up 3=down 4=ipl/lin  (must be constant)
    `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)

    ## mkv
    ] markov-chain element

    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.

    `a` input A (def=0)
    `b` input B (def=1)
    `r` random input. use previous output value when not connected.
    `ab` A \=> B probability (0..1)
    `ba` B \=> A probability (0..1)
    `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)

    ## mul
    ] multiply variable

    Multiply variable

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

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

    ## neg
    ] negate previous output

    Negate previous output

    Example:

<out:
saw
neg

    ## nos
    ] generate xor-shift white noise

    Render white noise via xor-shift pseudo random number generator

    Example:

<out:
nos seed=0x44894489 cycle=0

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

    ## not
    ] invert previous logic signal output

    Invert previous logic signal output

    Example:

<out:
1

# 20 1 ⇒ 0

not

# 21 0 ⇒ 1

not

    ] invert previous output

var v_state
<out:

# 22 use "inc" module in "not" mode and invert the boolean state of the given variable

not v_state

    ] invert boolean variable

    ## note
    ] return voice note number (plugins)

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

id notetest1
<out:
sin
freq:
0.1
+ note

- 1/32

    ] note example

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

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

    ## nth
    ] pass every nth clock signal

    Discards all clock signals except for every nth one.

    Example:

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

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

    ## out
    ] write sample to (cross-zone) voicebus

    Write sample to (cross-zone) voicebus.

    Examples:

id vbouttest
param lvl 1 0 1
<out:
sin
out 1

    ] write sine output to voice bus 1

    `nri` is in the range 1..32 (bus 1..32)
    `nrf` is in the range 0.1..0.32 (bus 1..32)

    caution: only supported in STFX plugin mode

    ## pha
    ] phase accumulator

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

    Example:

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

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

    ## p2s
    ] exponential power-of-two scaling

    Return `(pow(2, exp * prev_out) - 1.0) / (pow(2, exp) - 1.0)`.

    Can be used for e.g. scaling filter cutoff frequencies (linear to exponential).

    Example:

arg cutoff
<out:
svf
freq:
$cutoff
p2s 7

    ] (non-kbd-tracked) exponential cutoff scaling

    When the `kbd` (and optionally the `off`) input is connected and Cycle is in STFX plugin-generation mode, the module input follows the keyboard.

id cutoffkbdtrackingtestp2s
name "cutoff kbd tracking test p2s"
author bsp
cat filter
param p_cutoff 0.5 0 1
param p_keyfollow 1 0 1
param poctaveoff 0 -4 4
<out:
svf
freq:
$p_cutoff
p2s kbd=$pkeyfollow off=$poctave_off

    ] filter cutoff keyboard tracking example plugin

    `exp`/`e` is in the range 0..15 [7]
    `scl`/`s` is in the range 0..1 [0] (0=>scl=(pow(2,exp)-1)) (must be const)
    `kbd`/`k` is in the range -16..16 (keyboard follow amount)
    `off`/`o` is in the range -4..4 (octave offset)

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

    tip: for keyboard tracking purposes, the [#kbd][] module is usually the preferred solution

    ## pow
    ] raise previous output to the power of 'f'

    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. 2.71828182845904523 # e
   exp:
   sin

    ] generic pow with base input connected

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

    tip: useful for bending [#rmp][] envelopes into non-linear shapes

    caution: constant exponents 2 and 3 behave differently than arbitrary (or dynamic) exponents, i.e. -2²=+4 while e.g. -2^4=-16
    caution: arbitrary or dynamic exponents are only supported in float-mode (fall back to float in int mode)

    ## pul
    ] generate pulse wave

    Generate pulse wave

    Example:

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

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

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

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

    ## qua
    ] quantize previous output

    Quantize float value to n fractional steps per integer.

<out:
sin
qua num=10

    `num`/`n` is in the range 1..n

    tip: useful for controllers, e.g. FM ratios

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

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

    ## rcp
    ] reciprocal (1 / x)

    Calc reciprocal (1/x) of previous output.

    ## rdl
    ] read left channel (plugin) input

    Read left channel input sample.

    caution: available only in plugin mode.

    ## rdr
    ] read right channel (plugin) input

    Read right channel input sample.

    caution: available only in plugin mode.

    ## red
    ] detect logic signal rising edge

    Detect logic signal rising 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

    note: this module has no inputs

    see also: [#fed][]

    ## rep
    ] loop / unroll statement sequence

    Loop / unroll statement sequence. The number of iterations must be constant.

arg p_num 8 1 32
var i
<out:
1.0
sto i
0.0
rep $p_num
+ sin
freq:
$i

- 0.5

- 0.1

clp

    `num`/`n` range is 0..1023

    ## rev
    ] --reserved--

    **reserved**

    ## rmp
    ] generate linear ramp

    Generate linear ramp

    Example:

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

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

    ## saw
    ] generate sawtooth wave

    Generate sawtooth wave

    Example:

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

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

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

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

    ## sat
    ] saturate previous output

    Saturate previous output.

    Processes previous output value through tangens hyperbolicus (tanh) function.

    Example:

arg drive 0 -1 1
var v_amp
<prepare:

# 23 amp=/10..*10

10
pow $drive
sto v_amp
<out:
sin

- $v_amp

sat # same as tanh

    ] sat

arg drive 0 0 2
lut tanh shared 4096
<lut_tanh:

# 24 0..1 ⇒ -100..+100

fma 200 -100
sat # same as tanh
var v_amp
<prepare:

# 25 amp=*1..*100

10
pow $drive
sto v_amp
<out:
sin

- $v_amp

# 26 -100..+100 ⇒ 0..1

fma 1/200 0.5
lut tanh lin=1

    ] tanh lookup-table

    See [#tanh][] (deprecated name in earlier releases)

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

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

    ## set
    ] set variable

    Set variable

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

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

var f
<out:
1.23
set f

    ## sin
    ] generate sine wave

    Generate sine wave

    Examples:

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

<out:
sin
pd:
tri freq=1

    ] sine osc phase distortion

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

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

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

    ## slf
    ] read past sample output

    Read past sample output ("self" feedback)

<out:
pul
+ slf rate=0.98
clp

    `rate`/`r` range is 0..1

    ## slw
    ] slew

    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)

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

    ## spd
    ] return voice note speed (plugins)

    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

# 27 wrap-around (keep only the fractional bits)

frc

# 28 0..1 ⇒ -1..1

fma 2 -1

    ] a simple saw oscillator (pitch follows keyboard)

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

    ## spr
    ] dampened spring

    Apply dampened spring oscillation to previous output value.

    Example:

id springtime1
<out:
pul

- 0.5

spr spd=0.1 dcy=0.8
clp

    ] dampened spring example plugin

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

    ## ssl
    ] sine slice oscillator

    Sine slice oscillator.

    Works similar to how the additional 7 waveforms on the Yamaha TX81Z were created back in the 1980ies.
    The (built-in) sine table as well as the destination waveform(-cycle) is divided into 8 regions / slices.
    Each target slice is assigned one of the 8 sine regions (0..7).

<out:
ssl 01234567 freq=1.0 phase=0.0 mode=4 mod=0 seq=0 seqmod=0 modmask=1.0 zeromask=0.0

    The first argument is the base slice sequence (0=region 0, 7=region 7).
    When the sequence has less than 8 entries, it will be repeated.

    `freq`/`f` is in the range 0.25..4 (1.0=middle C in fixed frequency mode)
    `phase`/`ph` is in the range 0..1
    `mod` is in the range -1..1 (slice modulation)
    `seq`/`s` is in the range 0..1 (0=base slice sequence, 00000001..7777777). Must be const.
    `modmask`/`mm` is in the range 0..1 (0=no slices, 1=all slices)
    `zeromask`/`zm` is in the range 0..1 (0=no slices cleared, 1=all slices cleared)
    `mode`/`m` is in the range 0..5 (must be const):
    |: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
    ] SLL mode settings
    `seqmod`/`sm` is in the range 0..3 (per-sample sequence modulation)

    ## sta
    ] store variable in LUT

    Store variable in LUT at normalized index determined by previous output value.

    Syntax: `sta <lut_id> <index_mul> <index_add> <src_var> <var_mul> <var_add>`

    Example:

lut ws shared 4096
lut ws_inv shared 4096 inv
<lut_ws:

# 29 transform implicit input x=

fma 2 -1
pow 3

# 30 store in inverse LUT 'ws_inv'

# 31 - transform 'pow' result (-1..1) into normalized index (0..1)

# 32 - transform x from 0..1 to -1..1 and store in LUT

sta ws_inv 0.5 0.5 x 2 -1

# 33 store 'pow' result in LUT 'ws' (done by auto-generated code)

# 34 (note) holes in the ws_inv LUT will be auto-filled afterwards

<out:
sin

# 35 -1..1 ⇒ 0..1

fma 0.5 0.5
lut ws
lpf freq=0.3 res=0.5
clp

# 36 -1..1 ⇒ 0..1

fma 0.5 0.5
lut ws_inv

    ] inverse exponential lookup table

    Example 2:

curve 0 ws
lut ws_inv shared 2048 inv
<lutwsinv:

# 37 use implicit input x (0..1) as index into bezier curve 'ws'

lut ws
clp

# 38 store in inverse LUT 'ws_inv'

# 39 - transform 'clp' result (-1..1) into normalized index (0..1)

# 40 - transform x from 0..1 to -1..1 and store in LUT

sta ws_inv 0.5 0.5 x 2 -1

# 41 don't write output value to lut (already written)

end
<out:
sin

# 42 -1..1 ⇒ 0..1

fma 0.5 0.5
lut ws
lpf freq=0.3 res=0.5
clp

# 43 -1..1 ⇒ 0..1

fma 0.5 0.5
lut ws_inv

    ] inverse bezier curve lookup table

    note: 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

    ## sto
    ] store previous output in variable

    Store previous output in variable

    Example:

var t
<out:
sin
sto t

- $t
- $t

    The variable is initialized with 0 but retains its content over multiple sample frame iterations.
    Example:

var sin_val
<out:
$sin_val
+ sin
sto $sin_val
clp

    ## svf
    ] state-variable filter (lpf, hpf, bpf, notch)

    Process previous output with state-variable filter

    Example:

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

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

    ## tanh
    ] saturate previous output

    **deprecated**, see [#sat][]

    ## tmp
    ] instantiate template

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

    see [#templates][]

    ## trc
    ] debug-trace last output value

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

    Example:

lut mylut shared 16
<lut_mylut:

# 44 print current $x

trc
<out:
sin

    `c` 1=trace value changes only, 0=trace all values  (def=1) (must be const)
    `z` 1=trace 0 values, 0=skip (def=1) (must be const)

    ## tri
    ] generate triangle wave

    Generate triangle wave

    Example:

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

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

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

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

    ## tsq
    ] generate trigger sequence

    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 arrayparamseqtest_1
param p_seq
curve 0 steps param=p_seq size=16 var=4
var venvlvl
<out:
clk div=1/4

# 45 (note) 'x' is a dummy pattern

tsq x len=16 stepth=0.5
step:

# 46 input is step index (0..15)

- 1/16

lut steps
ontrig:
set venvlvl 1
tri

- $venvlvl
  *sr 0.9993
  sto venvlvl
  slw 0.05 0.05

    ] read trigger pattern from array param

    `dur`/`d` range is 0..f (trigger pulse duty cycle duration in milliseconds)
    `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))
    `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)
    `loop`/`lp` range is 0..1 (1=repeat pattern, 0=oneshot/stop at end of pattern) (must be const)
    `len`/`l` sequence length (1..32(float), 1..15(integer/lo-fi)). default=0=use xx.xx pattern length. must be const.
    `step`/`s` when connected, eval subtree when value is needed for step 'x'. input is step index (0..n), output is step value.
    `stepth`/`sth` threshold for `step` output (value=1 when output is `> stepth`, 0 otherwise). def is 0.0. must be const.
    `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

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

    ## vel
    ] return voice velocity (plugins)

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

id veltest1
<out:
sin

- vel # linear velocity curve

    ] velocity example 1

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

id veltest2
<out:
sin

- ipl a=0.1 b=0.9 t=vel win=smoothstep # non-linear, biased velocity curve

    ] velocity example 2

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

    ## vpl
    ] invoke voice plugin

    Process previous output via [stfx](https://github.com/bsp2/stfx/) voice plugin.

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

    ] Example (single line)

arg drive 0.39 min=0 max=1
<out:
sin 5.0
vpl "bsp ws quintic" "Dry / Wet"=0.5 "Drive"=$drive

    ] Example (singe line with arg slider)

arg drive 0.39 min=0 max=1
<out:
sin 5.0
vpl "bsp ws quintic"
"Dry / Wet" = 1
"X^0" = 0
"X^1" = 0.5
"X^2" = 0.74
"X^3" = 0.8
"X^4" = 0.62
"X^5" = 0.5
"Drive" = $drive

    ] Example (multi line with arg slider)

    In Lo-Fi (integer) mode, the input/output values are temporarily converted to / from float.

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

    note: Plugin setup can be exported to single- or multi-line Cycle script (clipboard) via Eureka sampler "FX" tab context menu

    caution: `vpl` modules cannot be exported to "C" source

    ## vsq
    ] generate value sequence

    Generate value sequence.

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

    See [#tsq][] module

    ## vst
    ] declare variable and store previous output

    Example:

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

- 0.5

    ] vst example

    note: the variable will only be accessible within the lane it was declared in

    ## wrl
    ] write left channel (plugin) output

    Write left channel output sample.

    `value`/`v` when connected, read sample from input instead of previous output value

    caution: available only in plugin mode.

    note: implicitely enables STEREO plugin mode

    ## wrr
    ] write right channel (plugin) output

    Write right channel output sample.

    `value`/`v` when connected, read sample from input instead of previous output value

    caution: available only in plugin mode.

    note: implicitely enables STEREO plugin mode

    ## xfd
    ] crossfade

    Crossfade between previous output and input `b`.

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

    ] Example (crossfade between triangle and saw)

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

    See also: [#ipl][]

    ## zlp
    ] read looped sample frame from other zone

    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

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

    note: sample len is determined by last loop length of the specified zone
    note: sample len should be a power of two (read offset is ANDed with (sampleLen-1))

    ## zon
    ] read sample frame from other zone

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

    Example:

zone 0 otherzone
zone 1 othersmp.otherzone
<out:
zon 0 rate=0.3
+ zon 1 0.9
clp

    Upsampled loop example:

zone 0 loopzone
<out:
zon 0 rate=0.5 lin=1 and=255

    `rate`/`r` range is 0..15 (must be const)
    `lin`/`l` enables bilinear filtering (def=off) (must be const)
    `and`/`a` enables power-of-two wrap-around (def=off, 1..65535 otherwise) (must be const)
    `andf`/`af` logic-and read position with float converted to fixpoint (can be dynamic)
    `clamp`/`c` clamp source offset (always in float mode, def=1 in int mode) (must be const)

    tip: in order to create chords, synthesize the highest note to a zone, then in a following zone read and mix the previously rendered zone at different speeds / rates

    Minor chord example:

zone 0 singlenote_g3
<out:
zon 0
+ zon 0 d#3/g-3
+ zon 0 c-3/g-3

- 0.5

clp

    ## zsq
    ] sequence zone sample

    Sequence (other) zone sample.

    Can be used to create (multi-channel) drum loops. Example:

zone 0 bd
zone 1 sd
<out:

# 47 0 1 2 3 4 5 6 7

= zsq 0 xx……x….x.. note=16 bpm=125 rate=1
+ zsq 1 ….x…….x… bpm=125 rate=1.1 lin=1
clp

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

    tip: When the input zones (one-shot samples) are placed in the same samplebank as the sequenced loop, ticking the `Recalc All` checkbox (next to the `F11: Synth` button at the bottom of the screen) will cause all procedural zones to be recalculated when any synth patch changes. This makes it possible to tweak the input sounds and immediately hear the edits in the context of the loop.

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

    # 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 1 # millisec=0 (auto-fit sample size) start=0 end=1
  lut amp

    ] Using a curve LUT as an amp envelope

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

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

arg p_amp 0.7 0 8
arg pfegamt 0.2 0 4
curve freq
curve amp
<out:
sin
freq:
rmp 0 0 1
lut freq
pow 2
fma $pfegamt 1

- rmp 0 0 1
  lut amp
- $p_amp

clp

    ] curve auto enum test 1

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

# 48 (note) implicit var "x" stores normalized table position (0..1=last table element)

# 49 (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:

# 50 (note) implicit var "x" stores normalized table position (0..1=last table element)

# 51 (note) the default out value is $x

fma 2 -1
pow exp=$m_exp
<out:
sin
fma 0.5 0.5
lut pow lin=1

    ] per-voice modulated LUT example

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

    note: the default table size is 64
    note: the default table mode is `voice`
    note: LUT init sections must be named `lut_<id>` (e.g. `<lut_pow:`)
    warning: if no init section has been declared for a LUT used by a "lut" module, compilation will fail
    note: the shared LUTs are calculated after the `<init:` section
    note: the per-voice (or per-wavetable cycle) LUTs are updated after the `<prepare:` / `<wt_init:` section

    ## Inverse Lookup Tables
    See [#sta][] module documentation for examples.

    # 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 oscmixmacro_manual
name "osc mix macro manual"
author bsp
cat osc

# 52 Plugin parameter

param poscmix 0 0 1

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

# 54 calculate interpolated macro values

# 55 (updated 1000 times per second)

mac oscmix pos=$posc_mix
<out:

# 56 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 oscmixmacro_permutate
name "osc mix macro permutate"
author bsp
cat osc

# 57 Plugin parameter

param poscmix 0 0 1

# 58 auto-generated settings

# 59 (permutations of values 0/1/-1 ⇒ 3*3*3*3=81 settings)

macro osc_mix
sin tri saw pul
permutate 0 1 -1
<prepare:

# 60 calculate interpolated macro values

# 61 (updated 1000 times per second)

mac oscmix $posc_mix
<out:

# 62 calculate current output sample

sin

- osc_mix.sin

+ tri

- osc_mix.tri

+ saw

- osc_mix.saw

+ pul

- osc_mix.pul

    ] macro example (permutated settings)

    `pos` is in the range 0..1 (last setting is lerped back to first setting)

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

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

    # 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 maptest4
map mymap
freq vol cutoff # map field names

0. 0.0 1 0.2 0.8 # map region 1 starts at 0.0
1. 4.0 1.5 0.5 0.2 # map region 2 starts at 4.0
2. 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.

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

    tip: 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](#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](#init-prepare-calc-lanes)))

    ## 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 testtemplate2
modparam lvl1 0.75
modparam hdcy1 0.25
modparam lvl2 0.25
modparam hdcy2 0.125
def oscsinhdcy_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:
~oscsinhdcy1 1 $mhdcy1

- $m_lvl1

+ ~oscsinhdcy1 freq=2.5 hdcy=$mhdcy2

- $m_lvl2

    ] additive synthesis template example

    ## 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 # amcamp for id=mc
arg a%id%fb 0 # amcfb for id=mc
~myclass id=mc
<out:
mc.out

- $amcfb

+ tri freq=mc.freq

- $amcamp
  - 8

sto mc.out

- rmp 0 1

clp

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

    ## Audio lanes
    Template declarations may also include audio lanes.

    tip: all audio lanes are summed to generate the final output, see [#layers][]

id testtemplate4
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

- $mmyosc%%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

    ### 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 # amcfreq for id=mc1
arg a%%amp 1 # amcamp 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)

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

    ## 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 testtemplate5
inc oscsintri1lut
<out:
~oscsintri1lut
clp

    ] lut based template oscillator plugin

    # 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 testtemplate3
modparam m_lvl1 1
modparam m_lvl2 0
modparam m_trimix 0

# 63 the include file defines the template "osc_sin_tri_1"

inc oscsintri_1
<out:
~oscsintri_1 freq=1

- $m_lvl1

+ ~oscsintri_1 1.5

- $m_lvl2

ipl t=$m_trimix
b:
tri

- $m_lvl1

+ tri freq=1.5

- $m_lvl2

    ] include file example

    tip: 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)

    # 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

    note: sample zones referenced by modules will be embedded in the plugin (e.g. [#zon][], [#zlp][], [#ssl][])

    Example:

id fm
oversample 4
param pmodfreq 0.5 min=0 max=1
param pmodamt 0 0 1
param p_vsync 0 1 16
mod mmodfreq
mod mmodamt
<out:
sin
phase:
sin vsync=$p_vsync window=sin
freq:

# 64 0..1 ⇒ 1/16 .. 0 .. *16

$mmodfreq
fma 2 -1
bts 16

- $mmodamt

    ] simple 2-op FM plugin

    ## Plugin header
    Patches **must** declare a unique plugin id. Example:

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

    ## Parameters and modulations
    Plugins have "n" parameters (shared among all voices), and "n" mods (per-voice modulation).
    It is recommended to not exceed 8 parameters and mods.

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

    note: 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 + voice→mods;

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

    ### Params
    Parameters are always in the (normalized) range `0..1`.

    Parameter may have default (reset) values, which will be exported to the plugin:

param p_cutoff 0.9

    Normalized parameters are scaled to their (optional) min / max value range when they are accessed.

param p 0.5 1 16

    ] p will be scaled to range 1..16. the effective default value is 0.5*(16-1)+1=8.5

    Instead of `param`, its shortcut form `p` can be used instead:

p drv 0.5

    ] param shortcut syntax

    #### 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
paramvalues pangle 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)

    note: parameter preset values always refer to _unscaled_, normalize 0..1 values

    #### 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 ptune1 0.5 -1 1 group=tune
param ptune2 0.5 -1 1 group=tune
param ptune3 0.5 -1 1 group=tune
param ptune4 0.5 -1 1 group=tune
param ptune5 0.5 -1 1 group=tune

    ] parameter groups (excerpt from osc_hh_v1.cy)

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

id morethan8_params
param p_amp 1 0 1
def myosc id=0
param pmyosc%id%freq 0 0 1 section=myosc%id% group=tune
param pmyosc%id%amp 0.5 0 1 section=myosc%id% group=amp
param pmyosc%id%vsync 0 0 1 section=myosc%id% group=vsync
sin
freq:
$pmyosc%id%freq
fma 2 -1
bts 4 4
vsync:
$pmyosc%id%vsync
bts 1 16

- $pmyosc%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)

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

# 65 additive synthesis using 16 sine partials

# 66 the 'level' array defines the partial magnitudes

# 67

id arraytest1
name arraytest1
cat osc
author bsp

# 68 selects 'level' array variation 'level' (0..1 ⇒ variation 1..7..0)

param p_level 0 0 1

# 69 modulates 'level' array variation (per block)

mod m_level

# 70 connect curve 'level' to param 'p_level'

# 71 - array size = 16 elements

# 72 - num array variations = 8

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

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

# 74 "0" is the index of the curve (0..15) which defines the initial array element values for new plugin instances

# 75 (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 arrayparamelnametest2
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 arrayshortcuttest_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

    caution: 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

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

    caution: 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).

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

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

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

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

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

mod m_cutoff

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

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

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

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

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

    ### prepare
    The `<prepare:` section is evaluated before the main section(s), and after the (optional) `<init:` section.

    Heavy calculations, or calculations which do not need to be evaluated per sample frame should be placed in the `prepare` section.

    Code placed in this section will be executed once per processed block. This can be used to implement macro modulation that reads the current modulation variables and writes one or many (internal voice-state) variables.

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

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

# 76 declare param p_amp and mod m_amp

modparam detune 0.5 0 1
var vfreql smooth=1
var vfreqr smooth=1
<prepare:

# 77 calc detuned left / right channel oscillator frequencies

$m_detune
fma 0.1 -0.05

1. . neg
   + 1
   sto vfreql
   +1
   sto vfreqr

<out:
sin freq=$vfreql

- 0.7

wrl # write left channel output
sin freq=$vfreqr

- 0.7

wrr # write right channel output

    ] stereo sine oscillator plugin

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

    tip: 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

    ### 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
- @cyclenew // %cyid% voicenew() test code line
- @cycledelete // %cyid% voicedelete() test code line
- @cyclevoice // %cyid% voicet 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
- @cycleemit %cyio% *= %cyindrive%;
- @cycleemit if(%cycfsat% > 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.

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

    ### Module input and output

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

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

    tip: 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

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

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

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

    ### 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`):

- @cyclevoice jeringmodstatet %cyid%;

    ] je ringmod voice field instance

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

- @cyclenoteon voice→%cyid%.reset(voice→samplerate, voice→sr_factor);

    ] 303 filter voice field note on reset

    tip: `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).

    ### Extension modules

    #### x303
    ] 303-ish filter

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

    `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

    #### xdi
    ] diode waveshaper

    A diode wave shaper (based on JE's ringmod which is based on Julian Parker's [A simple model of diode-based ring-modulator](http://recherche.ircam.fr/pub/dafx11/Papers/66_e.pdf).

    `vb` forward voltage-bias (must be const)
    `vbvl` linear voltage threshold (must be const)
    `slope` linear voltage slope (must be const)

    #### xdly
    ] delay

    A delay.

    `t` normalized delay time (0..1 => 0..delay_buffer_size-1) (def=0.5)
    `fb` feedback amount (0..1) (def=0)
    `wet` dry / wet mix (def=0.5)

    #### xfs
    ] frequency shifter with built-in sin / cos oscillators

    A frequency shifter based on Squinky Lab's sql_bootyshifter.

    `freq` frequency shift (-f..f)
    `range` range in Hz (def=500)

    #### xfsxy
    ] frequency shifter with sin / cos inputs

    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.

    `x` sine input
    `y` cosine input

    #### xldf
    ] Moog-ish ladder filter

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

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

    #### xlrf
    ] Linkwitz-Riley LP/HP/AP filter

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`

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

    #### xrm
    ] four quadrant diode ring modulator

    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](http://recherche.ircam.fr/pub/dafx11/Papers/66_e.pdf).

    `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
    `apol` carrier polarity (0..2) (must be const)
    `bpol` modulator polarity (0..2) (must be const)
    `mode` 0=ring 1=sum 2=diff 3=min 4=max (must be const)

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

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

    ### Windows
    Please install MSYS2 <https://www.msys2.org/>, then (in MSYS2 bash console) type:

$ pacman -Syu
$ pacman -S base-devel gcc

    to update the package database and install the compiler environment.

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

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

    ### Linux
    On Debian or Ubuntu, type

$ sudo apt update
$ sudo apt install build-essential

Document created on 28-Nov-2024 10:18:37
].create