# 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. 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

| 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:

```json
{
  "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.
