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 blocksValues
Section titled “Values”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.
| Form | Rule |
|---|---|
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).
if / else
Section titled “if / else”| Rule | Error 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.
return
Section titled “return”| Rule | Error 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.
Operators
Section titled “Operators”| Operators | Result |
|---|---|
+ - * / | 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 |
| Call | Rule |
|---|---|
__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.
| Error | Cause |
|---|---|
bare builtin call 'sin' — use __native("sin", ...) instead | A 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 2 | The wrong number of arguments for a builtin. |
helper 'f' expects 2 arguments but got 1 | The wrong number of arguments for a helper. |
'unknown function 'print'' is not supported in DSP blocks | Any other function. |
Not supported
Section titled “Not supported”Each of these is an error, '<construct>' is not supported in DSP blocks:
| Construct | Name in the error |
|---|---|
"text", f"…" | string, format string |
NUL, let x; | nul |
C4, [C4 E4], <…>, {…} | note, pattern, alternating, polymetric |
C4:maj in a pattern | chord |
#[…], #{…} | array literal, dict literal |
match | match |
loop, do N, break, continue | loop, do, break, continue |
fn(x) { … } | anonymous function |
assert | assert |
use | use |
a.b, a.b = c, a method of anything but an object instance | property access, property assignment, method call |
<<, >> | output, route |
...xs, f(name: value) | spread, named argument |
PLAY, PAUSE, STOP, RESET, RECORD, SEEK | play, pause, stop, reset, record, seek |
Also rejected, with their own message:
| Construct | Error |
|---|---|
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 function | a parse error: a definition is written at the top level |
Example
Section titled “Example”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");