Skip to content

DSP Code

The code inside a dsp block’s functions is compiled for the audio thread. It uses the language’s syntax, but only the part of it listed here. What a block may declare around its functions is in DSP Blocks.

A construct outside this part is a dsp error when the block is defined, wherever it is — in the entry point, a helper, a dsp fn or an object method, called or not:

DSP error at /home/me/song/src/main.non:5:10: 'string' is not supported in DSP blocks

Every value is a number (a 64-bit float). true is 1 and false is 0. A condition is true when it is not 0.

FormRule
let x = value;A local. It cannot be assigned again.
name = value;Only for a state or voice state name.
name += value;+=, -=, *=, /= and %=, for the same names.
buf[i], buf[i] = value;, buf[i] += value;Only for a buffer name.

Assigning a local, a param or an argument is an error: cannot assign to 'x' (only state variables can be assigned).

RuleError when broken
Every if has an else.if/else in DSP blocks must have an else branch
Both branches give one value, or both give two.if/else branches must both be mono or both be stereo

Each sample, only the branch the condition picks runs. The other branch’s assignments and buffer writes do not happen, and its builtins with memory — a filter, a delay, a sample-and-hold — do not advance: they hold where they were until a sample takes their branch again.

if is also an expression: let g = if x > 0.0 { 1.0 } else { 0.0 };. Its value is the last statement of the branch that ran. A branch that ends with a let or a for gives no value, and using that if as a value is an error.

for i in range(N) { … } with N a number literal. The loop is unrolled when the block is compiled: N copies of the body, i being 0 to N - 1. A non-whole N is truncated.

Any other for is an error: for loops in DSP blocks must use range(N) with a constant. range(n) with a variable, range(start, end) and iterating anything but range all fail.

RuleError when broken
A return has a value.return in DSP blocks must have a value
A return is the last statement of its function, or the last of an if/else branch that is.return must be the last statement of the function
Every path through the entry point ends with a return.every path through the function must end with a return
The entry point returns one value per output channel: return (l, r); for stereo, return out; for mono.process function returns 1 value(s) but 2 output(s) declared
A pair (a, b) appears only in a return of the entry point. A helper, dsp fn or method returns one value.tuples are only allowed in return statements

A helper, dsp fn or method without a return gives the value of its last statement.

OperatorsResult
+ - * /Arithmetic. / by 0 is 0.
%Remainder, with the sign of the left side. % by 0 is 0.
== != < > <= >=1 or 0
&&, ||, !1 or 0, testing each side against 0. Both sides are always evaluated.
unary -, +Negation, identity
CallRule
__native("name", …)A DSP builtin. The name is a string literal; the argument count must match.
helper(…)A fn of the same block.
name(…)A dsp fn of the file, or one imported with use.
x.method(…)A method of a dsp object instance.

Helpers, dsp fns and methods are inlined where they are called, up to 16 calls deep; a function that calls itself is an error: inlining recursion limit exceeded for helper 'f'. Parameters are plain names, with no default values, rest parameters or types.

let x = Name(); makes an instance of a dsp object, with no arguments. It is written in the entry point’s body, outside any if or for.

__native("cc", n, ch), __native("pitchbend", ch) and __native("aftertouch", ch) take whole number literals: a controller 0–127, a channel 0–15.

ErrorCause
bare builtin call 'sin' — use __native("sin", ...) insteadA builtin called by its name.
__native first argument must be a string literal__native(name, …) with a variable name.
'tanh' expects 1 argument(s), got 2The wrong number of arguments for a builtin.
helper 'f' expects 2 arguments but got 1The wrong number of arguments for a helper.
'unknown function 'print'' is not supported in DSP blocksAny other function.

Each of these is an error, '<construct>' is not supported in DSP blocks:

ConstructName in the error
"text", f"…"string, format string
NUL, let x;nul
C4, [C4 E4], <…>, {…}note, pattern, alternating, polymetric
C4:maj in a patternchord
#[…], #{…}array literal, dict literal
matchmatch
loop, do N, break, continueloop, do, break, continue
fn(x) { … }anonymous function
assertassert
useuse
a.b, a.b = c, a method of anything but an object instanceproperty access, property assignment, method call
<<, >>output, route
...xs, f(name: value)spread, named argument
PLAY, PAUSE, STOP, RESET, RECORD, SEEKplay, pause, stop, reset, record, seek

Also rejected, with their own message:

ConstructError
let (a, b) = …;tuple destructuring (let (a, b) = ...) is not supported in DSP blocks; bind elements individually (e.g. let a = t[0];)
f(a)(b)chained call application (f(a)(b)) is not supported in DSP blocks; bind the intermediate result to a variable first
fn or class inside a functiona parse error: a definition is written at the top level
dsp fn soft(x) -> y {
return __native("tanh", x);
}
dsp effect Crush {
param steps: 16 range(2, 64);
state held: 0;
state count: 0;
fn process(left, right) -> (out_l, out_r) {
let mono = (left + right) * 0.5;
let step = if steps > 1.0 { 1.0 / steps } else { 1.0 };
count = (count + 1.0) % 4.0;
held = if count == 0.0 { __native("floor", mono / step) * step } else { held };
for i in range(2) {
held = held * 0.999;
}
return (soft(held), soft(held));
}
}
let crush = Crush("crush");