# Two ways to build a DSP — Builder vs `.dsp`, version control, sharing

A **DSP** is the thing that makes sound: a compiled Faust worklet, running. Two kinds of document produce one, and FaustWave keeps them apart on purpose — a **Builder** (`.builder`) is a node graph you wire, a **Faust DSP** (`.dsp`) is source text you write. Both compile through the same libfaust pipeline and run as the same worklet; what differs is the editing surface and the file that ends up on disk.

That distinction runs through the whole product. The two documents are `builder` and `faust-dsp`, and the actions follow: `builder.*` edits a graph, `dsp.*` drives whatever is running — either kind. This section covers when to use which, how to evolve one, and how to share it.

## Builder vs `.dsp` — when to use which

| You're… | Use |
|---|---|
| Wiring effects + filters in a signal flow you can SEE | Builder (`.builder`) |
| Translating a Faust paper / online example into something runnable | `.dsp` |
| Iterating on a synth with a few oscillators + filter | Builder |
| Writing a custom Master FX slot | `.dsp` source pasted into `master.fx.slot.add` |
| Sharing with someone unfamiliar with Faust | Builder — they can SEE the structure |
| Sharing with a Faust dev who wants to fork the source | `.dsp` — they get text they can read + edit |

You can move between them: Builder canvas → *Show Faust source* → paste into a `.dsp` file. A `.dsp` with a `process = ...` body → `builder.import` it into an empty Builder (planned; manual paste-and-cleanup works today).

## The canonical Builder loop

The short round-trip from idea to running DSP:

```yaml
actions:
  - action_id: documents.add
    input: { type: builder, title: "My first Builder" }
    save_as: b
    label: "▶ 1. Spawn a Builder"
  - action_id: builder.graph.node.add
    input: { module_id: "{{b.module_id}}", kind: osc }
    save_as: osc
    label: "▶ 2. Add an oscillator"
  - action_id: builder.graph.node.add
    input: { module_id: "{{b.module_id}}", kind: lp }
    save_as: lp
    label: "▶ 3. Add a low-pass filter"
  - action_id: builder.graph.node.add
    input: { module_id: "{{b.module_id}}", kind: output }
    save_as: out
    label: "▶ 4. Add the output"
  - action_id: builder.graph.connect
    input:
      module_id: "{{b.module_id}}"
      source_node_id: "{{osc}}"
      target_node_id: "{{lp}}"
    label: "▶ 5. Wire osc → lp"
  - action_id: builder.graph.connect
    input:
      module_id: "{{b.module_id}}"
      source_node_id: "{{lp}}"
      target_node_id: "{{out}}"
      target_port: in_l
    label: "▶ 6. Wire lp → output"
  - action_id: builder.graph.node.param.set
    input:
      module_id: "{{b.module_id}}"
      node_id: "{{lp}}"
      param: cutoff
      value: 800
    label: "▶ 7. Cutoff = 800 Hz"
  - action_id: dsp.run
    input: { module_id: "{{b.module_id}}" }
    label: "▶ 8. Compile + run"
  - action_id: documents.save
    input: { module_id: "{{b.module_id}}" }
    save_as: saved
    label: "■ 9. Save to assets/"
```

> 🟦 **HANDS-FREE PATH**: a complete osc → lp → output graph, parametrised, compiled, played, and saved — all through MCP. The same eight `dsp.*` calls that drive the canvas drive the assistant here.

## The `.dsp` workflow

`.dsp` documents are plain text. Open one via Documents → Open from disk, or:

```
documents.add { ref: "asset:assets/<your patch>.dsp" }
```

Each `.dsp`:

- Has a `process = ...` definition (the audio chain).
- Compiles to a worklet on Run (same pipeline the Builder takes).
- Exposes Faust sliders / bargraphs as live params, accessible via `dsp.param.set` / `dsp.param.get` and the Inspector panel.

You can `import("stdfaust.lib");` at the top to pull in the Faust standard library plus everything mounted under custom `.lib` files.

### Compile vs Run

The `.dsp` editor's footer carries **two** buttons when stopped: **Compile** and **Run**.

- **Compile** (Hammer icon) — calls libfaust only. Reports syntax / semantic errors and stops there. Use when you want to validate a paste without starting audio.
- **Run** (Play icon) — compile + start the worklet. Requires the source to be a **generator** (`process = …`, no audio input). An FX-shape source (`process(in)` or `process(in_l, in_r)`) compiles cleanly but Run bails at start with a friendly error: *"DSP modules are generators with no audio input. For FX-shape sources use master.fx.slot.add or the Master panel's 'Add FX' picker."*

The Compile split lands clean when you paste a Builder-generated source — Builder graphs always emit `process(in_l, in_r) = …`, which is FX-shape. Use Compile to confirm the paste survived transit, then mount it into a Master FX slot (or back into an empty Builder via `builder.import` once that's wired) instead of trying to Run it standalone.

> 🔘 **MCP**: `dsp.compile`, `documents.list`, `dsp.run`, `dsp.stop`, `dsp.param.set`, `dsp.param.get` work on either kind — the Builder's graph and a `.dsp`'s text compile to the same worklet. See `palette.list { category: "DSP" }` for the full set.

## `documents.save` vs `builder.export`

Two write-to-disk paths that look similar but differ in one critical way:

| Action | What it does | When to use |
|---|---|---|
| `documents.save { module_id, path? }` | Writes the `.builder` to disk AND **links it to the module** so the project reopens it on next load. Without `path`: auto-place in the active project's `assets/`. | When building content that should persist in the project. |
| `builder.export { module_id, path? }` | Writes the `.builder` to disk (or returns the JSON if no `path`). Does NOT link to the module — the graph would reopen empty next session. | When sharing a one-off file. |

The trap: `builder.export` followed by `hub.publish` works, but next time you open the project the Builder is empty (no linked file). Use `documents.save` for project-resident documents, then either `hub.publish` directly with the saved file, or `builder.export` to a separate share location.

## Importing a `.builder`

```
1. documents.add      { type: "builder", title: "Import target" }      → module_id
2. builder.import { module_id, path: "asset:assets/<name>.builder" }   (or an absolute OS path for an external file)
```

`builder.import` accepts EITHER `path` (loads from disk + KB re-indexes it) OR `doc` (an in-memory BuilderDocument object / JSON string). Pass exactly one.

## Evolving one — version control without the ceremony

`.builder` and `.dsp` documents are **text / JSON files in your project's `assets/` folder**. That means git tracks them naturally:

```bash
cd ~/your-project-as-git-repo
git diff assets/my-saturator-lead.builder
```

Two patterns:

- **Per-project git repo** — your whole FaustWave project is a git repo. DSPs, samples (gitignore the hashed bytes if you don't want them in repo), sequencer state — everything.
- **One repo per instrument** — share an evolving Builder via a tiny repo + a `.builder` + a `README.md` describing what it does. Other users `builder.import` from disk.

Plus the Hub for one-click distribution when git isn't the right granularity.

## Sharing one

Three escalating channels:

1. **Send the file** — `.builder` or `.dsp`. Recipient opens it; if there are sample refs they don't have, FaustWave prompts on compile. Works for one-off shares.
2. **Push to a git repo** — best for "I'm iterating with one collaborator and I want diffs."
3. **Publish to the Hub** — `hub.publish { kind: "builder" | "dsp", file_path, slug, title, ... }`. Best for "anybody can install with one click." See **FaustWave — Hub** § 3.

## Templates from the Hub

Before starting from scratch, check the Hub:

```yaml
actions:
  - action_id: hub.search
    input: { kind: builder, q: bass }
    save_as: hits
    label: "▶ Look for bass templates"
  - action_id: hub.install
    input: { item_id: "{{hits.data.0.id}}" }
    label: "▶ Install the top hit"
```

Install a starter, fork it, evolve. Faster than learning the Builder from a blank canvas; you see how an existing graph puts the nodes together.

## Where to go from here

- **FaustWave — Builder** § 1 — the canvas + node-kinds + lifecycle.
- **FaustWave — Builder** § 3 — loading samples into a `soundfile` node.
- **FaustWave — Hub** § 3 — publishing them as `hub.publish kind: "builder"` / `kind: "faust-dsp"`.
- **FaustWave — Mixer & Master** § 2 — turning a `.dsp` body into a Master FX insert.
- **FaustWave — Node-Pack Authoring** — the Box-DSL syntax that pack-author `toBox` templates use, per-kind `imports` declaration, and paired-port stereo for community packs.
