Authoring a node-pack
A node-pack is a JSON manifest (.nodepack.json) shipping a set of Builder node-kinds. Install via the Hub (hub.install kind:"node-pack") or bundle with the IDE (packages/app/build-resources/bundled-node-packs/). Each pack carries packId, title, version, ownerHandle, description, and an array of kinds[].
A kind is one node in the picker: id + label + I/O ports + params + a toBox field describing how to compile the kind into Faust source. The toBox is the heart of the authoring surface — it's a tiny declarative DSL the Builder evaluates per node-instance, producing a Box AST that the generator emits as Faust DSL.
This pack covers the authoring surface end to end: classification (bundled vs Hub), Box-DSL syntax, per-kind library imports, paired-port stereo, the Sound-Quality Conventions Welle (smoothing / velocity / drift), and the publish workflow.
Pack classification — bundled vs Hub
Two tiers exist by design — see docs/NODE-PACK-ARCHITECTURE-ZIEL.md for the formal classification:
| Tier | What it is | Examples |
|---|---|---|
| Bundled (auto-restored) | stdfaust-library wrappers — generic Building Blocks. Each kind wraps one library_prefix.function from libfaust-wasm's bundled libs. | faust (70 kinds), faust-analysis, faust-reverbs, faust-physical-models, faust-spatial, faust-vintage |
| Hub | Community-created packs that build on top — composed Kinds combining multiple stdfaust primitives + opinionated defaults. | faust-vocal (Vocoder, Harmonizer, …) |
Exception: faustwave-conventions stays bundled despite being composed — the Coloration Kinds (tape / tube / transistor_drive / bus_glue) + PolyBLEP variants are the Sound-Quality Welle DNA of FaustWave, not third-party additions. The exception is the only one — every other community pack is Hub.
The classification matters because it shapes the author's mental model:
- Bundled-pack author: you're picking a stdfaust function (
os.osc,re.jpverb,fi.lowpass) and exposing its parameters as Faust sliders. MinimaltoBox, generally one or twolib()calls. - Hub-pack author: you're orchestrating multiple primitives into a domain-meaningful Kind (formant filter from three resonant bandpasses, vocoder from a bank of envelope-followed gains, etc.). Richer
toBox, often withdefsfor shared subexpressions.
The seven bundled packs at a glance
| Pack | Version | Kinds | What it wraps |
|---|---|---|---|
faust | v29 | 70 | The big stdfaust wrapper — oscillators (os.), filters (fi.), effects (ef.), envelopes (en.), math (ma., ba., si.), instruments (pm.) |
faustwave-conventions | v3 | 6 | Coloration (tape, tube, transistor_drive, bus_glue) + PolyBLEP variants (saw_polyblep, square_polyblep). The composed exception — see above. |
faust-analysis | v4 | 6 | analyzers.lib (an.*) — spectrum, FFT, mid-side goniometer, true-peak, Goertzel |
faust-reverbs | v3 | 7 | reverbs.lib (re.*) — Schroeder, Zita-Rev1, Dattorro Plate, Spring-Tank, Greyhole + variants |
faust-spatial | v2 | 3 | Paired-port stereo Kinds (mono_to_stereo, stereo_widener, lush_reverb_stereo) — Phase 4 / channels=2 case study |
faust-physical-models | v2 | 5 | physmodels.lib (pm.*) — struck instruments (marimba, bowl, bells, djembe) |
faust-vintage | v5 | 46 | tubes.lib (18 tube stages) + tonestacks.lib (25 amp tonestacks) + vaeffects.lib (3 wah pedals) — case study for per-kind imports |
If you're authoring a Hub pack, faust-vocal (4 composed vocal kinds) is the reference for the consume-multiple-primitives-into-one-Kind shape; it's not bundled but published on Hub.
Minimal kind anatomy
The smallest kind that compiles + audible is roughly:
{
"kind": "my_passthrough",
"label": "My Passthrough",
"category": "Effects",
"description": "Audio pass-through. Replace toBox to do something real.",
"bypass": { "input": "in", "output": "out" },
"inputs": [{ "id": "in", "label": "in", "kind": "audio" }],
"outputs": [{ "id": "out", "label": "out", "kind": "audio" }],
"params": [],
"toBox": {
"outputs": { "out": "input('in')" }
}
}
Drop that kind into a pack, install via hub.install (or write to userData/node-packs/<packId>/manifest.json and packs.node.restore for a bundled pack), and it shows up in the picker under the Effects category with a bypass toggle, coloured as an audio node because its output port carries audio. Wire audio → my_passthrough → output and you'll hear the input unchanged.
Every other field on a kind — params, defs, imports, drift_param, bodyStyle, paired-port channels: 2 — is additive on top of this minimum. The next section walks the Box-DSL syntax that turns toBox from a passthrough into anything Faust can express.