# Loading samples

You have an audio file (a kick drum, a vocal phrase, a synth bounce) and you want to use it in a Builder — in a `soundfile` node, in a sampler DSP. This section is the workflow. The sample-store + the round-trip from recording → sample also touches **FaustWave — Recorder** § 2.

## What FaustWave accepts

| Format | Notes |
|---|---|
| **.wav** | Full header parsing — channels / sample rate / duration are populated. The default for FaustWave's own recordings. |
| **.flac** | Bytes import fine; header parsing for channels / rate / duration is a TODO so those fields land as 0. |
| **.aiff** | Same as `.flac` — bytes-only for now. |

Hard limit: **100 MB per sample**. Bigger files are rejected at import; chop them externally first.

## How samples are addressed

FaustWave doesn't reference samples by path. **Every sample lives in the sample store keyed by its sha256 content hash.** Two consequences:

1. **Idempotent imports** — importing the same bytes twice is a no-op. The second import returns the existing sha and adds no new file. Drag the same WAV in 50 times; you have one sample.
2. **Portable documents** — a `.builder` references its samples by sha. Share the DSP with a friend who already has the sample → it just works. Share with a friend who doesn't → they get a "browse for this sha" prompt at compile, with the original filename + channel/rate metadata as hints.

> 🟦 **WHY SHA256?** Path-based references break the moment you rename a folder. Hash-based references survive folder reshuffles + cross-machine handoff. The cost (the sample's filename is no longer in the DSP URL) is borne by the metadata carry — the `.builder` records the original filename alongside the sha so the prompt is still recognizable.

## Three ways to import

### A) Drag-and-drop onto a Builder canvas

Drop the file onto the Builder canvas. If there's a `soundfile` node selected, the sample binds to it. If not, FaustWave adds a `soundfile` node, binds the sample to it, and places it at the drop position.

### B) The `samples.import` MCP action

```
samples.import { file_path: "<absolute OS path to your .wav/.flac/.aiff>" }
```

Returns the SampleEntry — `{ sha256, channels, sampleRate, duration, originalFilename, byteSize }`. Idempotent (same bytes twice = same entry).

### C) Hub install of a sample-pack

If you don't have the bytes locally but somebody published them to the Hub:

```
hub.install { item_id: "<sample-pack id from hub.search>" }
```

The install fetches the bytes + runs the same sha-keyed write path. The sample lands in your store ready to reference. See **FaustWave — Hub** § 3 for the publishing side.

## Using the sample in a graph

```
1. samples.import   { file_path: "<abs path to kick.wav>" }              → { sha256, … }
2. documents.add      { type: "builder", title: "Sample player" }          → module_id
3. builder.graph.node.add     { module_id, kind: "soundfile" }                     → sf node id
4. builder.graph.node.ref.set { module_id, node_id: <sf>, param: "sha256", value: <sha256> }
5. builder.graph.node.add     { module_id, kind: "output" }                        → out node id
6. builder.graph.connect      { module_id, source_node_id: <sf>, source_port: "audio_l",
                      target_node_id: <out>, target_port: "in_l" }
7. dsp.run          { module_id }
```

(Not a click-chain — step 1 needs the absolute OS path of YOUR audio file, the one place paths are expected: the sample import gate. Ask the assistant to run the sequence with a real file.) The `soundfile` node's `sha256` ref is the binding point: once `builder.graph.node.ref.set` lands, the Builder's compile includes the sample in its worklet bundle, and `dsp.run` plays it.

## The `soundfile` node is a player

Since the sample-hybrid round (2026-08-29) the node is a one-shot **player**, not a tape that runs once at patch start:

- **`trig_in`** restarts playback on every rising edge — wire `midi_key.gate` (one node per drum voice), `midi_note.gate`, or a `clock.tick`.
- **`freq_in` + `root_hz`** make the sample follow the played note like a sampler zone: the read rate is `freq / root_hz`. Measure the root with `analysis.sample` (the `strongestLowBinHz` field of its spectrum block) — do not trust the pack's key label. `root_hz` 0 = no tracking, plain `rate`.
- **Two read heads.** A retrigger starts the *other* head and crossfades to it in ~5 ms while the old tail runs out; the last 5 ms of the file fade to zero. Both were single-sample jumps before — measured as clicks on every retriggered open hat and at the hard end of a distorted 808 sample. See **FaustWave — Analysis** § *The click hunt*.
- Shape a tail with `adsr x mul` when the sample is longer than the note; nothing else is built (no loop, reverse or start offset — nobody asked).

The kit recipe is `midi_key → soundfile → mul(velocity) → gain → add …` per voice; the bundled *Around the World* project's `Sample Kit.builder` is seven of those summed.

## Browsing your sample store

```yaml
actions:
  - action_id: samples.list
    input: {}
    save_as: store
    label: "▶ Enumerate every sample"
```

Returns an array of SampleEntry — sha, original filename, channels, sample rate, duration, byte size. `samples.get { sha256 }` returns one entry's metadata (not the bytes — too heavy for MCP). `samples.remove { sha256 }` deletes (destructive — confirm before calling; DSPs referencing the sha will prompt-to-locate on next compile).

## Where the bytes live on disk

| OS | Path |
|---|---|
| **Windows** | `%APPDATA%\FaustWave IDE\samples\<sha>\<original-filename>.<ext>` |
| **macOS** | `~/Library/Application Support/FaustWave IDE/samples/<sha>/<filename>.<ext>` |
| **Linux** | `~/.config/FaustWave IDE/samples/<sha>/<filename>.<ext>` |

The folder structure (sha-prefixed) is what lets the import be idempotent + content-addressed.

## A common end-to-end task: record → re-import

The round-trip from synth performance to playable sample is one chain (full version in **FaustWave — Recorder** § 2):

1. Press Rec.
2. Play the synth via the Keyboard or Sequencer.
3. Press Rec to stop — `recorder.stop` returns the WAV path.
4. `samples.import { file_path: <path> }` — the recording is now sha-keyed in the store.
5. Drop a fresh `soundfile` node + bind by sha as in the chain above.

From "synth voice" to "playable sample slice" without leaving FaustWave.

## Common moves

- **"Use the kick I dragged in last week"** — `samples.list { search: "kick" }` narrows by filename; pick it by `filename` or `durationMs`, copy the sha, bind it to a fresh `soundfile` node via `builder.graph.node.ref.set`.
- **"Delete every old WAV I dragged in for testing"** — `samples.list`, filter by sha or filename, iterate `samples.remove`. No trash.
- **"Re-bind a soundfile in an existing Builder"** — `builder.graph.node.ref.set` with a new sha. The change is topology-relevant so the engine recompiles; the new sample plays immediately.

## Where to go from here

- **FaustWave — Builder** § 1 — wiring `soundfile` nodes into the rest of your graph (envelope, filters, FX).
- **FaustWave — Recorder** § 2 — the round-trip from recording → sample-store.
- **FaustWave — Hub** § 3 — publishing samples as `hub.publish kind: "sample-pack"`.
- **FaustWave — Getting Started** § *Your first recording* — the 5-minute starter flow.
