1 Table of Contents
1
Table of Contents
2
Preface
3
Global Keywords
4
Values and sample output
5
Curve Lookup Tables (LUTs)
6
Procedural Lookup Tables (LUTs)
7
Macros
8
Output lanes
9
Constants
10
Args
11
Variables
12
Operators
13
Modules
13.1
abs - discard sign of previous output
13.2
bit - bit-crush previous output
13.3
boo - boost previous output
13.4
box - box filter previous output
13.5
bpf
13.6
bts - bipolar to scale, e.g. -1..1=>1/16..*1..*16
13.7
buf
13.8
bus - read sample from (cross-zone) voicebus
13.9
clp - clip previous output
13.10
dec - decrement variable
13.11
div - divide constant values
13.12
dly
13.13
drv
13.14
end
13.15
env
13.16
fld - fold previous output
13.17
fma - fused multiply add
13.18
frc - fractional part of previous output
13.19
fsr - linear feedback shift register noise
13.20
hbx - high-pass box filter
13.21
hld - sample and hold previous output
13.22
hpf - high-pass filter previous output
13.23
if - conditional code generation
13.24
inc - increment variable
13.25
int - emit sub-tree in integer mode
13.26
kbd - MIDI note keyboard tracking
13.27
lle - log / lin / exp scaling
13.28
log - calc logarithm of previous output
13.29
lop - loop statement sequence
13.30
lpf - 1RC low pass filter
13.31
lut - read from look-up table
13.32
mac - update interpolated macro fields
13.33
neg - negate previous output
13.34
nos - generate xor-shift white noise
13.35
pha - phase accumulator
13.36
p2s - power-of-two scaling
13.37
pow - raise previous output to the power of 'f'
13.38
pul - generate pulse wave
13.39
qua - quantize previous output
13.40
rep - loop / unroll statement sequence
13.41
rev
13.42
rmp - generate linear ramp
13.43
saw - generate sawtooth wave
13.44
set - set variable
13.45
sin - generate sine wave
13.46
slf - read past sample output
13.47
ssl - sine slice oscillator
13.48
sta - store variable in LUT
13.49
sto - store previous output in variable
13.50
svf - state-variable filter (lpf, hpf, bpf, notch)
13.51
tanh - saturate previous output
13.52
trc - debug-trace last output value
13.53
tri - generate triangle wave
13.54
vpl - invoke voice plugin
13.55
xfd - crossfade
13.56
zlp - read looped sample frame from other zone
13.57
zon - read sample frame from other zone
13.58
zsq - sequence zone sample
14
Plugins
2 Preface
Cycle
is a modular soft-synth which emits
TKS
or
C
sample generation code, or
STFX plugins.
It operates either in hi-fi (32bit float), or lo-fi (8bit signed integer) mode:
- 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) |
var | Declare variable |
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 |
|
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. |
Global Keywords
3.1 Plugin keywords
Keywords used in plugin-generation mode:
Keyword | Description |
name
| Plugin name
|
the plugin name (and dll / so / dylib) will be prefixed with cycle_ |
|
author | Plugin author |
cat
category
|
Plugin category
| unknown | limiter | pan | transient |
| ringmod | enhance | pitch | convolver |
| waveshaper | dac | chorus | mixer |
| filter | lofi | flanger | comparator |
| eq | gate | delay | utility |
| compressor | amp | reverb | osc |
|
Plugin Keywords
4 Values and sample output
The same value type is used for module parameters, args, variables, and intermediate sample data.
In hi-fi mode, values are represented by 32 bit floating point numbers. The final sample output is also a float
.
In lo-fi mode, values are represented by signed twos-complement, 16 bit fixed point numbers (4:11), i.e. the value range is -15..15.
The final sample output is a signed 8 bit sample.
5 Curve Lookup Tables (LUTs)
Cycle supports up to 16 cubic-bezier curve tables that can be used as amp or filter envelopes, or sample waveshapers.
Curve tables are internally converted (sampled) to short, power-of-two sized arrays. The array sizes can be configured in the UI.
Before a curve table can be used with a
lut module, it must first be declared with a
curve
statement:
curve 0 amp
<out:
sin
* rmp 0 0 0.99
lut amp
Using a curve LUT as an amp envelope
6 Procedural Lookup Tables (LUTs)
Cycle supports an arbitrary number of procedural look up tables which share the same namespace with curve lookup tables.
The table size must be a power of two.
Tables are calculated either per-voice (at note-on, using the then-current parameters and modulation), or statically (shared among all voices).
lut pow shared 256
<lut_pow:
# (note) implicit var "x" stores normalized table position (0..1=last table element)
# (note) the default out value is $x
fma 2 -1
pow 1.44
<out:
sin
fma 0.5 0.5
lut pow lin=1
shared LUT example
param p_exp 1 1 16
mod m_exp
lut pow voice 32
<lut_pow:
# (note) implicit var "x" stores normalized table position (0..1=last table element)
# (note) the default out value is $x
fma 2 -1
pow exp=$m_exp
<out:
sin
fma 0.5 0.5
lut pow lin=1
per-voice modulated LUT example
|
lookup tables can greatly improve the rendering performance by pre-calculating recurring values |
|
the default table size is 64 |
|
the default table mode is "voice" |
|
LUT init sections must be named "lut_<id>" |
|
if no init section has been declared for a LUT used by a "lut" module, compilation will fail |
|
the shared LUTs are calculated after the "<init:" section |
|
the per-voice (or per-WT cycle) LUTs are updated after the "<prepare:" / "<wt_init:" section |
6.1 Inverse Lookup Tables
See
sta module documentation for examples.
7 Macros
Macros are used to define smoothly interpolated value sets, controlled by a single parameter (or other value).
They are mostly useful to hide the internal complexity of a Cycle patch, and reduce the number of (plugin-)parameters.
The
mac module updates the current, interpolated values of the macro fields.
The current value of a macro field is accessed (read) using the <macro_id>.<field_name>
syntax.
Examples:
id osc_mix_macro_manual
name "osc mix macro manual"
author bsp
cat osc
# Plugin parameter
param p_osc_mix 0 0 1
# 4 manually configured settings
macro osc_mix
sin tri saw pul
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
<prepare:
# calculate interpolated macro values
# (updated 1000 times per second)
mac osc_mix pos=$p_osc_mix
<out:
# calculate current output sample
sin
* osc_mix.sin
+ tri
* osc_mix.tri
+ saw
* osc_mix.saw
+ pul
* osc_mix.pul
macro example (manual settings)
id osc_mix_macro_permutate
name "osc mix macro permutate"
author bsp
cat osc
# Plugin parameter
param p_osc_mix 0 0 1
# auto-generated settings
# (permutations of values 0/1/-1 => 3*3*3*3=81 settings)
macro osc_mix
sin tri saw pul
permutate 0 1 -1
<prepare:
# calculate interpolated macro values
# (updated 1000 times per second)
mac osc_mix $p_osc_mix
<out:
# calculate current output sample
sin
* osc_mix.sin
+ tri
* osc_mix.tri
+ saw
* osc_mix.saw
+ pul
* osc_mix.pul
macro example (permutated settings)
pos
is in the range 0..1 (last setting is lerped back to first setting)
|
the maximum number of (auto-generated) permutations is limited to 16384 |
|
alternative to macros, the <init: and <prepare: sections may also contain arbitrarily complex, procedural pre-calculations |
8 Output lanes
Lanes divide the script into multiple output sections.
Each script must contain the main section (named out
):
<out:
sin
8.1 Layers
When a script contains multiple lanes, they will be summed to create the final sample output.
This can be used for layered synthesis (lane == layer).
8.2 Init lane
A script may contain an optional init
section that is evaluated before the main section.
It can be used to implement procedural macro parameters that read args and write one or many variables.
arg angle 0 0 1
var v_sin
var v_cos
<init:
sin freq=0 phase=$angle
sto v_sin
sin freq=0
phase:
$angle
+ 0.25
sto v_cos
sin / cos init example
|
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) |
8.3 Prepare lane
The prepare
section is evaluated before the main section(s), and after the (optional) init
section.
Heavy calculations, or calculations which do not need to be evaluated per sample frame should be placed in the prepare
section.
id simple_2op_fm_osc
name "simple_2op_fm_osc"
author bsp
cat osc
param p_op2_ratio 0.5 0 1
param p_op2_level 0 0 16
mod m_op2_ratio
mod m_op2_level
var v_op2_freq
<prepare:
# ratio 0..1 => (1/4096)³ .. 1 .. 4³
$m_op2_ratio
# 0..1 => -1..1
fma 2 -1
# -1=>div 4096, +1=>mul 4
bts 4096 4
pow 3
# quantize to 100 fractional divisions per integer
qua 100
sto v_op2_freq
<out:
sin
phase:
sin freq=$v_op2_freq
* $m_op2_level
simple 2 operator FM / phase modulation oscillator plugin
|
in plugin-mode, the prepare section is called at a rate of 1kHz while a voice is playing. |
8.4 Wavetable Cycle Init lane
The optional wt_init
section is evaluated at the beginning of each wavetable cycle.
It can be used to implement procedural macro parameters that read the built-in $wt_x
/ wt_y
vars and write one or many variables.
arg angle_scl 1 0 15
arg angle_off 0 -1 1
var v_sin
var v_cos
<wt_init:
sin freq=0
phase:
$wt_x
* $angle_scl
+ $angle_off
sto v_sin
sin freq=0
phase:
$wt_x
* $angle_scl
+ $angle_off
+ 0.25
sto v_cos
sin / cos wavetable cycle init example
|
the wt_init section must occur before the first audio lane, and after the (optional) init lane |
9 Constants
Constants are typically used for static module parameters, e.g.
<out:
sin freq=1.5
A constant is either a floating point number (in the range -15..15), or a note ratio in the form of d#3/g-3
:
<out:
sin freq=d#4/c-3
10 Args
Args are named constants that are declared by the global keyword arg
(before any output section).
Arg values are editable in the (auto-generated) script UI (slider + value widgets).
Example:
arg lpf1_ms 30 min=0 max=500
arg lpf1_ms 30 0 500
arg lpf_rng 0.3
The default (implicit) min / max range is 0..1
Script code can reference args by prefixing the arg id with a $
sign, e.g.
arg amp_lvl 1.5
<out:
sin
* $amp_lvl
clp
|
args and variables share the same namespace |
11 Variables
Variables are very similar to args.
While they do
not generate UI widgets, their content may be updated at any time via the
sto module.
Script code can read variables by prefixing the var id with a $
sign, e.g.
var myvar
<out:
2.5
sto myvar
sin
* $myvar
clp
|
args and variables share the same namespace |
11.1 Built-in Variables
$x
stores the current, normalized position in procedural look-up tables (
lut)
$wt_x
stores the current, normalized horizontal (or 1D) wavetable position (0..1)
$wt_y
stores the current, normalized vertical (2D) wavetable position (0..1)
12 Operators
Each module may be prefixed by an operator that determines how its output is combined with the previous module's output.
Operator | Id | Description |
= | OP_REP | Replace |
+ | OP_ADD | Add |
- | OP_SUB | Subtract (a-b) |
r | OP_RSUB | Reverse-Subtract (b-a) |
* | OP_MUL | Multiply |
& | OP_AND | Bitwise and |
| | OP_OR | Bitwise or |
^ | OP_EOR | Bitwise exclusive-or |
m | OP_MIN | Minimum |
x | OP_MAX | Maximum |
M | OP_ABSMIN | Absolute minimum |
X | OP_ABSMAX | Absolute maximum |
. | OP_NONE | Discard / Ignore |
Operators
The default operator is replace
.
13 Modules
Modules generate new sample frames or process the output of the previous module.
Modules may be nested (stacked), i.e. module inputs can be controlled by the output of other modules.
Example:
<out:
sin
freq:
sin 5.0
* 0.25
+ 1
* 3.0
clp
Static module parameters are set by key=value
pairs, or simply by value (in internal parameter order)
lpf freq=0.4 res=0.5
lpf 0.4 0.5
Dynamic module parameters must be placed on separate, indented lines.
Please note that some module parameters cannot be dynamic (e.g. hpf
filter configuration).
Also keep in mind that dynamic parameters require more complex code that will lengthen the synthesis duration.
13.1 abs
Return absolute value of previous output.
Example:
<out:
-42
abs
13.2 bit
Bit-crush (shift) previous output.
Example:
<out:
sin
bit shift=0.5
bit 0.5
shift
range is 0..1
13.3 boo
Boost sample deltas of previous output.
Example:
<out:
sin
boo amount=0.6
boo 0.6
amount
range is 0..1
13.4 box
Box filter previous output.
Example:
<out:
saw
box freq=0.5
box 0.5
freq
range is 0..1
13.5 bpf
not implemented, yet
|
use svf in mode=2 instead |
13.6 bts
Bipolar to scale.
Example:
arg $freq
<out:
sin
freq:
$freq
# 0..1 => -1..1
fma 2 -1
# -1..1 => 1/16 .. *16
bts 16
div
range is 1..32 [def=8]
mul
range is 1..32 [def=8]
|
uses div input when mul is not connected |
|
only supported in float-mode (fall back to float in int mode) |
13.7 buf
not implemented, yet
13.8 bus
Read sample from (cross-zone) voicebus.
Example:
arg bus 0 0 0.32
arg lvl
<out:
sin
phase:
bus nr=$bus
* $lvl
phase-modulate sine wave by previous zone output
nr
is in the range 0..0.32 (bus 1..32, 0=previous zone nr)
|
only supported in STFX plugin mode (returns 0 otherwise) |
13.9 clp
Clip previous output
Examples:
<out:
sin
* 7
clp floor=-0.75 ceil=0.6
separate boundaries
ceil
range is 0..1 (default=1)
floor
range is -1..1 (default=-1) (when not connected, floor=-ceil)
13.10 dec
Decrement variable
var f
<out:
set f 0
rep 16
dec f 0.1
dec f delta=0.1
dec f
delta:
0.05
* 2
13.11 div
Divide constant values.
Example:
arg p_num 16 1 32
<out:
div 1.0 $p_num
|
useful in conjunction with rep / lop , e.g. when indexing LUTs |
13.12 dly
not implemented, yet
13.13 drv
not implemented, yet
13.14 end
Close current lane.
|
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 instead) |
13.15 env
not implemented, yet, may be removed
(use
rmp and
lut instead)
13.16 fld
Fold previous output value at ceiling.
Example:
<out:
sin
* 2.0
fld ceil=1.0
|
ceil sets min=-ceil, max=+ceil
|
Example:
<out:
sin
* 2.0
fld min=-1.0 max=1.0
|
min / max override default ceil
|
|
all oscillators can be used as wavefolders by setting their speed to 0, then modulating their phase input |
13.17 fma
Fused multiply add: out = out * mul + add
Example:
<out:
sin
# -1..+1 => 0..1
fma 0.5 0.5
mul
input is in range -15..15 [def=2]
add
input is in range -15..15 [def=-1]
13.18 frc
Calc fractional part of previous output (discard integer bits)
Example:
<out:
sin
* 3.3
frc
|
int does the opposite and discards all fractional bits |
13.19 fsr
Generate linear feedback shift register ("C64") noise (normalized to -1..1).
Example 1:
<out:
fsr
Example 2:
<out:
fsr seed=$4489 bits=1.0 cycle=1
seed
is the initial shift register state (16 bits). must not be 0. Default is $4489
bits
determines the number of output bits (1.0=all bits, 0=1 bit (actually two, sign+msb). Default is 1.0.
cycle
determines whether the shift register state resets at each (wavetable) cycle. Default is 1 (true).
shift1
is the first feedback shift (negative=shift left). -15..15. def=7
shift2
is the second feedback shift. -15..15. def=-9
shift3
is the third feedback shift. -15..15. def=13
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
|
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
|
13.20 hbx
High-pass Box filter previous output.
Example:
<out:
saw
hbx freq=0.5
hbx 0.5
freq
range is 0..1
13.21 hld
Sample and hold previous output.
Example:
<out:
hld rate=0.5
hld
range is 0..1
|
if the previous output uses complex calculations, it is more efficient to render these into a separate zone at a lower rate, then read the precalced values via zon |
13.22 hpf
High-pass filter previous output. Uses preconfigured, static cutoff/resonance configurations (biquad coefficients).
Example:
<out:
saw
hpf cfg=3
13.22.1 Filter configurations
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 |
13.23 if
Conditional code generation (compile time).
Syntax:
if <arg> [<,<=,==,!=,>=,>] <constant>
then:
..
else:
..
Example:
arg mod_waveform;
<out:
if mod_waveform == 0
then:
nos
else:
if mod_waveform < 0.33
then:
sin
else:
if mod_waveform < 0.66
then:
tri
else:
saw
13.24 inc
Increment variable
var f
<out:
set f 0
rep 16
inc f 0.1
inc f delta=0.1
inc f
delta:
0.05
* 2
13.25 int
Evaluate sub-tree in integer mode.
The previous output value is converted to 16bit fixpoint (1:4:11) before the sub-tree is emitted.
Afterwards the integer output value is converted back to float.
<out:
int
sin
|
in integer mode, no conversions take place and the sub-tree is emitted as usual |
13.26 kbd
Scales the input by 127 (normalized float to MIDI note), adds (current_note - 64 + (off * 12))
, clamps to 0..127, then converts the note to its corresponding frequency and divides it by the sample rate (back to normalized 0..1 range).
Useful for scaling filter cutoff frequencies (linear to exponential).
id cutoff_kbd_tracking_test
name "cutoff kbd tracking test"
author bsp
cat filter
param p_cutoff 0.5 0 1
param p_keyfollow 1 0 1
param p_octave_off 0 -4 4
<out:
svf
freq:
$p_cutoff
kbd $p_keyfollow $p_octave_off
filter cutoff keyboard tracking example plugin
kbd
is in the range -16..16 (keyboard follow amount)
off
is in the range -4..4 (octave offset)
scl
is in the range 0..16 (post scaling)
|
only supported in plugin / float mode (fall back to float within int-subtree) |
13.27 lle
Scale previous output by logarithmic(c<0)..linear(c=0)..exponential(c>0) factor
8
lle c=1.4
c
is in the range -f..f.
|
only supported in float-mode (fall back to float in int mode) |
13.28 log
Return logarithm of previous output.
8
log base=2
base
is in the range 1..f (must be constant). When input is not connected, return the natural logarithm.
|
only supported in float-mode (fall back to float in int mode) |
13.29 lop
Loop statement sequence. Supports dynamic loop iterators.
var i
var num
<out:
2.0
* 4.0
sto num
0.0
sto i
lop $num
+ 0.01
num
range is 0..1023
|
some modules (e.g. sin ) use variables to store their state. evaluating such modules in a loop will cause all iterations to use the same state. |
|
use rep to generate unrolled loops where each iteration will have its own state |
13.30 lpf
1RC low pass filter
<out:
saw
lpf freq=0.4 res=0.5
lpf 0.4 0.5
freq
range is 0..1
res
range is 0..1
13.31 lut
Use previous output as index for curve look-up table (LUT)
Example:
curve 0 amp
<out:
sin
* rmp 0 0 0.99
lut amp clamp=0 lin=0
clamp
must be either 0 (repeat lut) or 1 (clamp to last index)
lin
must be either 0 (nearest neighbour) or 1 (linear interpolation)
|
the lut module is often combined with a rmp module that generates the look-up index |
13.32 mac
The mac
module updates the current, interpolated values of a macro's fields.
See
macros for further information and examples.
pos
is in the range 0..1 (last setting is lerped back to first setting)
13.33 neg
Negate previous output
Example:
<out:
saw
neg
13.34 nos
Render white noise via xor-shift pseudo random number generator
Example:
<out:
nos seed=0x44894489 cycle=0
seed
is the random seed (a 32bit integer). the default seed is 0x3d9fb971
cycle
range is 0..1 (0=disable wavetable cycle reset, default=1)
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
13.35 pha
Generate phase output (0..1 normalized saw-wave).
Example:
curve 0 mywaveshape
<out:
pha freq=1.5 phase=0.25
lut mywaveshape
freq
range is 0.25..4
phase
range is 0..1 (start phase)
cycle
range is 0..1 (0=disable wavetable cycle reset, default=1)
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed
range is 0..1 (1=enable fixed frequency mode, default=0)
13.36 p2s
Return (pow(2, exp * prev_out) - 1.0) / (pow(2, exp) - 1.0)
.
Can be used for e.g. scaling filter cutoff frequencies (linear to exponential).
Example:
arg cutoff
<out:
svf
freq:
$cutoff
p2s 7
(non-kbd-tracked) exponential cutoff scaling
When the kbd
(and optionally the off
) input is connected and Cycle is in STFX plugin-generation mode, the module input follows the keyboard.
id cutoff_kbd_tracking_test_p2s
name "cutoff kbd tracking test p2s"
author bsp
cat filter
param p_cutoff 0.5 0 1
param p_keyfollow 1 0 1
param p_octave_off 0 -4 4
<out:
svf
freq:
$p_cutoff
p2s kbd=$p_keyfollow off=$p_octave_off
filter cutoff keyboard tracking example plugin
exp
is in the range 0..15
scl
is in the range 0..1
0⇒scl=(pow(2,exp-1)) (must be const)
kbd
is in the range -16..16 (keyboard follow amount)
off
is in the range -4..4 (octave offset)
|
only supported in float-mode (fall back to float in int mode) |
|
for keyboard tracking purposes, the kbd module is usually the preferred solution |
13.37 pow
Return previous output raised to the power of 2 or 3 (fast-path), or an arbitrary exponent (float-mode only).
Example:
<out:
sin
pow 3
exp
range is [2, 3] (int) or any float value
|
useful for bending rmp envelopes into non-linear shapes |
|
constant exponents 2 and 3 behave differently than arbitrary (or dynamic) exponents, i.e. -2²=+4 while e.g. -2^4=-16 |
|
arbitrary or dynamic exponents are only supported in float-mode (fall back to float in int mode) |
13.38 pul
Generate pulse wave
Example:
<out:
pul freq=1 phase=0 width=0.5 vsync=2.5
freq
range is 0.25..4
phase
range is 0..1 (start phase)
width
range is 0..1 (length of duty cycle)
vsync
range is 0.01..4 (virtual oscillator sync)
cycle
range is 0..1 (0=disable wavetable cycle reset, default=1)
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed
range is 0..1 (1=enable fixed frequency mode, default=0)
window
range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
|
windowed vsync is only supported in float-mode (ignored in integer mode) |
13.39 qua
Quantize float value to n fractional steps per integer.
<out:
sin
qua num=10
num
is in the range 1..n
|
useful for controllers, e.g. FM ratios |
|
when num=1, simply discard fractional bits (cast to integer) |
|
only supported in float-mode (fall back to float in int mode) (except when num=1) |
13.40 rep
Loop / unroll statement sequence. The number of iterations must be constant.
arg p_num 8 1 32
var i
<out:
1.0
sto i
0.0
rep $p_num
+ sin
freq:
$i
* 0.5
* 0.1
clp
num
range is 0..1023
13.41 rev
not implemented, yet
13.42 rmp
Generate linear ramp
Example:
<out:
rmp millisec=8 start=0 end=1 cycle=1
millisec
range is 0..4000. 0 auto-adjusts the duration to match the target zone sample length
start
range is 0..15 (start value)
end
range is 0..15 (end value)
cycle
determines whether the ramp position is reset at each new wavetable cycle (0 or 1)
13.43 saw
Generate sawtooth wave
Example:
<out:
saw freq=1 phase=0 vsync=2.5
freq
range is 0.25..4
phase
range is 0..1 (start phase)
vsync
range is 0.01..4 (virtual oscillator sync)
cycle
range is 0..1 (0=disable wavetable cycle reset, default=1)
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed
range is 0..1 (1=enable fixed frequency mode, default=0)
window
range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
|
windowed vsync is only supported in float-mode (ignored in integer mode) |
13.44 set
Set variable
var f
<out:
set f 1.23
set f value=1.23
rep 16
inc f 0.1
13.45 sin
Generate sine wave
Example:
<out:
sin freq=1 phase=0 vsync=2.5 window=1
freq
range is 0.25..4
phase
range is 0..1 (start phase)
vsync
range is 0.01..4 (virtual oscillator sync)
cycle
range is 0..1 (0=disable wavetable cycle reset, default=1)
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed
range is 0..1 (1=enable fixed frequency mode, default=0)
window
range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
|
windowed vsync is only supported in float-mode (ignored in integer mode) |
13.46 slf
Read past sample output ("self" feedback)
<out:
pul
+ slf rate=0.98
clp
rate
range is 0..1
13.47 ssl
Sine slice oscillator.
Works similar to how the additional 7 waveforms on the Yamaha TX81Z were created back in the 1980ies.
The (built-in) sine table as well as the destination waveform(-cycle) is divided into 8 regions / slices.
Each target slice is assigned one of the 8 sine regions (0..7).
<out:
ssl 01234567 freq=1.0 phase=0.0 mode=4 mod=0 seq=0 seqmod=0 modmask=1.0 zeromask=0.0
The first argument is the base slice sequence (0=region 0, 7=region 7).
When the sequence has less than 8 entries, it will be repeated.
freq
is in the range 0.25..4
phase
is in the range 0..1
mod
is in the range -1..1 (slice modulation)
seq
is in the range 0..1 (0=base slice sequence, 00000001..7777777). Must be const.
modmask
is in the range 0..1 (0=no slices, 1=all slices)
zeromask
is in the range 0..1 (0=no slices cleared, 1=all slices cleared)
mode
is in the range 0..5 (must be const):
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
is in the range 0..3 (per-sample sequence modulation)
13.48 sta
Store variable in LUT at normalized index determined by previous output value.
Syntax: sta <lut_id> <index_mul> <index_add> <src_var> <var_mul> <var_add>
Example:
lut ws shared 4096
lut ws_inv shared 4096 inv
<lut_ws:
# transform implicit input x={0..1} => {-1..1}
fma 2 -1
pow 3
# store in inverse LUT 'ws_inv'
# - transform 'pow' result (-1..1) into normalized index (0..1)
# - transform x from 0..1 to -1..1 and store in LUT
sta ws_inv 0.5 0.5 x 2 -1
# store 'pow' result in LUT 'ws' (done by auto-generated code)
# (note) holes in the ws_inv LUT will be auto-filled afterwards
<out:
sin
# -1..1 => 0..1
fma 0.5 0.5
lut ws
lpf freq=0.3 res=0.5
clp
# -1..1 => 0..1
fma 0.5 0.5
lut ws_inv
inverse exponential lookup table
Example 2:
curve 0 ws
lut ws_inv shared 2048 inv
<lut_ws_inv:
# use implicit input x (0..1) as index into bezier curve 'ws'
lut ws
clp
# store in inverse LUT 'ws_inv'
# - transform 'clp' result (-1..1) into normalized index (0..1)
# - transform x from 0..1 to -1..1 and store in LUT
sta ws_inv 0.5 0.5 x 2 -1
# don't write output value to lut (already written)
end
<out:
sin
# -1..1 => 0..1
fma 0.5 0.5
lut ws
lpf freq=0.3 res=0.5
clp
# -1..1 => 0..1
fma 0.5 0.5
lut ws_inv
inverse bezier curve lookup table
|
referencing an inverse LUT in another LUT's init lane via sta causes the inverse LUT to be filled before the init lane is executed, and potential holes (unwritten elements) in the inverse LUT to be closed afterwards |
13.49 sto
Store previous output in variable
Example:
var t
<out:
sin
sto t
* $t
* $t
The variable is initialized with 0 but retains its content over multiple sample frame iterations.
Example:
var sin_val
<out:
$sin_val
+ sin
sto $sin_val
clp
13.50 svf
Process previous output with state-variable filter
Example:
<out:
saw
svf freq=0.4 res=0.5 mode=0
svf 0.4 0.5 0
freq
range is 0..1
res
range is 0..1
mode
0=lpf, 1=hpf, 2=bpf, 3=notch
13.51 tanh
Process previous output through tangens hyperbolicus (tanh) function.
Example:
arg drive 0 -1 1
var v_amp
<prepare:
# amp=/10..*10
10
pow $drive
sto v_amp
<out:
sin
* $v_amp
tanh
tanh
arg drive 0 0 2
lut tanh shared 4096
<lut_tanh:
# 0..1 => -100..+100
fma 200 -100
tanh
var v_amp
<prepare:
# amp=*1..*100
10
pow $drive
sto v_amp
<out:
sin
* $v_amp
# -100..+100 => 0..1
fma 1/200 0.5
lut tanh lin=1
tanh lookup-table
|
useful for saturation-like non-linear distortion (converges to -1..1) |
|
only supported in float-mode (fall back to float in int mode) |
13.52 trc
Debug-trace last output value (print to console).
Example:
lut mylut shared 16
<lut_mylut:
# print current $x
trc
<out:
sin
13.53 tri
Generate triangle wave
Example:
<out:
tri freq=1 phase=0 vsync=2.5
freq
range is 0.25..4
phase
range is 0..1 (start phase)
vsync
range is 0.01..4 (virtual oscillator sync)
cycle
range is 0..1 (0=disable wavetable cycle reset, default=1)
reset
range is 0..1 (0=disable plugin voice note on reset, default=1)
fixed
range is 0..1 (1=enable fixed frequency mode, default=0)
window
range is 0..3 (0=windowed vsync off, 1=sine window, 2=half-sine window, 3=triangle window) (must be const)
|
windowed vsync is only supported in float-mode (ignored in integer mode) |
13.54 vpl
Process previous output via
stfx voice plugin.
<out:
sin 5.0
vpl "bsp ws quintic" "Dry / Wet"=0.5 Drive=0.39
Example (single line)
arg drive 0.39 min=0 max=1
<out:
sin 5.0
vpl "bsp ws quintic" "Dry / Wet"=0.5 "Drive"=$drive
Example (singe line with arg slider)
arg drive 0.39 min=0 max=1
<out:
sin 5.0
vpl "bsp ws quintic"
"Dry / Wet" = 1
"X^0" = 0
"X^1" = 0.5
"X^2" = 0.74
"X^3" = 0.8
"X^4" = 0.62
"X^5" = 0.5
"Drive" = $drive
Example (multi line with arg slider)
In Lo-Fi (integer) mode, the input/output values are temporarily converted to / from float.
|
See Eureka sampler "FX" tab for a list of available plugins. |
|
Plugin setup can be exported to single- or multi-line Cycle script (clipboard) via Eureka sampler "FX" tab context menu |
|
vpl modules cannot be exported to "C" source
|
13.55 xfd
Crossfade between previous output and input b
.
arg mod_waveform 0 -1 1
<out:
tri
xfd amount=$mod_waveform
b:
saw
Example (crossfade between triangle and saw)
13.56 zlp
Read looped sample frame from other zone. Can be used to mix samples or create chords.
Example:
zone 0 otherzone
zone 1 othersmp.otherzone
<out:
zon 0 rate=0.3 phase=0.5
+ zon 1 0.9
clp
rate
range is 0..1
phase
range is 0..1
|
sample len is determined by last loop length of the specified zone |
|
sample len should be a power of two (read offset is ANDed with (sampleLen-1)) |
13.57 zon
Read sample frame from other zone. Can be used to mix samples or create chords.
Example:
zone 0 otherzone
zone 1 othersmp.otherzone
<out:
zon 0 rate=0.3
+ zon 1 0.9
clp
Upsampled loop example:
zone 0 loopzone
<out:
zon 0 rate=0.5 lin=1 and=255
rate
range is 0..1 (must be const)
lin
enables bilinear filtering (def=off) (must be const)
and
enables power-of-two wrap-around (def=off, 1..65535 otherwise) (must be const)
|
in order to create chords, synthesize the highest note to a zone, then in a following zone read and mix the previously rendered zone at different speeds / rates |
Minor chord example:
zone 0 singlenote_g3
<out:
zon 0
+ zon 0 d#3/g-3
+ zon 0 c-3/g-3
* 0.5
clp
13.58 zsq
Sequence (other) zone sample.
Can be used to create (multi-channel) drum loops. Example:
zone 0 bd
zone 1 sd
<out:
# 0 1 2 3 4 5 6 7
= zsq 0 xx......x....x.. note=16 bpm=125 rate=1
+ zsq 1 ....x.......x... bpm=125 rate=1.1 lin=1
clp
note
range is 1..256 (step note duration) (must be const)
bpm
range is 0..1000 (<10: use project bpm) (must be const)
swing
range is -0.5..0.5 (must be const)
rate
range is 0.125..8 (must be const)
lin
range is 0..1 (>=0.5: enable bilinear filtering, def=0) (must be const)
div
range is 0..256 (divide source zone into n regions) (must be const)
off
range is 0..1 (select sub-divided source region, e.g. $wt_x
)
|
When the input zones (one-shot samples) are placed in the same samplebank as the sequenced loop, ticking the Recalc All checkbox (next to the F11: Synth button at the bottom of the screen) will cause all procedural zones to be recalculated when any synth patch changes. This makes it possible to tweak the input sounds and immediately hear the edits in the context of the loop. |
|
rate can (as usual) also be expressed as a note ratio, e.g. c-3/d#3
|
14 Plugins
Cycle patches can also be exported as native code (C/C++) voice plugins.
The following restrictions apply:
- no references to sample zones (e.g.
zon
, zlp
, zsq
, slf
, ssl
)
- no references to voice plugins (
vpl
)
dur
, wt_w
, wt_h
, wt_cyclelen
, wt_freqs
, skip
, xfade
, mirror
, zone
keywords are not allowed
Patches must declare a unique plugin id. Example:
id myplugin_v1
name "my plugin v1"
author bsp
cat osc
14.2 Parameters and modulations
Plugins have "n" parameters (shared among all voices), and "n" mods (per-voice modulation).
It is recommended to not exceed 8 parameters and mods.
|
parameter names may be prefixed with p_ (prefix will be removed during export) |
|
mod names may be prefixed with m_ (prefix will be removed during export) |
If a mod has a parameter of the same name (ignoring the optional prefix), the effective mod value is the sum of the shared and mod values.
float modcutoff = shared->params[PARAM_CUTOFF] + voice->mods[MOD_CUTOFF];
auto-generated code example
Frequency, parameters, and modulation will be interpolated over processing blocks. The block update rate (in Eureka) is 1000Hz, i.e. a processing block consists of e.g. 48 sample frames at a sample rate of 48kHz.
14.2.1 Params
Parameters are always in the (normalized) range 0..1
.
Parameter may have default (reset) values, which will be exported to the plugin:
param p_cutoff 0.9
Normalized parameters are scaled to their (optional) min / max value range when they are accessed.
p will be scaled to range 1..16. the effective default value is 0.5*(16-1)+1=8.5
14.2.2 Mods
Mods are (usually) in the range -1..1
.
Mods have an implicit default (reset) value of 0.
mod m_cutoff
14.2.3 Args and envelopes
(Regular) args and curves (spline envelopes) will be baked into the plugin source.
The initial value is the output of the previous sound module (voice plugin, sample, VST effect, ..).
id my_amp
name "my amp"
author bsp
cat amp
param p_level 1
mod m_level
<out:
* $m_level
simple "amp" plugin
14.2.5 init
The <init:
section can be used to implement macro parameters that calculate additional internal (voice state) variables from user defined parameters. Code placed in this section will be executed everytime a new note is played.
14.2.6 prepare
The <prepare:
section is evaluated before the main section(s), and after the (optional) <init:
section.
Heavy calculations, or calculations which do not need to be evaluated per sample frame should be placed in the prepare
section.
Code placed in this section will be executed once per processed block. This can be used to implement macro modulation that reads the current modulation variables and writes one or many (internal voice-state) variables.
14.2.7 Free runnning oscillators
By default, the phase and noise generator states reset when a new note is played.
The sin
, tri
, saw
, pul
, pha
, fsr
, nos
modules have a (constant) reset
input that determines whether the reset occurs at note on (reset=1
, default), or just once when the voice is allocated (reset=0
). The cycle
input should be set to 0 (cycle=0
).
Example:
<out:
fsr cycle=0 reset=0
free-running linear feedback shift register noise oscillator
14.3 Export
Press lctrl-p
in the Cycle dialog to export the current patch as a plugin (and (re)-load it).
Press lctrl-lshift-p
to toggle auto-export when patch is saved / edited (lctrl-s
or after arg changes).
14.4 Compiler installation
Cycle uses Clang / GCC to compile the plugins.
No knowledge of C/C++ is required to create new plugins but the following (free) build tools have to be installed so the application can invoke them in the background.
14.4.1 Windows
$ pacman -Syu
$ pacman -S base-devel gcc
to update the package database and install the compiler environment.
|
In case MSYS2 was not installed to its default directory, edit eureka_config.tks and update the stfx_cc_msys2_dir variable accordingly. |
14.4.2 macOS
Please install brew
https://brew.sh/ (which will also install the XCode commandline tools):
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
14.4.3 Linux
On Debian or Ubuntu, type
$ sudo apt update
$ sudo apt install build-essential
Document created on 19-Aug-2023 10:18:05