pole dspa language for audio, made by patchhaus
a language for audio, with a compiler that ships nothing.
Pole compiles a DSP patch to native code: an object file and a C header. Link them and the plugin you ship carries no JIT, no compiler, and no Pole library: the compiled code only calls the C math library. The same compiler also targets WebAssembly, which is why the patch below plays in this page.
processor Pluck
{
input stream float pitch;
input stream float gate;
output stream float out;
param float decay = 0.4f [0.05f, 2.0f];
param float tone = 0.4f [0.05f, 0.95f];
float phase = 0.0f;
float env = 0.0f;
float lp = 0.0f;
void main()
{
loop
{
if (gate > 0.5f) { env = 1.0f; }
else { env = env * (1.0f - 1.0f / (decay * 48000.0f)); }
let saw = phase * 2.0f - 1.0f;
lp = lp + tone * (saw - lp);
out <- lp * env * 0.3f;
phase = phase + pitch / 48000.0f;
if (phase >= 1.0f) { phase = phase - 1.0f; }
advance();
}
}
}the output
what it compiles to.
native
An object file and a C header. The header says how big the state block is, and Pole never allocates. Link it into a plugin and the plugin carries no compiler.
webassembly
An object and a JSON manifest that tells a browser everything the C header would have told C. Nothing on that side parses the header.
the same code
The exported object is the optimised module a JIT session would have run, so an exported plugin is not a port of what you were hearing. A bit-identity gate holds it to that.
native
how it runs in a plugin.
The browser is where Pole is easiest to try. The native object is what ends up in your plugin, and this is how it behaves there.
$ polec home-modules.pole --emit=export --out=home-modulespolec: wrote home-modules.o (7 functions, target arm64-apple-darwin25.0.0)polec: wrote home-modules.h$ wc -c home-modules.o 24904 home-modules.o$ nm -u home-modules.o_expf_sinf_tanf_tanhf$ export-check home-modules.pole OK identity: 1500 blocks of 64 frames, 3 side channels, 33 MIDI events, 24 parameter writes -- 0 block(s) differ, largest difference 0export-check: the plugin sounds like the editora real session, 2026-09-17 · export-check trimmed to its last two lines
compiled before it ships
polec turns the patch into machine code for the processor it runs on (Apple M1 and later, by default, on Apple silicon) before the plugin is built. Nothing compiles while it plays.
--emit=export → an object file and a C header
nothing but math
The compiled code calls a few math functions and nothing else. It never calls a memory allocator, takes a lock, or asks the operating system for anything while audio runs.
all 30 examples: libm only, three import nothing
state in one block
The generated code says how big its state is. The plugin allocates that once, when it loads, and hands the same block to every render call, so nothing grows while it plays.
reverb.h: POLE_REVERB_STATE = 10253 floats
what you heard is what ships
On every graph patch we test, the exported plugin plays the editor's samples bit for bit. export-check drives both with chords, retriggers, releases, and parameter moves, with wavetables bound, and compares every block.
30 graph patches, 0 blocks differ
measured, not estimated
1,944 bytes
a reverb, compiled.
polec --emit=export on examples/reverb.pole. The header beside it is 4.3 KB.
one
undefined symbol in it.
_expf, from the C math library the operating system already has. The compiled code needs nothing else at run time.
21–27 ms
to compile a patch.
Median of eleven runs each for gain, filter, synth, and reverb, to an object and a header.
Every figure here was produced by running the thing that produces it, on the commit this page was written from.
the browser against the compiler
24 of 29
examples render bit-identically in the browser runtime.
- adsr
- bounded_search
- delay
- filter
- fuzz
- gain
- modmatrix
- octave_up
- poly_graph
- poly_simple
- poly_synth
- poly_voices
- reverb
- saw_gate
- shaper
- sine
- soft_clip
- stereo_chorus
- stereo_width
- synth
- wavefolder
- wavetable
- wt_load
- wt_synth
- fft93 of 4,096 samples differ, by −150.5 dBFS at most
- fx_chain2 of 4,096 samples differ, by −156.5 dBFS at most
- spectral923 of 4,096 samples differ, by −138.5 dBFS at most
- wt_osc532 of 4,096 samples differ, by −138.5 dBFS at most
- feedback_lfoa feedback graph, which cannot run in a browser yet
- poly_eventsneeds notes, which the check does not play
Every example rendered by the compiler and by the browser runtime, then compared float for float by scripts/check_wasm.py. The four that differ all call sin, cos, exp, pow, or log, which the compiler takes from macOS’s maths library and the browser from musl’s.
platforms
where it runs.
| macOS | Linux | Windows | |
|---|---|---|---|
| compile, render, export | verified | verified | not built |
| WebAssembly output | verified | verified | not built |
| live audio and MIDI | verified | no backend | no backend |
“Not built” means exactly that: the compiler core has no platform headers and no reason not to build on Windows, but nobody has built it, so this table does not say it works. The platforms page has the detail.