# Effects and boards people can share

**Status: effects and boards play everywhere.** The browser (Chirp Studio), the desktop tool, the CLAP plugin and the pedal compile effect files with one freestanding compiler (`core/graph_build.h`) and run them with one interpreter (`core/graph.h`). A board can have one split with two lanes; a lane with no effects is the dry sound, and a lane whose effects are all off fades out beside it rather than doubling the dry sound.

An effect in Chirp is an algorithm anyone can open, change and pass on, and a board is a set of them
ready to play. The same file plays in the browser, the plugin and the pedal. The design borrows from
the tools that do this well (see "Where the ideas come from" below).

## Three layers

| Layer | What you see | Who works here | File |
| --- | --- | --- | --- |
| **Board** | Effects in a row, or split into parallel lanes; snapshots; what each footswitch does | Everyone: this is what gets shared and played | `forge.board/0` |
| **Effect face** | A name, up to 6 knobs and 2 menus, and a display, like a pedal | Everyone: turn knobs, save settings | `forge.graph/0`, `face` |
| **Effect inside** | Blocks, cables between them, modulation dragged onto parameters | Builders, when they choose to open it | `forge.graph/0`, `nodes`, `mods` |

## An effect: `forge.graph/0`

```json
{
  "format": "forge.graph/0",
  "name": "Tape Echo", "author": "Chirp", "license": "CC-BY-4.0",
  "remixed_from": null,
  "face": {
    "knobs": [ { "id": "tone", "label": "Tone", "default": 0.45,
                 "controls": [ { "target": "echo.cutoff", "from": 700, "to": 15839, "curve": "exp" } ] } ],
    "menus": [ { "id": "path", "label": "Feedback", "options": ["Clean", "Saturate"], "default": 1,
                 "controls": [ { "target": "path.choice" } ] } ]
  },
  "nodes": {
    "line": { "block": "delay", "set": { "glide": 250 }, "in": { "x": ["in", "fb"] } },
    "echo": { "block": "onepole", "in": { "x": "line" } },
    "flutter": { "block": "lfo", "mono": true, "set": { "rate": 6.5 } }
  },
  "mods": { "wow": { "from": "flutter", "target": "line.time", "amount": 0.0007 } },
  "out": "echo"
}
```

**The face.** Each knob is a *macro*: it turns any number of things inside, each over its own range
(`from` at the knob's minimum, `to` at its maximum) and curve (`lin`, `exp` for times and
frequencies, `square` for depths that should start gently). A target is a node's parameter
(`echo.cutoff`) or a modulation's amount (`mods.wow`). A menu sets its targets to the option's
number, or to the option's entry in `values` (`{"target": "line.glide", "values": [60, 250]}`).
Knob values are 0 to 1, and `default` is where the knob starts.

**Labels fit the pedal's screen.** It shows the knobs in two rows of three, as the encoders sit, with
7 characters of each label, and the first menu's choice beside the effect's name, in 8. So a knob
label is at most 7 characters, a menu choice 8, a menu label 11 and a name 23, and the checker
refuses longer ones rather than letting the screen cut them mid-word. Use the short words pedals
print: Repeats (not Feedback), Reso, Thresh, Pre-dly, Hi Cut, Sens; 1/8T for a triplet and
1/8 dot for a dotted note. A face has at most 6 knobs and 2 menus of 2 to 4 choices.

**The display.** A face can show one signal from inside on the pedal, the way a tremolo's rate
light or a compressor's gain-reduction meter does. Its `source` is any block's output, named as a
cable is (`lfo:sine`, `steps:gate`, `shape.L`), so whatever an effect computes can be watched:

```json
"display": { "kind": "trace", "label": "Wave", "source": "lfo:sine", "range": [-1, 1] }
```

- **`kind`**: `lamp` (a light that glows with the signal, its last moments as fading dots),
  `meter` (segments, each reading the highest since the last) or `trace` (the last 1.5 seconds as
  a line).
- **`range`**: the readings that show empty and full (default `[0, 1]`). It can run backwards: the
  Compressor's `[1, 0.5]` fills as its gain drops from 1 to half (-6 dB).
- **`label`**: at most 7 characters, as a knob's.
- The engine reads the signal every 16 samples (into `EffectDisplay` in `core/effect.h`, the same
  history the built-in templates fill for theirs), so a display costs next to nothing; a block that
  only the display needs runs (and counts in the effect's budget) like any other.
- Some blocks have an output just for this: a `gate` block's `open` (0 shut, 1 open) lights the
  Noise Gate's lamp. A `rhythm` block of two steps, the first lit, with its rate set per beat by the
  same menu that sets the echo's time, blinks at the delay time: the tempo echoes' time light.
- Distortion is antialiased: every `clip` curve (and `saturate`) outputs the curve averaged
  between samples (first-order antiderivative antialiasing), and `clip_2x` adds twice the rate on
  top. The `diode` block is a circuit (a resistor into two diodes, through the diode equation),
  antialiased the same way; `tonestack` is an amp's treble-bass-middle network worked out from its
  parts; `ramp` is a modulation source that rises, falls or bounces, once or again and again;
  `octave_down` divides the playing as a Boss OC-2 does (and `octave_up`'s rectifier is antialiased
  too). The `pitch` block picks where each read head lands by correlation, so held notes don't warble.
- `cab` is a speaker cabinet as an impulse response: one of four built in (1x12 and 2x12 open
  back, 4x12 closed back, a modern 4x12), with the microphone moved from the cone's centre to its
  edge by `mic`. The responses are part of the engine (`core/cab_irs.h`, made by
  `tools/gen_cab_irs.py` from real speakers' published responses, `tools/cab_speakers.json`, and
  how cabinets measure), not of the effect file, so an effect with a
  cabinet stays a few lines long and every pedal plays the same one. It is the heaviest block:
  run it mono, once.
- `track` follows the note being played and is a modulation source: `hz` (its frequency, holding
  the last note through silence), `note` (its MIDI number, cents as the fraction) and `voiced` (1
  while a note sounds). It's YIN at half the sample rate (`core/pitch_track.h`): under a cent off
  on a held note, a reading every 10.7 ms, the first about 40 ms after a pluck, and a jump of more
  than a semitone without a new pluck has to read twice before it's believed. Wire `hz` into an
  `osc`'s `freq` (amount 1, with a `value` of -1 to cancel the oscillator's 1 Hz floor) for a synth
  that plays your notes, as Note Synth does. One note at a time.
- `harmony` is an intelligent harmonizer: a voice `steps` scale degrees from the note played, in
  `key` (0 C to 11 B) and `scale` (major, natural minor, mixolydian, dorian), pitched to the scale
  note exactly. Smart Harmony is one on a face.
- `forge graph` also pushes every knob to its ends (16 mixes, and each menu option with the knobs
  up): none may make NaN, or climb into the output limiter once the playing stops (a held freeze or
  a self-oscillating echo may ring on). It sweeps sines from the low E up the
  neck, and a drive (tagged distortion, overdrive, fuzz, drive or preamp) fails if it folds back
  more than -40 dB anywhere. `forge golden graphs` compares every effect with its fingerprint in
  `tools/golden_effects.json` (50 ms levels and octave bands, two knob settings): 0.05 dB off fails,
  so nothing changes how an effect sounds without `forge golden graphs --update` saying so.
  `web/golden.html` takes the same fingerprints in the browser's engine (`core/fingerprint.h`, one
  routine for both): every effect plays there bit for bit as on the desktop. Both, and the pedal's
  firmware, are built without fused multiply-add (`-ffp-contract=off`) so that they round alike.
- The `clip` and `clip_2x` blocks' `hot` says how hard they're driven: 0 up to the knee, then the
  share of each peak past it (0.5 at 6 dB over, 0.75 at 12 dB). It fills the distortions' Clip
  meters; the fuzzes built from formulas work it out with one more, `max(0, 1 - 1 / max(abs(a * b), 1))`.
- `forge graph` checks that a display moves while the guitar plays and stops (with the knobs turned
  up, if it must: a Clip meter at a low drive stays empty); one that shows a setting instead (the
  Freeze's lamp, lit while it holds) must follow the menus.
- Studio's editor puts one on the face with **Show on display** on a block, and the plugin window
  and Studio draw it under the effect's menus; the pedal shows it beside the effect's name on its
  screen.

**Layout.** An optional `"layout"` maps block names (and `"in"`, `"out"`) to `[x, y]`: where
Chirp Studio's node editor draws them. The sound never depends on it; Studio writes it when you
move a block, so a shared effect looks the way its author laid it out.

**Time in beats.** Any knob or menu control can add `"unit": "beats"` (its numbers are note
lengths: 1 is a quarter note, 0.75 a dotted eighth, 0.3333 an eighth-note triplet) or
`"unit": "per_beat"` (its numbers are rates: 2 is two cycles or steps a beat). The engine turns
them into seconds or Hz at the current tempo, so an echo set to a dotted eighth stays a dotted
eighth when the tempo changes:

```json
{ "id": "division", "label": "Division", "options": ["1/4", "1/8 dot", "1/8", "1/8T"],
  "controls": [ { "target": "line.time", "unit": "beats", "values": [1, 0.75, 0.5, 0.3333] } ] }
```

The tempo comes from wherever the effect is playing: the DAW's tempo in the plugin, the Tempo field
or Tap button in Studio (or the T key), tap tempo on the pedal (hold footswitch 2, tap footswitch 3), MIDI clock into the pedal,
or a board's `"tempo"`. Two blocks make rhythms: `tap` reads a delay's memory again at its own time
(several echoes from one line), and `rhythm` plays a step pattern (a gate and a ramp per step) for
slicers, stutters and step filters.

**The inside.**
- **Nodes** are blocks from the catalog (`blocks.json`). Everything is **stereo** unless the node
  says `"mono": true`; a mono node fed a stereo cable hears the average of the two sides, and its
  output goes to both.
- **Cables.** An audio input takes a source: `in` (the effect's input), `node` (its first output),
  `node:output` (e.g. `dark_hp:hp`), or `name.L` / `name.R` for one side. A list of sources is summed.
  Any output can go into any audio input. An unconnected input takes its catalog default (the
  effect's input, or silence), so a block makes sound the moment it's dropped in.
- **`set`** fixes a parameter or constant to a number in its range. Parameters not set, turned or
  modulated sit at their catalog default.
- **Mods** move a parameter with a source: `amount` is how far (in the parameter's own units, e.g.
  seconds) the source moves it at full swing. A knob can turn the amount. Sources are any node
  output; LFOs, random and envelope are the usual ones. The engine decides how often a parameter
  updates (every sample, or every 16 samples for filters), so any source works on any parameter.
- **`formula`** is the expert's block: math on up to four signals (`"tanh(a * 3) * b"`), still
  bounded like every other block.

## A board: `forge.board/0`

```json
{
  "format": "forge.board/0",
  "name": "Wide Tape", "author": "Chirp", "license": "CC-BY-4.0", "remixed_from": null,
  "chain": [
    { "slot": "short", "effect": { "id": "…", "name": "Tape Echo" }, "knobs": { "time": 0.3 } },
    { "split": [ [ { "slot": "long", "effect": { "id": "…", "name": "Ping-Pong Tape" } } ], [] ],
      "levels": [1, 1] }
  ],
  "snapshots": [ { "name": "Lead", "set": { "long.on": true, "short.mix": 0.5 } } ],
  "footswitches": [ { "switch": 1, "does": "bypass" }, { "switch": 3, "does": "snapshot", "snapshot": "Lead" } ]
}
```

- **`chain`** runs top to bottom. A step is a slot (one effect) or a `split`: lanes that each get the
  signal and are mixed back at `levels`. An empty lane is the dry signal, so `[[delay], []]` is a
  delay in parallel with the dry sound, Helix-style.
- **A slot** names an effect by `id` (and `name` for people), with knob and menu settings by id and
  `on`. A shared board link carries its effects inside it (`effects`, keyed by id), so it always
  plays, even offline.
- **Snapshots** change knob settings and on/off states within the board, instantly and without
  gaps, from a footswitch. `set` keys are `slot.knob`, `slot.menu` or `slot.on`. A name is at most
  7 characters, the width of a footswitch's label on the pedal's screen. The pedal keeps three: the
  ones on its footswitches, then the first of the rest.
- **Footswitches** 1 to 3: `bypass`, `snapshot`, `toggle` (one slot on and off), `tap` (tap tempo).
  `"momentary": true` on switch 1 or 3 makes it act only while held: letting go puts back the
  snapshot, bypass or slot it changed (a held boost, a freeze). Switch 2 always latches: holding it is
  how the pedal leaves snapshot mode.
- **Morph.** An expression pedal (MIDI CC 1, 11 or 100 into the pedal; the Morph slider in Studio;
  the Morph parameter in the plugin) glides from the first snapshot to the second: knobs in between,
  menus and switches as the end last reached has them.

## What makes sharing safe and simple

- **Ids are content.** An effect's or board's id is the SHA-256 of its canonical JSON (keys sorted, no
  spaces), first 16 hex digits. A link always plays exactly what was shared.
- **Remixes keep their family tree.** `remixed_from` holds the parent's `id`, `name`, `author` and a
  `note` in the remixer's own words on what they changed. The checker shows what changed against the
  parent automatically; the note is where the remixer gives credit and explains why.
- **Licences.** Every effect and board says what others may do with it: `CC-BY-4.0` (share and change
  it, crediting the author; the library's own), `CC-BY-SA-4.0` (the same, and remixes keep this licence:
  the checker refuses a remix of one that doesn't) or `CC0-1.0` (no conditions). Studio shows it on
  every card and lets you pick it on the effect's own panel.
- **Links carry the whole thing.** A board's share link (`#board=…`) carries its effects, and an
  effect's (`#effect=…`, the creator's Share link) carries the effect with its author and licence, so
  a link plays the same anywhere with nothing to look up. Effects a link brings that the library
  doesn't have are kept with your own, marked "saved", and Studio's Credits button copies who made
  each effect on the board and under which licence, ready to paste under a recording.
- **Packs move a set.** A pack (`forge.pack/0`) is one file with `name`, `author`, `effects` (whole
  effect files) and `boards` (whole board files, effects included): Studio's Save yours writes your
  own and saved effects with the current board, and Open file (or dropping the file on the page)
  reads one back, adding the effects you don't have yet and offering to load the board.
- **Loops only through memory.** A feedback loop must pass through an input marked `late` (a delay's
  `x`): the delay's output comes from the past, so every sample has a well-defined order.
- **Only blocks, never code.** Effects wire built-in blocks, so a shared effect can't crash, hang or
  reach anything outside its audio, and it plays on every pedal with the same engine. New blocks
  arrive as engine updates.
- **Cost known before it runs.** Every block has a CPU cost and memory in `blocks.json`, so the
  checker gives an effect's and a board's budget units before loading. Only the picked input of a
  `select` runs, so an effect costs its most expensive menu option. A board must fit the pedal's 12
  units (84% of its processor), and has at most eight effects: two rows of four. (The costs are placeholders until `daisy/bench` measures the real blocks.)
- **The output guard stays on.** The DC blocker, NaN guard and -1 dBFS limiter run after every effect.
- **Checked before publishing.** A file must pass `graph_check.py`, then `forge check`'s audio tests
  (level within 6 dB of bypass, no NaN, overs or clicks).

## How an effect runs

The pedal never reads JSON. The desktop (later the browser) compiles a file into a **program**: fixed
size, plain numbers, blocks in run order. Before running one, the interpreter checks every index and
count in it (`graph::valid`), so a damaged or hostile program is refused instead of crashing.

Each sample, every block that the output needs runs once, in order; then each delay writes its input,
which is how feedback reaches it. Blocks behind a menu option that isn't picked don't run. Knobs and
menus update the parameters every 16 samples, with a ramp in between so turning a knob never zippers,
and filter coefficients are only redone when something moved them.

**What it costs.** Tape Echo matches the built-in delay it was rebuilt from (same level and brightness,
envelope match 0.96), and on a desktop it takes about 2.5 times the hand-written template's time. The
pedal's own number comes from `daisy/bench` on a real Daisy. If it's too slow, the next step is to run
the blocks outside feedback loops a whole block of samples at a time instead of one sample at a time.

## Where the ideas come from

| Idea | From |
| --- | --- |
| A simple outside, an inside you open when you want | Reaktor Blocks, Kilohearts snapins |
| Knobs as macros that turn many things | Ableton Racks |
| Drag a modulator onto any parameter | Kilohearts Snap Heap and Phase Plant, Bitwig |
| Stereo by default, any output into any input, blocks work when dropped in | Bitwig Grid |
| Lanes in series or parallel, snapshots | Line 6 Helix, Snap Heap |
| Star an effect online and it's on your pedal | Neural DSP Cortex Cloud |
| Same built-in blocks everywhere, so a shared board never misses a plugin | The opposite of MOD's plugin dependencies |
| Remix family tree plus credit in the remixer's own words | Scratch |

## Files here

- `blocks.json`: the block catalog.
- `index.json`: the library list Chirp Studio reads (written by `graph_check.py`).
- `picks.json`: staff picks (`forge.picks/0`), the effects and boards Studio's library shows first, each
  with a line on why; `graph_check.py` checks each is here and passes. In `index.json` each remix also
  names its `parent` file (found by id, or by name and author if the parent changed since), which is
  how Studio draws a remix family; `display`, `tempo` and `knobs` feed its filters.
- Effects: Tape Echo (the built-in Delay template's tape line, rebuilt) and its remix Ping-Pong Tape;
  Silicon Fuzz, Muff Wall, Octave Fuzz, Classic Tremolo and its remix Touch Tremolo, Wide Chorus,
  Jet Flanger, Four-Stage Phaser, Envelope Filter, Lo-Fi Crusher, Ring Mod, Shimmer Cloud, Backwards,
  Auto Pan, Vibrato, Harmonizer, Sustainer, Volume Swell, Spring Reverb.
- Distortion, built on the `clip` and `clip_2x` blocks (seven clipping curves: soft, hard, diode,
  tube, cubic, fuzz and wavefold): Green Screamer, Rat Distortion, Valve Drive, Brit Crunch (an amp
  with its cabinet), Rectified Lead (high gain, gated), Fold Fuzz, Sputter Fuzz and Clip Lab (one knob
  steps through every curve).
- Utilities: Clean Boost, Studio EQ, Noise Gate, Limiter, Stereo Imager, Utility (gain, balance,
  width, polarity) and Cab Sim.
- Time, in note values that follow the tempo: Tempo Echo (digital, analog or ping-pong), Dotted
  Eighth (the classic two-echo rhythm, with a `tap`), Multi-Tap Rhythm, Slicer and Step Filter (both
  on the `rhythm` block's eight-step patterns), Beat Repeat (a stutter: a delay that recirculates its
  last slice on the beats a `rhythm` picks), Ducked Echo (an `envelope` pushes the repeats down while
  you play), and the remixes Tempo Tremolo (of Classic Tremolo), Tape Echo Sync (of Tape Echo), Sync
  Phaser (of Four-Stage Phaser) and Backwards Sync (of Backwards).
- Boards: Shoegaze Wall (Muff Wall, Wide Chorus, Tape Echo, Shimmer Cloud; snapshots Dream, Wall and
  Bloom on the footswitches), Ambient Swell (Volume Swell, Harmonizer, Backwards, Shimmer Cloud),
  Surf Shack (Classic Tremolo, a slapback Tape Echo, Spring Reverb), Drive Stack (Green Screamer as a
  boost into Brit Crunch, Tape Echo, Spring Reverb) and Wide Tape (a long ping-pong echo in a lane
  beside the dry sound) and Pulse (at 110 BPM: Slicer, Step Filter, Dotted Eighth, Spring Reverb;
  snapshots Echo, Chop and Gallop) and Stutter Lab (at 96 BPM: Beat Repeat, Sync Phaser, Ducked Echo,
  Backwards Sync; snapshots Verse, Glitch and Reverse) and Sixties Lounge (Valve Drive, Uni-Vibe,
  Rotary Speaker, Plate Reverb; snapshots Vibe, Slow and Fast).
- Boards for each layout: Dual Amp (two rigs side by side, a row each: Valve Drive, a 1x12 Cab Sim and
  Plate Reverb beside Brit Crunch, a 4x12 and Spring Reverb; snapshots Clean, Crunch and Lead switch
  a rig off, and it fades out), Wet Dry Wet (Compressor, Green Screamer and Brit Crunch, then Analog
  Delay and Concert Hall fully wet in a lane beside the dry sound; snapshots Dry, Verse and Solo),
  Synth Split (at 100 BPM: Compressor, then Sub Octave and a lowpass Envelope Filter beside Octave
  Fuzz and Sync Wah, then Tempo Echo after both; snapshots Bass, Fuzz and Both) and Full Board (all
  eight slots in one line: Noise Gate, Compressor, Green Screamer, Brit Crunch, Studio EQ, Wide
  Chorus, Analog Delay, Plate Reverb; snapshots Rhythm, Crunch and Lead), Parallel Space (Valve
  Drive and Wide Chorus, then Tape Echo beside Concert Hall, so the repeats never go through the
  reverb; Room, Echo and Huge), Parallel Squash (a hard Compressor beside the dry sound, New York
  compression, then Studio EQ, Wide Chorus and Plate Reverb; Light, Squash and Shine), Blend Fuzz
  (Silicon Fuzz beside a Clean Boost in slots 2 and 3, as bass players blend a fuzz; Clean, Blend
  and Fuzz) and Dust Lane (a Lo-Fi Crusher fully wet in slot 4 beside the dry sound, after a
  compressed Valve Drive and Tape Echo; Clean, Dust and Broken).
- A slow change from a menu: Rotary Speaker's Speed menu sets a `value`, and a `formula` with a
  `history` feeding back into it (`b + (a - b) * 0.00004`) eases towards it over about a second, so
  the rotors wind up and down instead of jumping. The eased signal modulates the LFOs' rates.

**Checking what a distortion does.** `forge graph` measures, on a steady note, how strong the even
(2nd, 4th...) and odd (3rd, 5th...) harmonics are: symmetric curves (soft, hard, cubic, fold) make
only odd ones, lopsided ones (diode, tube, fuzz, or any curve with bias) add even ones, the warmer,
tube-like sound. On a high note it measures aliasing, the harsh tones a clipper folds back below the
top of the audio band; `clip_2x` cuts it by about 15 dB on a hard clip at high drive.

`python tools/graph_check.py graphs` checks them all; `--id FILE` prints a file's id.
`build/forge graph graphs/tape-echo.graph.json --compare presets/warm-tape-echo.json --wav build/graphs`
plays one through the checks, compares it with the template, and writes what it sounds like.
