The language

Operators

Arithmetic, comparison, logic and bitwise — with C's precedence and no float bit-twiddling.

Anyone writing an expression longer than one term.

+ - * /                       arithmetic, elementwise on vectors
< > <= >= == !=               comparison, scalar only, produces bool
&& ||                         logical, on bool
& | ^ << >>                   bitwise, on int

Precedence follows C: || loosest, then &&, |, ^, &, comparison, shifts, + -, * /.

Expressions describe the signal path#

Multiplication changes gain. Addition mixes signals. Division often converts units, such as Hz to cycles per sample. Give those steps names when an expression becomes hard to read: let wet = ...; tells the reader what the result represents before it reaches the final mix.

Playground
let dry = in;
let wet = tanh(in * 3.0f) * 0.5f;
let mix = 0.25f;
out <- dry * (1.0f - mix) + wet * mix;

Here 75% of the dry sample and 25% of the wet sample are added. The parentheses around 1.0f - mix keep the whole difference together as a gain. Without them, dry * 1.0f - mix would subtract an offset from the signal instead.

A conditional expression chooses a value: condition ? whenTrue : whenFalse. For example, in > 0.0f ? in : 0.0f keeps only the positive half of the signal. Both alternatives must have compatible types.

Bitwise operators are int-only#

There is no meaning to a float's bit pattern that a DSP program should be reaching for, and reinterpreting one silently is how a denormal becomes a huge integer. Trying is POLE0355.

Comparison is scalar#

A comparison produces a bool, and vectors do not compare (POLE0346) — a per-element bool would have no if to feed. Compare elements instead:

Playground
input stream float<2> in;

let loud = in[0] > 0.5f || in[1] > 0.5f;
if (loud) { out <- 0.0f; } else { out <- in[0]; }

There is no modulo#

% is not in the language. For a power-of-two wrap, the array index already masks; for a phase wrap, subtract:

Playground
float phase = 0.0f;

phase = phase + 0.01f;
if (phase >= 1.0f) { phase = phase - 1.0f; }
out <- phase;

Subtracting once is what a phase accumulator wants anyway: it is one compare and one subtract, where a modulo is a division.