@codex-music/audio-kernels
Stock restricted-TypeScript DSP kernels, their ScriptC library profiles, and the rules kernel sources follow.
The stock DSP kernels: sources you can import, extend, or read for style. Each one is written in restricted TypeScript and paired with a ScriptC library profile, which is what lets the same file become both the JavaScript you run in a browser and the native archive a plugin links.
Dual-purpose by design
This package is two things at once, and understanding that resolves most confusion about it.
It is an npm workspace TypeScript module. Other packages import
@codex-music/audio-kernels for types, for a JS oracle to compare a compiled
build against, or as a plain fallback path. Subpath exports (./dsp-kernel,
./audio-engine-kernel, ./fx1-kernel) let you reach one kernel without
pulling the rest.
It is also ScriptC compilation input. Each profiles/*.profile.json names a
kernel as the entry graph for a native library build, declaring an ABI prefix,
init/collect/panic-sink symbols, and an export table mapping TypeScript function
names to C symbols with their parameter and return kinds:
{
"profile_format": 1,
"name": "scriptc-fx1-offline-batch",
"entry": "../../../apps/offline-batch/native-entry.ts",
"emission": "llvm",
"abi": {
"prefix": "scfx_",
"init_symbol": "scfx_init",
"sink_register_symbol": "scfx_set_panic_sink",
"collect_symbol": "scfx_collect",
"result_reset_symbol": null
},
"exports": [
{ "export": "renderFx1Variation", "symbol": "scfx_render_variation", "params": ["bytes", "f64", "u32"], "returns": "bytes" }
]
}
The abi.prefix is the same string your ParamSchema.kernelId carries and the
same one plugin.config.json declares as kernel.abiPrefix. Those three must
agree; the codegen refuses to run when they do not.
What ships
Three kernels are present today, each with a profile and a corresponding
build/<stem>/<stem>.lib.a archive after pnpm --filter @codex-music/audio-kernels build:
| Source | Profile | ABI prefix |
|---|---|---|
dsp-kernel.ts | vertical-slice-vector (+ -c variant) | scav_ |
audio-engine-kernel.ts | audio-engine-graph | — |
fx1-kernel.ts | fx1-chain (+ -c variant) | scfx_ |
The root export surfaces MonoVoiceKernel (a phase-accumulating sine into a
rational soft clip into a one-pole lowpass — the smallest stateful proof that
exercises ScriptC's static DSP-math path), renderAudioGraph,
renderFx1Variation (an FX-1 insert chain: immutable PCM source → distortion →
tone filter → feedback delay), and PCM helpers floatToPcm16,
renderTestPcm16, renderTestPcm16Partitioned, alongside the deterministic
test signal generators testFrequency, testGain, and testGate.
The osc1-plugin port — oscillator → ADSR → biquad → gain, fused into a
voice-subtractive kernel behind the scsub_ prefix — lands later.
apps/examples/osc-mini already references
profiles/subtractive-voice.profile.json, which stays dangling until it does.
Rules for kernel sources
Four constraints govern every file here, and they are not stylistic:
- No allocation inside
process(). Nonew, no literals that allocate, no growing. Delay lines, coefficient tables and wavetables areinit(sampleRate)work. Math.froundat stateful accumulations. Anywhere a value survives to the next sample — filter memory, delay feedback, envelope state — the explicit f32 boundary is what keeps WASM and native rounding identical. That is the mechanism behind matching output hashes, not a hope about IEEE 754.- Public fields only. No private state, no getters hiding computation. ScriptC lowers fields to a fixed struct layout shared by both backends.
init(sampleRate)instead of constructors. Setup is an explicit call the host makes, so the host owns the state and its lifetime.
The kernel sources are also deliberately import-free: no DOM, no Web Audio, no
Node, no Tone, no native-host imports. dsp-kernel.ts compiles into a native
library while the browser worklet runs JavaScript emitted from the same file,
and that only works if the file names nothing environment-specific.
The native-entry indirection
One wrinkle worth knowing before you write a profile. A profile's entry points
at an app's native-entry.ts, not at the kernel source directly — ScriptC
library mode names one module, and the app's thin entry re-exports the kernel's
functions under the names and signatures the profile's export table declares.
Those entry files import the kernel by relative path into src/, not by the
@codex-music/audio-kernels package specifier. ScriptC's --lib mode compiles
workspace packages statically or refuses, and this package's exports map points
at bare .ts sources with no compiled JS entry. Ordinary TypeScript consumers —
Vite apps, tsc --noEmit — use the package specifier normally.
For the authoring interface these kernels implement, see audio-contracts; for how a compiled kernel reaches a DAW, see plugin-shell-kit.