The language

Types

float, int, and vectors of either — plus the conversion rule that has no exceptions.

Anyone about to mix a sample rate with a sample index.

There are two scalar types, float and int, and vectors of either. float<2> is a stereo sample as one value.

Read the type as part of the meaning#

Type or valueTypical useExample
floatAudio samples, Hz, seconds, gains and interpolation positions0.5f
intIndices, counts and discrete choices7
float<2>One stereo samplefloat<2>(left, right)
A comparison resultA condition for if or ?:gate > 0.5f

The suffix on 1.0f is worth noticing: it says that the literal is a float. The literal 1 is an int. A whole-number value can still be a float, just as “one second” and “one sample” are different meanings despite both using one. See conversions for call signatures and worked unit conversions.

No implicit conversion, ever#

Mixing float and int is POLE0307. There is no promotion rule to remember because there is no promotion.

Playground
let i = int(2.7f);      // 2, truncated toward zero (not floor)
let f = float(i);

int(-1.7f) is -1, while floor(-1.7f) is -2.0f. They differ, and confusing them is the classic off-by-one in a table lookup. int is 32-bit; a literal that does not fit is POLE0316.

Vectors#

Arithmetic on a vector is elementwise and compiles to one instruction, not a loop — a float<2> is an LLVM vector, so a + b on a stereo pair is a single fadd.

Playground
input  stream float<2> in;
output stream float<2> out;

let mid  = (in[0] + in[1]) * 0.5f;   // an element
let wide = in * 0.5f;                // a scalar broadcasts across the lanes
out <- float<2>(mid, mid);

The rules are narrow on purpose:

  • Widths must match, or one side must be a single element to broadcast (POLE0345). Broadcasting widens; it does not convert — an int against a float<2> is still POLE0307.
  • A constant element outside the width is an error (POLE0347), not the silent wrap an array index gets. A vector has no power-of-two masking to fall back on, and s[2] on a stereo signal is a mistake every time.
  • Comparisons are scalar (POLE0346): a per-element bool would have no if to feed.
  • float<1> is rejected — it is float spelled longer.
  • The first output is the audio endpoint. Use float<2> for stereo; subsequent outputs can carry side-channel data such as a meter or telemetry. See endpoints before choosing a layout.

Builtins are elementwise too#

A scalar broadcasts into them, so clamp(stereo, -1.0f, 1.0f) is one call with ordinary numbers for bounds rather than one call per lane.

min, max and clamp also take ints, and are the only builtins that do. Ordering and bounding are not float ideas, and clamping an integer index is exactly what an array read needs. Their arguments must agree — all int or all float (POLE0327). The rest stay float-only: sqrt of an int returning an int would imply a rounding rule nobody asked for.

The full list is on the built-in maths page, which is produced by asking the compiler rather than by transcription.

Vectors in state and at the edges#

A state array holds vectors: float<2> buf[1024] is a stereo delay line as one array rather than two that can drift apart. It occupies 2048 floats of state — the size is in elements, the storage is per lane.

A vector endpoint occupies one host slot per lane, so float<2> in followed by float gain puts gain at slot 2. --params, the export header and the graph wiring all agree about that without being told separately, because they all ask the same function.