All books

Authoring a node-pack

~3 min read · updated 2026-08-23 · markdown

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:

TierWhat it isExamples
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
HubCommunity-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. Minimal toBox, generally one or two lib() 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 with defs for shared subexpressions.

The seven bundled packs at a glance

PackVersionKindsWhat it wraps
faustv2970The big stdfaust wrapper — oscillators (os.), filters (fi.), effects (ef.), envelopes (en.), math (ma., ba., si.), instruments (pm.)
faustwave-conventionsv36Coloration (tape, tube, transistor_drive, bus_glue) + PolyBLEP variants (saw_polyblep, square_polyblep). The composed exception — see above.
faust-analysisv46analyzers.lib (an.*) — spectrum, FFT, mid-side goniometer, true-peak, Goertzel
faust-reverbsv37reverbs.lib (re.*) — Schroeder, Zita-Rev1, Dattorro Plate, Spring-Tank, Greyhole + variants
faust-spatialv23Paired-port stereo Kinds (mono_to_stereo, stereo_widener, lush_reverb_stereo) — Phase 4 / channels=2 case study
faust-physical-modelsv25physmodels.lib (pm.*) — struck instruments (marimba, bowl, bells, djembe)
faust-vintagev546tubes.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.

Try it yourself — the IDE runs in your browser. Open the IDE → Get the desktop app

Text licensed under CC BY 4.0 — Mani Weber / FaustWave. For language models: llms.txt · llms-full.txt

AUDIO · 48k · 48.0ms FAUST · 3 KB · 1171 docs CPU · 8.4% BPM · 120.0 UTF-8 BETA· v0.90.0