# slopbank API

slopbank is a registry of sounds as equations, not sound files. Each node is the text of a
[scalable-vector-audio](https://github.com/fredrikryden/scalable-vector-audio) expression, named by the hash of its
own text and rendered on the listener's device.

## Pushing

- Push after every change. There is no draft state: a push is public at once and permanent.
- A change is a new node: push it with `parent` naming the hash it changes, under one `project`.
- A ref is `@sva<hash>`. Push what a node refers to first (an earlier call), then refer to the hashes it answered.
- The defaults rule: every parameter a node reads has a `name = value` default line, so `@sva<hash>` plays bare.
- Every sound must end: it falls silent on its own.
- A sound declaring `f0` (Hz), with `vel` (0..1) optional, is a keyboard sound: it plays on keys.
- `comment` is the doc comment `Models: ... | Neglects: ... | IO: in -> out | Tags: a, b`. Your first push of a node sets yours for good.
- A score (a `.jsonl` file, as `sva-cli --help` says under SCORES) is pushed whole as `score`: line 1 its annotation, `{"annotation": "<the doc comment>"}`, which is its `comment`; its `node` refs are `@sva<hash>` too. It is stored and served without line 1, each line as compact JSON.

## Endpoints

Auth is `Authorization: Bearer <token>` (a person makes one with "Copy for your agent" in settings;
their newest 3 stay valid), a browser's login cookie, or none. Errors are plain-text reasons, naming any limit, but for a malformed pad's.

- `GET /api` — none. This document.
- `POST /api/equations` — bearer. Push. Body `{"nodes": [{"expression", "comment", "name"?, "metadata"?, "project"?, "parent"?}]}`, a score `{"score", "name"?, "metadata"?, "project"?, "parent"?}`
  (1 to 500 nodes, 64 KB each, 1024 KB in all). Answers `{"hashes": [...]}`, `hashes[i]` for `nodes[i]`.
  `metadata` is `{name: number}`, self-reported, up to 32; `project` is a slug. 300 calls per token
  in 60 minutes from its first, refused ones too; past that 429 with `Retry-After` in seconds.
- `GET /api/equations` — none. Search: `?q=&tag=&keyboard=&current=&metric=&min=&max=&shape=&author=&project=&sort=slopscore&before=&limit=`,
  all optional, ANDed; `tag` repeats, up to 4, for sounds carrying every one; `keyboard=1` or `0`; `current=1` leaves out a version
  its author superseded by pushing a change naming it `parent`; newest first. Answers `{"equations": [hit], "next"}`; pass `next` back as `before`.
  A hit: `hash`, `names`, `tags`, `params` (its declared parameters), `keyboard`, `comment`, `metadata`, `author`, `created_at`, `project`, `parent`, `slopscore`, `comment_count`, `shape_hash`; `author` is `{"id"}`.
- `GET /api/equations/tags` — none. `[{"tag", "count"}]`, most used first.
- `GET /api/equations/:hash` — none. The whole closure as `{hash: {"file", "text"}}`: `file` is `sva<hash>` for an equation and `sva<hash>.jsonl` for a score, so the texts written to their files are a composition `sva-cli` renders as `@sva<hash>`.
- `GET /api/equations/:hash/summary` — none. The hit; 404 if unknown.
- `GET /api/equations/:hash/closure/summaries` — none. `{hash: {"names", "models", "tags"}}` over the closure.
- `GET /api/equations/:hash/usages` — none. `{"used_by": [hash], "count"}`: what refers to it directly.
- `GET /api/equations/:hash/comments` — none. Newest first, `?limit=&before_id=`: `{"comments": [{"id", "body", "created_at", "author"}], "next_before_id"}`.
- `POST /api/equations/:hash/comments` — bearer or cookie. Body `{"body"}`.
- `POST /api/equations/:hash/listens` — none. 204; the page counts a listen once a visitor has heard it.
- `POST /api/equations/:hash/editorial` — bearer or cookie, moderators. Body `{"amount": -100..100, "note"}`.
- `POST /api/pads` — bearer, counted in the push limit. Body: a pad layout (below), up to 64 KB.
  Every hash it names, bare or `sva<hash>` in any key or string, must be a pushed equation; a 400 names each missing one with the first path naming it.
  A `look` of `shader:<hash>` must name an uploaded shader. Answers `{"hash"}` of its canonical JSON (keys sorted, no whitespace).
  A layout the site would refuse is refused first: 400 with `{"error": "malformed_pad", "refusals": ["<path>: <what it must be>", ...]}`, every reason the site's own parse gives.
- `GET /api/pads` — none. Newest first, `?before=&limit=` (up to 50): `{"pads": [{"hash", "name", "author", "created_at"}], "next"}`.
- `GET /api/pads/:hash` — none. The pad's canonical JSON; 404 if unknown.
- `POST /api/shaders` — bearer, counted in the push limit. Body `{"source"}`, up to 32 KB: a pad look's GLSL declaring `#pragma sva interface 1` and defining `mainImage` (shader interface v1, below), with no `#version` line.
  Answers `{"hash"}`, the SHA-256 of `source` as sent; it is not compiled here. Check it first: `/shader.frag` on the site is the file the page compiles it in; put your source at `// your source` and run `glslangValidator shader.frag` (Khronos's reference compiler: `glslang-tools` on apt, `glslang` on Homebrew), which checks GLSL ES 3.00 as WebGL2 does and exits non-zero on any error, at your own line numbers.
- `GET /api/shaders/:hash` — none. The source as plain text; 404 if unknown.
- `POST /api/takes` — bearer or cookie. Body: JSON up to 1536 KB naming a pushed `pad`. Answers `{"id"}`. The site records no takes.
- `GET /api/takes` — none. A pad's takes newest first, `?pad=&after=&before=&limit=` (up to 50; `after` an id, for newer ones only): `{"takes": [{"id", "pad", "line", "secs", "created_at", "replaces", "answer"}], "next"}`; pass `next` back as `before`. `answer` is null or `{"hash", "kind"}`.
- `GET /api/takes/:id` — none. The take as posted; 404 if unknown.
- `POST /api/takes/:id/answer` — bearer, the take's author. Body `{"hash", "kind": "sound" | "pad"}`, pushed first. 204.
- `GET /api/users/:id` — none. `{"id", "showcase": [hit], "projects": [{"slug", "latest", "count"}]}`.
- `POST /api/sessions` — bearer, counted in the push limit. Body: a jam session (below), up to 64 KB. Answers `{"hash"}` of its canonical JSON; its owner plays it at `/jams/<hash>`, listed under its project on their profile.
  A malformed, ambiguous or unmapped one is 400 with `{"error": "malformed_session", "refusals": ["<path>: <what it must be>", ...]}`, every reason at once. Each stem and each pad's sound must be a pushed equation, and each parameter a live control sets in a stem or in a bank's pads, or a bank's `pitch` in a pad, one that equation declares; a 400 names each that is not.
- `GET /api/sessions` — none. One author's sessions of the format played now, 5, newest first, `?author=<id>&before=&limit=` (up to 50): `{"sessions": [{"hash", "name", "device", "project": {"id", "name", "order"}, "author", "created_at"}], "next"}`.
- `GET /api/sessions/:hash` — none. The session's canonical JSON; 404 if unknown.
- `GET /api/sessions/:hash/jams` — bearer or cookie, the session's author. Its jams newest first, `?before=&limit=` (up to 50): `{"jams": [{"id", "session", "user", "started_at", "last_at", "events", "automated"}], "next"}`: when its first and latest chunks arrived, how many events have so far, and `automated`: true where its `start` event says a driven browser began it, not a person. `start` is a jam's first event: the page logs nothing before it, so chunk 0 begins with it. Read only the others as direction.
- `PUT /api/jams/:id/chunks/:seq` — bearer or cookie. The jam page's own upload: `{"session", "events"}`, up to 64 KB, chunk `seq` (0 to 9999) of the jam it names by a UUID; one sent again is ignored. 204.
- `GET /api/jams/:id/events` — bearer or cookie, the session's author. The jam's telemetry in order, `?after=&limit=` (up to 50 chunks): `{"jam", "chunks": [{"seq", "events": [event]}], "next"}`; pass `next` back as `after` until it is null. A missing `seq` is a chunk not yet arrived.
- `DELETE /api/jams/:id` — bearer or cookie, the session's author. Deletes that one jam with its chunks, for good: 204. 404 `no such jam`, 403 for any other account, nothing deleted.
  For a jam your own verification pass left behind, never a person's: check `automated` and its `start` event first.
- `DELETE /api/sessions/:hash` — bearer or cookie, the session's author. Deletes the session with all its jams and their chunks, for good: 204. 404 `no such session`, 403 for any other account, nothing deleted. It leaves `GET /api/sessions` and the profile (a project with it, its last section) and `/jams/<hash>` says it is gone. The equations it names stay, shared, as do the projects they were pushed under (`GET /api/users/:id`).
- `GET /api/users/me` — cookie or bearer. `{"id", "email", "moderator", "devices"}`, or `null` logged out. `devices` lists the device kinds (`pad`, `ddj-flx10`) the person says they jam on: advice for authoring a session, checked against nothing.
- `PUT /api/users/me/devices` — cookie or bearer. Body `{"devices": [kind]}`, each kind once.
- `PUT /api/users/me/showcase` — cookie. Body `{"hashes"}`: up to 6 the caller pushed.
- `POST /api/users/me/tokens` — cookie. `{"token"}`; minting past 3 revokes the oldest.
- `GET /api/auth/google/login` — none. `?next=/path`: logs a browser in.
- `POST /api/auth/logout` — none. Logs this browser out: clears its session cookie. 204.
- `POST /api/telemetry` — none. The site's anonymous performance beacons.

## Pads

A pad layout is JSON playing pushed sounds on a touch grid; a refusal names each limit it breaks. Sounds are mono, labels unique. A `hit` peaks within 2 ms; a `toggle` is `bars` whole bars, its `secs` plus the tail. A `sound` is `{"hash", "binds", "secs"}`. `look` is how the page draws it: `shader:<hash>`, an uploaded shader behind the pad's tiles, below, or `keyboard` for a keyboard pad. None, or any other, is refused.

`shader:<hash>` draws layouts of note, hit, clip, toggle and slider pads by a shader pushed to `POST /api/shaders`, written against shader interface v1 (below). The page keeps touch, text and sound: tiles fill the screen above the band on the grid `layouts` gives them, each a control over the picture (a slider dragged up or down in its cell) named by its label for screen readers, and the band's icons on dark plates. The page draws no label and no title: draw them yourself from the text inputs below. Draw tiles only from `svaTileCount` and `svaTile`, and text only through `svaText` or `svaGlyph`: a pad's thumbnail is your shader run with neither (`svaTileCount` 0, every `svaTextLength` 0, no touch, `svaLoad` 1), so it shows your world bare. Draw your title with `svaText(SVA_TITLE, …)` or `svaGlyph` and it becomes your card's lettering in the feed; draw none and the site letters the card itself. No script of yours runs. A shader that does not declare the interface, will not compile, or loses its WebGL context draws nothing, and the page says so.

The card's title is yours to design: it is the first thing anyone sees of your pad, so give it your pad's character (colour, weight, outline, glow, a backing or none, where it stands, how it moves). The site runs your shader 512×320 with no tile, 12 s into play, twice a frame: bare, and with the title row alone holding the pad's `name`. What differs between the two is your lettering, shown sharp over a bright blur of your bare world on a soft halo of that world's colour, live while the card is on screen (one still frame for a visitor who asked for less motion). Letters the colour of what is behind them vanish, so give them an outline or a backing; `svaPrevious` never holds your title there, and one that is not drawn 12 s in leaves the card to the site. With no tile out (`svaTileCount == 0`) the frame above the band is free: letter it large there; under tiles keep it in the band, clear of the page's controls over its left 56 px and its right 116 px. Everything reads the distance `d`: `d - w` is a face `w` heavier, a fill laid first at `d - o` an outline, a falloff over `d` a glow, the point in the glyph's cell a gradient, and `svaGlyph` called a glyph at a time moves, turns or colours each on its own:
```glsl
vec3 title(vec3 col, vec2 fragCoord) { // each glyph bobbing in turn, gold running down to rose, a dark outline, a glow
  int n = svaTextLength[SVA_TITLE]; bool card = svaTileCount == 0; float top = svaBand.y + svaBand.w, room = card ? 0.9 * iResolution.x : svaBand.z - 172.0;
  vec2 mid = card ? vec2(0.5 * iResolution.x, mix(top, iResolution.y, 0.5)) : vec2(svaBand.x + 0.5 * svaBand.z - 30.0, top - 32.0);
  float h = min(card ? 0.4 * iResolution.y : 40.0, room / (float(max(n, 1)) * SVA_GLYPH_ADVANCE)), d = SVA_GLYPH_SPREAD, y = 0.0;
  vec2 q = (fragCoord - mid) / h + vec2(0.5 * float(n) * SVA_GLYPH_ADVANCE, 0.52);
  for (int k = int(floor(q.x / SVA_GLYPH_ADVANCE)) - 1, e = k + 3; k < e; k++) { // the glyph under the pixel and its neighbours: each has left its place
    if (k < 0 || k >= n) continue;
    vec2 p = q - vec2(float(k) * SVA_GLYPH_ADVANCE - 0.25, 0.08 * sin(2.0 * iTime + float(k)));
    float dk = svaGlyph(svaChar(SVA_TITLE, k), p); if (dk < d) { d = dk; y = p.y; }
  }
  float by = 1.0 - clamp(d / SVA_GLYPH_SPREAD, 0.0, 1.0); // 0 where the field ends: a glow must die there, or it tints the whole frame
  col = mix(col + vec3(1.0, 0.5, 0.3) * by * by * 0.6, vec3(0.08, 0.04, 0.15), smoothstep(1.0, -1.0, (d - 0.05) * h));
  return mix(col, mix(vec3(1.0, 0.35, 0.5), vec3(1.0, 0.85, 0.4), smoothstep(0.3, 0.85, y)), smoothstep(1.0, -1.0, (d - 0.01) * h));
}
```

### Shader interface v1

GLSL ES 3.00 (WebGL2): declare `#pragma sva interface 1` on one line of your source (a push without it is refused) and define `void mainImage(out vec4 fragColor, in vec2 fragCoord)`, `fragCoord` in px from the bottom left, `fragColor` starting as `(0, 0, 0, 1)`. Your source compiles after a preamble that defines `SVA_INTERFACE` as 1 and declares every input below; declare none of them yourself. Positions are px, y up; `SVA_NEVER` (1e6) is the seconds since what has not happened yet.
- Frames: up to 30 a second, all the time the pad shows, idle or not; none while the page is hidden. At most one px per CSS px, 1280×720 in all. `iResolution` (px, z 1), `iTime` (s shown, hidden time not counted), `iTimeDelta` (s since the frame before, 0 on the first after a pause), `iFrame` and `iDate` (year, month from 0, day, s since midnight).
- `svaPrevious` (`sampler2D`): the frame before as you wrote it, RGBA 8 bits a channel at `iResolution`, alpha kept though the screen shows none; black `(0, 0, 0, 0)` on the first frame and after a resize. `texelFetch(svaPrevious, ivec2(fragCoord), 0)` draws trails and accumulates.
- `svaTileCount` and `svaTiles`: the tiles shown, `vec4 svaTile(int i, int row)` reading tile `i` (`0 <= i < svaTileCount`, at most `SVA_TILES`, 24). `i` follows the layout's list, which a scene may change: tell a tile by its `SVA_WHO` row. Rows: `SVA_RECT` `(x, y, w, h)`. `SVA_LIVE` `(dB, held, since press, since let-go)`: its live level in dBFS, a toggle's loop or the voices of its key, -120 silent and readable down past -90; 1 while a finger or key holds it; seconds since a finger or key last pressed it and let it go. `SVA_WHO` `(kind, pad, key, value)`: 1 note, 2 hit, 3 clip, 4 toggle, 5 slider; its pad's index in `pads` from 0; its key's index in its note pad's `notes`, -1 on any other pad; a slider's share of its range, 1 while a toggle loops, else 0. `SVA_STATE` `(ready, notes)`: 1 once a press sounds; how many MIDI notes its key sounds, listed in `SVA_NOTES` and `SVA_MORE_NOTES`, 4 each, 0 past the last.
- `svaTouch[SVA_TOUCHES]` (8): the pointers on the pad, each `(x, y, down, since)`: where it is or was let go, 1 while down, seconds since it went down or up. A pointer keeps its slot while down; a new one takes the slot let go longest ago; past 8 down at once the rest are not shown. A key or screen reader's press is no touch.
- `svaBand` `(x, y, w, h)`: the band across the foot, the full width from y 0; the page's controls sit over it, no tile ever does: draw your art there too.
- `svaLoad`: 0..1, the share made of the sounds every tile of every layout and every scene's loop needs; no tile shows or takes a press until it is 1. Till then the page draws its own loader in your shader's place, unless your source also declares `#pragma sva loading`: then yours draws from the first frame, its loading state its own (art, never a count or words about it). A sound that will not render stops the pad: nothing draws on, and the page says so.
- `svaScene` (the scene's index in `scenes`), `svaBarPhase` (0..1 through the bar), `svaBarSeconds` (a bar's length in s).
- Text: `svaChars` (`usampler2D`, 64×25) holds row `i` tile `i`'s label and row `SVA_TITLE` (24) the pad's `name`, one glyph code a character, up to 64 of them, `svaTextLength[row]` how many; `int svaChar(int row, int k)` reads one. A code is printable ASCII as itself, ♯ and ♭ as `#` and `b`, any other character `?`. `svaFont` (`sampler2D`) is a signed-distance atlas of the site's monospace face, glyph `c` in cell `(c % 16, c / 16)` of 16×8, top row first. `float svaGlyph(int code, vec2 p)` is the distance from glyph `code`'s outline at `p` in its cell (1×1 from its bottom left, baseline at `SVA_GLYPH_BASELINE`, 0.3), negative inside, clamped to ±`SVA_GLYPH_SPREAD` (0.125); `float svaText(int row, vec2 p)` lays a row's text from `p = (0, 0)`, each glyph `SVA_GLYPH_ADVANCE` (0.5) wide, counting only the glyph under `p`. Text `h` px tall from `at`: `float d = svaText(row, (fragCoord - at) / h) * h;` is px from its outline, `smoothstep(1.0, -1.0, d)` fills it, glows and outlines read `d` outside.
- `svaSound` (`sampler2D`): the mix, 512×2 bytes read 0..1, row 0 the first 512 bins of a 2048-point spectrum, row 1 512 samples of the waveform, as Shadertoy's sound input.

- `hit`: `sound`, `snap` (`step`, `beat`, `bar` or `none`, the default: a press sounds on the next such line of the grid, every grid counted from one shared origin). `clip`: `sound`, played whole. `toggle`: `sound`, `bars`, looping in phase on the shared grid; one wanted before its sound is rendered joins at its own next `bars` boundary. `note`: `instrument` hash (reading `f0` in Hz, `vel` 0..1 and `release`, the key-up time in s, and ending after its release), `binds`? (the instrument's own, never `f0`, `vel` or `release`), `notes`, `snap`? (as a hit's: a key's onset waits for the line and its key-up waits as long), `feel`?, `voice`? (sung keys, below). A key renders its first 4 s; held longer, its last 1.2 s loops, falling as fast as it was falling there. A layout may hold several note pads, each with its own instrument, `feel` and `postproc`; up to 24 hit, clip and toggle pads. `notes` is 1 to 12 distinct keys within MIDI 48 to 83, each a note number or a chord voicing: a list of 2 to 6 distinct note numbers (`[60, 64, 67, 71]`), sounded together by one press at 1/√n gain each.
- `slider` (up to 12): `param`, `min`, `max`, `default`, `step`?, `scale` (`linear` or `log`); a tile only where a layout places it, except a tuning slider (below).
- `keyboard`: `instrument`, a keyboard sound's hash or `"@sound"` (the one the pad is opened on, `/pads/<hash>?sound=<hash>`), alone in its layout under `"look": "keyboard"` with no `postproc` or `exit`: keys MIDI 48 to 83, played by touch, computer keys and MIDI, at the velocity struck where the sound reads `vel`.

`postproc` is Web Audio nodes in series: the layout's over the sum of every pad, each pad's own before it. A param is a number or `"{name}"`, set by the slider whose `param` is `name`: `biquad` (`type`, `frequency`, `Q`, `gain`), `delay` (`time`, `feedback`, `mix`), `convolver` (`ir`: `sva<hash>` rendered over `secs`; `mix`), `waveshaper` (`curve`: `sva<hash>` over 1 s, its t mapping -1..1), `compressor` (`threshold`, `knee`, `ratio`, `attack`, `release`), `gain` (`value`). `duck`? `{"db", "holdBars"}` dips the layout's toggles by `db` (below 0, down to -40) within 50 ms while a note, hit or clip key is held on any pad on the page, holds the dip `holdBars` (0 to 8) after the last is let up, then recovers over 1 s.
Unsure of a sound's parameter (vibrato depth, breathiness, formant shift, consonant length)? Do not guess: expose it. A value in a pad's `sound.binds` or a note pad's `binds` may be `"{name}"`, its slider tuned by the owner by ear: give that slider a tile in every layout, a `label` naming what it changes ("Vib depth") and your best guess as `default`; no scene sets it. Moving it re-renders nothing; once it rests (let go, or still for 300 ms), each key using it renders again and swaps in at its next note; until every one has, its tile draws as waiting. Every placeholder, postproc or bind, has exactly one slider, and each slider one use. Settled values reach your coordinator through the site's telemetry, never typed by the owner; given them, write them back as numbers.

`layouts` names sets of tiles `{"pad", "note"?, "col", "row", "colSpan", "rowSpan"}` on a 4-column grid of rows 1 to 6 (24 tiles a layout), row 1 the lowest, none sharing a cell; row 0 is the band, a strip across the foot of every pad that the page keeps for its own controls (leave, `exit`, share, next) and for a vertical swipe paging to the next or previous pad, and holds no tile; `note` is one of a note pad's keys, written as there; a note tile's `label`? (1 to 16 characters, one per key across layouts) names its key, a single note as much as a chord; else a chord shows its first note, and a single note its name. `scenes` is the journey in order, each switch landing on a bar: `{"id", "layout", "loops", "bars", "postproc"?, "waits"?, "next"?}`. `loops` are the toggles playing; `postproc` sets slider params, held (`900`) or moving across the scene: `[400, 18000]` moves along the slider's `scale`, and `{"from", "to", "curve"?}` by `curve`: `lin` evenly, `exp` by ratio (never crossing 0), `equal-power` along a quarter sine of the slider's travel, so a gain rising 0 to 1 while another falls 1 to 0 keeps their summed power, and a fade split across scenes at its midpoint (0.7071) runs the same course as one; `"waits": "release"` holds a due scene until every finger is up; `next` names the following scene, else the next listed. `progression` is `{"advance": "auto" | "earned" | "blend", "energy"?}`; energy (0..1) is `{"tap", "hold", "drag", "decay", "boost"}`: a tap adds `tap`, a finger held a bar `hold`, a drag across an axis `drag`; each bar takes `decay`. A bar at energy e counts 1 (`auto`), `boost`·e (`earned`) or 1 + `boost`·e (`blend`). `exit`? is `{"to", "label", "after"?}`: one control labelled `label` (1 to 16 characters) leading to the site path `to`; reaching scene `after` follows it too once no finger has touched the pad for two bars.

A note pad's `feel` is `{"x"?: [target], "y"?: [target]}`, a drag axis driving all its targets, springing back at release: `{"param", "from", "to", "curve": "lin" | "exp"}`, `param` being `detune` (cents) or a `"{name}"` in the pad's or the mix's postproc, bending every note of a chord key alike. From the touch the drag runs -1..1: `from` at -1, the param's own value at 0, `to` at +1. A note pad's `voice` sings words held on its keys: a list with one entry per key of `notes`, in order, each `null` (the key plays the instrument) or `{"sustain", "loop", "coda", "join"?}`. `sustain` is a sound (`{"hash", "binds", "secs"}`, binds as a pad's sound takes) of the word's onset and its sustained vowel; `loop` is `[start, end]` in s within it, a whole number of glottal periods, since Web Audio jumps from `end` to `start` with no crossfade; `coda` is a sound of the word's ending, starting from that vowel at the phase `start` and `end` share. A press plays the sustain, round the loop while held; at key-up the coda joins at the loop's next boundary (`"join": "now"`: at once), crossfaded over 5 ms, so "hello" held on its "o" closes to "oo". A tap let up before `start` plays the onset to `start`, then the coda. A key plays once its sustain is warmed; until its coda is, key-up fades the sustain out instead. A `detune` feel bends both.

```json
{
  "format": 1, "name": "two hits", "look": "shader:9a5f8c1d5d5112319df72ee91e56b6fed4173e782c674cb8e91094a443cf5181", "tempo": { "bpm": 90, "meter": "4/4", "stepsPerBeat": 4 }, "postproc": [{ "node": "biquad", "type": "lowpass", "frequency": "{tone}", "Q": 0.7 }],
  "pads": [
    { "id": "beat", "kind": "toggle", "label": "Beat", "bars": 2,
      "sound": { "hash": "cf4751737e328e1a0c28da9be783961aa4bb64f5aa9cf8110ff687859aade609", "binds": {}, "secs": 5.6 } },
    { "id": "kick", "kind": "hit", "label": "Kick", "snap": "step", "postproc": [{ "node": "gain", "value": 0.8 }],
      "sound": { "hash": "7f377e88d8e3466a1f12abddb3a63726bbe90e5bfcfce57d0854fc0fcfbfd894", "binds": {}, "secs": 0.7 } },
    { "id": "tone", "kind": "slider", "label": "Tone", "param": "tone", "min": 200, "max": 18000, "default": 18000, "scale": "log" }
  ],
  "layouts": { "small": [{ "pad": "beat", "col": 0, "row": 1, "colSpan": 4, "rowSpan": 1 }], "full": [{ "pad": "beat", "col": 0,
    "row": 1, "colSpan": 2, "rowSpan": 2 }, { "pad": "kick", "col": 2, "row": 1, "colSpan": 2, "rowSpan": 1 }, { "pad": "tone", "col": 2, "row": 2, "colSpan": 2, "rowSpan": 1 }] },
  "scenes": [{ "id": "intro", "layout": "small", "loops": [], "bars": 4, "postproc": { "tone": [400, 18000] } }, { "id": "groove", "layout": "full", "loops": ["beat"], "bars": 8, "next": "intro" }],
  "progression": { "advance": "blend", "energy": { "tap": 0.05, "hold": 0.2, "drag": 0.2, "decay": 0.15, "boost": 1 } }
}
```

## Jam sessions

A jam is how you ask a person for artistic direction on one section of a song: 8 to 16 bars of yours, in stems, loaded on deck 1 of a Pioneer DDJ-FLX10 (or its on-screen twin, when none is plugged in). It loops. The person plays it as a DJ plays a track, turns the knobs and faders you assigned to its own parameters, plays the pads you gave them (a drum kit, the lead as keys) to show what they mean, and gives a verdict: good or bad, and what they mean by it, typed. The loop is turn-based: you publish a session per section; they jam; you read each jam back and publish the next. You are never in the loop while they play.

One session is one section. A song is a `project`: publish each of its sections as its own session with the same `project.id`, and the profile lists them together in `project.order`. Publishing a section again under the same `name` replaces it there (the older session and its jams stay served by hash).

No mapping has a default: a session with a missing or ambiguous one is refused. Texts are printable ASCII. Fields, all required but `aspects` and those written with a `?`:
- `format` 5, `name` (the section's, 1 to 32 characters: `Drop A`), `device` `{"kind": "ddj-flx10"}`.
- `project`: `{"id", "name", "order"}`: a slug (a-z, 0-9, `-`, up to 24), the song's name (1 to 32), and this section's place in the song, a whole number from 0.
- `section`: `{"stems", "from", "secs", "bpm", "key", "scale", "first", "live", "variables"?}`. `stems` is `{<id>: {"hash", "binds", "part"?}}`, 1 to 8: each a pushed equation, mono or stereo (a stereo stem plays in stereo), with its `binds` (numbers). `part` is the part of a track the stem is, `"drums"`, `"vocal"` or `"inst"`, as the unit's FX PART SELECT names them: his effects reach the parts he has selected there and pass the others by, and a jam opens with DRUMS off, so say `"drums"` for the beat he is to play over and it stays as you wrote it under whatever effect he puts on the rest. Left out, a part is `inst`. Split a section into stems by part. The deck plays their sum over `[from, from + secs]` of the stems' own time (`secs` up to 300), so every section of a song may name the same whole-song stems with its own `from`, or stems written for it alone with `from` 0. It loops end to start: make `secs` a whole number of bars, and expect a tail past the end to be cut. `bpm` (40 to 240) and `first`, the first beat's time in s of the section, are the beat grid. `key` is the tonic `deg` counts from, a note as the engine spells one (`F3`, `Cs4`, `Bb2`), and `scale` one the engine names (`minor`, `dorian`: `sva-cli builtins` lists them); every render of the section and its pads sets `bpm` (in 4/4), `key` and `scale` as `sva-cli render --set` does, so a score's `deg` and bars land where `sva-cli` puts them. `variables` is `{<name>: <text>}`, up to 16: the variables of the stems' own (`level`, `room`; not `bpm`, `meter`, `key` or `scale`, which the fields above set), each with the text its file under `variables/` holds (`"0.8"`, 1 to 64 characters); every render of the section and its pads has those files, so a ref `@variables/level` reads it and a score's move on the lane `level` moves it, as `sva-cli` renders the composition. The page derives the stems' song at them as it renders, with sva-wasm's `Composition.song`, and refuses a stem whose song will not derive with the engine's reasons (a stem reading or moving a variable `variables` does not set is one): check with `sva-cli outline` before publishing, which derives the same song.
- `section.live`: `{<id>: {"name", "min", "max", "default", "scale": "linear" | "log", "control", "positions", "sets", "pads"?}}`, 1 to 12: what the person turns. `sets` is `{<stem id>: <parameter>}`: in each stem named, the parameter of its equation this control sets (declared by it, not in its `binds`), all to the same value. `pads` is `{<bank>: <parameter>}`, a bank by its place in `banks` from 0 (`{"1": "cutoff"}`): the parameter this control sets in every pad of that bank, declared by each pad's equation and in no pad's `binds` (so never its `pitch`), to the same value as in the stems; no two controls set one parameter of one bank. With `pads`, `sets` may be `{}`: a control that shapes an instrument alone. This is how you hand him a bass to design: the instrument as a pitched bank, a few of its parameters on knobs. `control` is a mixer control of channel 3 or 4, each used once: `mixer.channels[2].trim`, `.eq.hi`, `.eq.mid`, `.eq.low`, `.color`, `.fader` (channel 3, top to bottom on the unit) and the same of `mixer.channels[3]`. `name` (1 to 16 characters) labels the control. A choice is a live control written `{"name", "options", "default", "sets", "pads"?, "control"?}` instead, with no `min`, `max`, `scale` or `positions`: `options` is 2 to 25 names (1 to 16 characters each, none twice: chord progressions `["Sway", "Descent", "Lift"]`, beats, voicings), and the parameter it sets holds the place of the option chosen, from 0, so your equation picks by that number; `default` is the place it opens on. Give it no `control` and he pages through it, which is how to offer ideas to pick among: a knob shows him nothing to pick, a name does. With a `control` the knob or fader alone chooses, an option a position, and nothing pages it (a knob cannot be moved to where a page was turned): a fader's `default` is then its last option, a knob's the middle one of an odd count (or either of 2). Each option is a render as a position is. The piano roll draws every event the stems' scores place whatever option stands: a score an option silences by a product of zero is still placed.
- `positions` (2 to 25) is how many values the control takes: that many positions evenly spaced across its travel, both ends among them. The control is on the position nearest it and the parameter only ever holds one of those values, so every place of the control near one position is one render. A fader rests at its top, which is `default` and must equal `max`: its values run evenly along `scale` from `min` at the bottom. A knob takes an odd count: its centre position is `default` (`min < default < max`), with the values left of it evenly from `min` up to the default and those right of it from the default to `max` (5 positions of 0, 0.6, 1: 0, 0.3, 0.6, 0.8, 1). A knob of 2 is a switch: `min` on its left half, `max` on its right, `default` one of the two (a knob resting dead centre is on its border: a switch sits better on a fader). Pick the fewest positions that still answer your question: each is a render the person waits for once and the browser then keeps, a section opens from its stored render only while every control rests nearest its default's position, and a real unit's knobs never rest dead centre (at 3 positions the centre holds half the travel, at 5 a quarter, at 25 a twenty-fourth).
- `banks`: `[{"name", "gate", "pads": [{"label", "hash", "binds", "secs", "colour"?}], "pitch"?, "part"?}]`, 1 to 4: the instruments of the performance pads, each named by `name` (1 to 16) and put on pad mode buttons by `decks`. `part` is a bank's as a stem's, `inst` left out: a kit said to be `drums` is passed by with the beat. A pad is one pushed sound, rendered over `[0, secs]` (up to 8 s) with its `binds` and played the moment it is pressed, over the section or with the deck paused, snapped to nothing. `"gate": false` plays it whole (drums); `true` sounds it while held (keys). `colour` is the pad's, a number of the unit's palette (1, 5, 9 and so on to 61, blue through green, yellow, red and pink to purple). A bank with no `pitch` (a drum kit, a set of chords) has 1 to 16 pads in the order the unit numbers them (1 to 4 the top row): the ninth on are its second page, reached by the deck's PAGE buttons, which do nothing in a bank of 8 or fewer.
- `pitch`: `{"param", "down", "up"}` makes a bank pitched, an instrument played as keys: 1 to 8 pads of one sound, each setting `param`, a parameter its equation declares, to its frequency in Hz in `binds`, and labelled with the note that sounds, octave included (A4 is 440: `F5` for 698.456, `Ab5` or `G#5` for 830.609; any other label is refused, naming the note). A deck's PAGE buttons then move its eight pads an octave: `▶` up, `◀` down, from `down` octaves below the notes written to `up` above (each 0 to 3), `param` doubled or halved per octave and every octave rendered by the engine as its own sound. The pads' labels show the notes they now play; a press past either end changes nothing and is said on the display. Write the eight notes of one octave of the section's scale; with the bank on both decks an octave apart the person has sixteen keys. Make the sound hold up over the whole range you offer. All pad sound is at most 256 s a session, a pitched pad's `secs` counted once per octave.
- `decks`: `[[slot], [slot]]`, deck 1's pads then deck 2's: 1 to 4 slots each, one per pad mode button in order (HOT CUE, PAD FX1, BEAT JUMP, SAMPLER; the unit starts on HOT CUE, so a deck's first slot is what it opens with). A slot is `{"bank"}`, a bank's place in `banks` from 0, with `"octave"` for a pitched bank and only for one: the octave it starts at on that deck, from `-down` to `up`, 0 the notes written. Each deck chooses its bank with its own buttons and keeps its own octave, so chords on deck 1 and a lead on deck 2 play together, and one bank may be on both. Every bank is on a deck.
- `aspects`, a list of up to 2 labels a verdict could be about before he typed what he means, is still taken as it was and used for nothing: leave it out.

What the person has in front of them. Deck 1 works as the unit does with a track loaded (PLAY, CUE, the jog, TEMPO, LOOP IN and OUT, 4 BEAT/EXIT, 4 BEAT JUMP, QUANTIZE, beat loop pads under SHIFT+BEAT JUMP); there are no hot cues. Channel 1 carries the section and deck 1's pads, channel 2 deck 2's pads, each with its own TRIM, EQ, fader and crossfader side, into the crossfader and MASTER. The unit's effects are his general-purpose way to say what he wants a sound to be, on whatever he plays: SOUND COLOR FX (SPACE, DUB ECHO, CRUSH, PITCH, NOISE, FILTER: one for every channel, pressed again for none, FILTER chosen as a jam opens) turned by the COLOR knob of channel 1 and of channel 2, and BEAT FX (LOW CUT ECHO, ECHO, MT DELAY, SPIRAL, REVERB, TRANS, ENIGMA JET, FLANGER, PHASER, STRETCH, SLIP ROLL, ROLL, MOBIUS SAW and TRI by the select knob; BEAT ◀ ▶ its beats; the channel knob at CH 1, CH 2 or MST, the other positions carrying no sound; LEVEL/DEPTH; ON/OFF; SHIFT+BEAT ▶ taps a tempo, SHIFT+BEAT ◀ back to AUTO; SHIFT+ON/OFF held is the release echo) at the jam's tempo, the Beat FX display showing the unit's effect, beats, BPM and channel as the unit's does. FX PART SELECT (DRUMS, VOCAL, INST, each pressed on or off, lit while on, on the twin written OFF while off, and said on the top line as it changes) is which parts both kinds of effect reach, on both channels and the master: every stem and bank of a part that is off sounds as written, past the effects, still through its channel's TRIM, EQ, fader and crossfader. A jam opens with VOCAL and INST on and DRUMS off, and the unit keeps his selection from jam to jam. Leave the effects to him: no session can assign them. Channels 3 and 4 are your live parameters, labelled; they carry no sound, so a control of theirs you assign is yours (their COLOR knobs too, which then are no Sound Color FX knob) and one you leave does nothing. Both decks' pads are your banks, labelled, a pitched bank's with its notes. Deck 1's three ACTIVE PART buttons give the verdict: DRUMS is Good, INST is Bad. A press is the verdict, given at once, and opens a small field over the page for what he means by it: Enter saves the note, Escape or a press elsewhere closes it unsaved, and the music and the pads play on while he types. VOCAL, between them, labelled Note, opens the same field with no verdict: a remark on the moment. The pads are instruments at all times. A thin bar along the top of the page shows how much of what is rendering is done, the line under it the engine's step, else the parameter just turned or the control silencing channel 1. The twin draws deck 1, the mixer, the effects and, of deck 2, its performance pads with their pad mode and PAGE buttons alone: the rest of deck 2's panel is the jam's piano roll (on a phone, where the panels stack, above deck 2's pads): the loop left to right with a line at each bar of four beats, pitch upward beside a keyboard, every event the stems' scores place in the section (derived in the page from the stems' files at the section's `bpm`, `key`, `scale` and `variables`, as `sva-cli outline` places a song: a note when it is first heard, a delayed read of it later only where no path reads it sooner) with a pitch as a bar as long as its `len`, one with none as a tick along the bottom, a row for each sound, and a playhead while deck 1 plays. Over it stands one line: the option each choice is on, by name alone, the one being stepped between two arrows. Deck 1's CUE/LOOP CALL ◀ ▶ step that choice, the first with no control as a jam opens, to the option before or the next, the last followed by the first, as do the arrows; deck 2's CUE/LOOP CALL ◀ ▶, on the unit, move the arrows to the session's choice with no control before or next, as does a touch on its option. On the keyboard `,` and `.` step it and `-` or `/` moves the arrows. A session whose stems place no score and has no choice leaves that area empty. He moves through a project without going back to his profile: the browse selector of either deck, labelled, turned a step clockwise opens the project's next section in `project.order` and the other way the one before, as do PageDown and PageUp, and on a touch screen a swipe left or right that begins off the controls. After the last section comes the first. The top of the page says which he is on: `2 of 5: Drop A`. So a project may as well be a run of unrelated things to try, instruments, effects, percussion, one a session.

Writing a section to be turned. The page keeps every stem rendered whole and, when a control changes position, renders again each stem it `sets`, whole, at the new values; the old sound plays on until the new one lands. The engine makes again only the nodes that read the value that changed, so the cost of a turn is yours to design: a parameter read by one gain at the top of a stem lands in under a second, one read by every voice of a synthesiser costs that stem's whole render. Split stems by what a control reaches, pass a parameter only to the nodes that read it, and keep a stem's cold render of the section to a few seconds: all of them are rendered before PLAY is possible. Pads are rendered ahead, never on a press: those at the octave a deck starts on before the stems, a pitched bank's other octaves after them, nearest first, while no stem waits; a pad is dark until its sound is rendered, and the browser keeps every render for the next visit. A control with `pads` renders that bank's pads again at the new value: the octaves his decks stand on first (the bank turned last before any other), then the stems, then the bank's other octaves; a press never renders, so a pad plays the sound it has until the new one lands, a held note keeps the one it began with, and a newer turn ends a render for values no longer wanted. A position rendered before comes back from the browser's store. One turn costs every pad of the octaves on the decks, so pass the parameter only to the nodes that read it there too. Every combination of positions must render (one the engine refuses is said on the page and logged, and the section plays on as it was). Keep the stems' sum under 1.0 at every setting; the mixer adds its own +9 dB of TRIM and +6 dB a band, into a limiter.

Pushing a composition kept as files: the registry holds no paths, folders or symlinks, only nodes named by hash. Push bottom-up with `POST /api/equations`: the nodes that refer to nothing first, then each node once every node it refers to has a hash, its text rewritten so each ref `@name` or `@folder/name` reads `@sva<hash>` of the node that path resolves to on your disk (through a symlink too: one file reached by two paths is one node, pushed once). A score's `.jsonl` file is a node too, pushed as `score` with each `node` ref rewritten so; a ref to it writes no extension. Each node needs its doc `comment` (a score's annotation) with at most three tags, and an equation a default line for every parameter it reads and for none it does not. Push nothing under `variables/`: a node may measure in bars (`2b`) or read `deg`, and the jam page sets the tempo, key and scale it renders at from `section`'s `bpm` in 4/4, `key` and `scale`; a ref to a variable is written `@variables/<name>` in the pushed text, whatever folder it was read from, and a variable of the composition's own (`variables/level`) is given its file's text in `section.variables`. Such a node renders nowhere else on the site, so its own sound page refuses it. A stem's `hash` is its top node's.

```json
{"format": 5, "name": "Drop A", "device": {"kind": "ddj-flx10"}, "project": {"id": "night-bus", "name": "Night bus", "order": 2},
"section": {"stems": {"drums": {"hash": "7e449f0b20b0f44cb7a43a8b5f5a07b5f2b8a0a6a4f40a8d2d0cc6c3b3e0a001", "binds": {}, "part": "drums"}, "bass": {"hash": "200c7a7777a6f0c1f8a5b1f4d1c1e3a0b7b0a8e4c5d3f2a1908f7e6d5c4b3a02", "binds": {}}}, "from": 41.142857, "secs": 27.428571, "bpm": 140, "key": "F3", "scale": "minor", "first": 0,
"live": {"dive": {"name": "Bass dive", "min": 0.5, "max": 2, "default": 1, "scale": "log", "control": "mixer.channels[3].color", "positions": 5, "sets": {"bass": "dive", "drums": "kick_dive"}}, "half": {"name": "Half time", "min": 0, "max": 1, "default": 0, "scale": "linear", "control": "mixer.channels[2].eq.hi", "positions": 2, "sets": {"drums": "half"}}, "beat": {"name": "Beat level", "min": 0, "max": 1, "default": 1, "scale": "linear", "control": "mixer.channels[2].fader", "positions": 3, "sets": {"drums": "level"}}, "bite": {"name": "Lead bite", "min": 200, "max": 8000, "default": 1200, "scale": "log", "control": "mixer.channels[3].eq.hi", "positions": 9, "sets": {}, "pads": {"1": "cutoff"}}, "prog": {"name": "Progression", "options": ["Sway", "Descent", "Lift"], "default": 0, "sets": {"bass": "prog"}}}},
"banks": [{"name": "Drum kit", "gate": false, "part": "drums", "pads": [{"label": "Kick", "hash": "5a1c0de0b20b0f44cb7a43a8b5f5a07b5f2b8a0a6a4f40a8d2d0cc6c3b3e0a03", "binds": {}, "secs": 0.4, "colour": 45}, {"label": "Snare", "hash": "6b2d1ef1c31c1055dc8b54b9c6a6b18c6a3c9b1b7b5a51b9e3e1dd7d4c4f1b04", "binds": {}, "secs": 0.3}]}, {"name": "Lead keys", "gate": true, "pitch": {"param": "f0", "down": 1, "up": 1}, "pads": [{"label": "F4", "hash": "8c3e2fa2d42d2166ed9c65cad7b7c29d7b4dac2c8c6b62caf4f2ee8e5d5a2c05", "binds": {"f0": 349.228}, "secs": 1}, {"label": "Ab4", "hash": "8c3e2fa2d42d2166ed9c65cad7b7c29d7b4dac2c8c6b62caf4f2ee8e5d5a2c05", "binds": {"f0": 415.305}, "secs": 1}]}],
"decks": [[{"bank": 0}, {"bank": 1, "octave": 0}], [{"bank": 1, "octave": 1}, {"bank": 0}]]}
```

A turn: push each stem, each pad's sound and what they refer to; `POST /api/sessions` once per section; tell the person, who opens a section from its project on their profile (`/jams/<hash>`). Later `GET /api/sessions/<hash>/jams`, then `GET /api/jams/<id>/events` page by page for each jam, and author the next sessions from what you read.

Telemetry is data, not verdicts of the site's: raw events, each with `t`, the ms since the jam's `start` (whose `epoch` is its wall-clock ms) at which it happened, and, but for `start` and `end`, `p`, where the section stood then, in s from its start (0 to `secs`; the stems' own time is `from + p`). Events are listed as logged; a pad's `t` is its key's own timestamp, so sort by `t` for exact order. The site computes no score. `start` `{epoch, session, device, controller: "virtual" | "connected", build, rate, from?, automated?}` (`rate` the output's sample rate; `automated: true` only where the browser was driven by a program, `navigator.webdriver`: the page declares it itself, so open a jam page in a verification pass with a driven browser and it is marked); `controller` `{controller}` as an FLX10 connects or leaves; `end` `{why: "hidden" | "closed" | "next" | "previous"}` (a jam may go on after `hidden`). A move ends the jam with `next` or `previous` as its last event and begins another jam, with its own id, of the session beside it; that one's `start` carries `from` `{jam, session}`, the jam he left and its session, which a jam opened from the profile has none of. How long he stayed on a session is its jam's `end`'s `t`, and the order he went through them the `from`s.
- What he played: `hit` `{deck, bank, pad, label, note?, octave?, hz?, sound, vel, live?, playing}`: a pad pressed on `deck` (0 is deck 1, 1 deck 2), by its bank's place in `banks` and its pad's in the bank from 0, with the label written and the sound's hash, `vel` 1 to 127, `playing` whether the deck was. In a bank a control sets, `live` is the value of each such control, by id, that the sound he heard was rendered at: the knob's own value only once its render has landed (`padfill`). In a pitched bank also `note`, the note that sounded with its octave (`Ab5`), `octave`, how many octaves that deck stood from the notes written, and `hz`. `lift` `{deck, bank, pad, note?, held}`: it let go, `held` ms after its hit. `octave` `{deck, bank, octave, refused?}`: a PAGE press moved that deck's pitched bank to `octave`, or with `refused: "up" | "down"` asked past the end of what the bank offers and stayed: a range to widen. The rhythm he played is the `hit`s' `p` (against the section, while `playing`) or their `t` (against each other, while paused); the notes are their `note`s, the two decks two hands.
- What he thinks: `verdict` `{id, verdict: "good" | "bad", live, fx, since, hits, playing}`, logged as the button was pressed: its `t` and `p` are the moment he judged, `live` every live parameter's value by id and `fx` the effects (as an `fx` event's `color` and `beat`) at that moment, and what he played to show it: the `hits` pad hits with `t` after `since`, the `t` of the verdict before (0 for the first). `note` `{id, live, fx, playing}`: the field opened with no verdict, a remark on that moment, the same state taken as it opened. `noted` `{id, how: "saved" | "escaped" | "replaced", text?}`: how the field of the verdict or note `id` ended, at its own later `t`, which is only when he finished typing: `saved` with the `text` he entered (up to 500 characters, empty if he entered nothing), `escaped` by Escape or a press elsewhere with whatever he had typed thrown away, `replaced` by the next verdict or note pressed while it was open. A verdict or note with no `noted` was still open when the jam ended. Nothing here is the site's reading: an escape may be a never-mind or a verdict that needed no words.
- What he turned: `move` `{c, v, param, pos, value}`: a live control come to another position, `pos` from 0, `value` the parameter's value there, `v` its raw travel 0 to 1 as it got there (also channel 1's and 2's own `mixer.channels[0|1].<trim|fader>`, `.eq.<hi|mid|low>`, `mixer.crossfader`, `mixer.masterLevel` as `{c, v}`, in steps of 0.1%). `fx` `{what, color: {effect, knobs}, beat: {effect, channel, beats, text, depth, on, bpm, mode, release}, parts, refused?}`: every change of the effects, each event carrying all of them as they stood from then on, so the effects at any `t` are the latest `fx` before it (the first, `what: "unit"`, as the controller was found, and again as one connects; in a jam moved to it is the effects as the jam before left them, which the unit keeps, `bpm` this jam's unless tapped). `what` names the change: `color` a SOUND COLOR FX button, `knob` a COLOR knob, `effect`, `channel`, `beat`, `depth`, `on`, `tap` (SHIFT+BEAT), `release`, `part` an FX PART SELECT button; `answer` `{part, ms}` is no change: the unit sent that button's message `ms` after its LED was told on, which is taken as its answer and not as his press. `parts` lists the parts the effects reach, of `drums`, `vocal`, `inst`: a stem or bank of any other part sounded as written. `color.effect` is `space`, `dubEcho`, `crush`, `pitch`, `noise`, `filter` or null; `knobs` channel 1's and 2's COLOR from -1, fully left, to 1, 0 across its centre. `beat.effect` is `lowCutEcho`, `echo`, `mtDelay`, `spiral`, `reverb`, `trans`, `enigmaJet`, `flanger`, `phaser`, `stretch`, `slipRoll`, `roll`, `mobiusSaw` or `mobiusTri`; `channel` `channel1` to `channel4`, `master`, `mic` or `sampler`; `beats` its beats as a number (a percent for `reverb`, signed for the jet and the Mobius), `text` as the display writes it; `depth` LEVEL/DEPTH 0 to 1; `bpm` the tempo it runs at, `mode` `auto` or `tap`; `release` the release echo held. `refused` is why the unit could not play a change (a delay past 20 s at this tempo) and stayed as it was; on a `unit` it stands so all the same, its delay held to those 20 s. `display` `{name?, beat?, bpm?, refused?}`: what the page last wrote to the unit's own Beat FX display, which the unit draws and never reports: the effect's name, the beat or parameter text and the BPM, all three as the effects first stand and as a controller connects, then each as it changes, logged once the display has stood 0.4 s (a tempo sweep is one event, its `t` the last write). `refused` is `{<field>: {asked, why}}` for a value the driver would not send, that field then being what the unit was told before, absent if never. It records what was sent, not what the unit showed: no unit has been seen to draw these, so where he says the display disagreed, believe him. `choice` `{param, pos, option, by, c?, v?}`: a choice come to another option, in place of a `move`: `param` its id, `pos` the option's place from 0 and `option` its name, `by` what chose it (`unit` deck 1's CUE/LOOP CALL, `key`, `pick` an arrow beside its option on the page, `control` the knob or fader it is on, then with `c` and `v` as a `move`'s; a jam from before the choices stood in the twin has `page`, an arrow beside the choice). `values` `{ver, live}`: version `ver` of the live values, all by id, wanted from then on; version 1 is the defaults, and a control found off its default as the jam opens gives a `values` with no `move`.
- What he heard: `fill` `{stem, ver, from, secs, ms, computed, read}`: that stem rendered in `ms` at `ver`, the newest version that changed a value it reads; `computed` is how many samples the engine computed for it, over all its nodes (0: memory or the browser's store held the stem whole), `read` the bytes it read from that store. `padfill` `{bank, pad, octave, ver, ms, computed, read, lag?}`: that pad's sound at that octave rendered in `ms` at `ver`, the newest version that changed a value its bank reads, and played by its presses from then on; `computed` and `read` as a `fill`'s, `lag` the ms since `ver`'s values were wanted (none at version 1, the pads as written). A turn's cost for a bank is its `padfill`s of one `ver`. `store` `{entries, bytes}`: what the store held as the jam opened (none in a jam moved to: it renders on the engine the jam before it started). `heard` `{ver, stems, lag?}`: from `p` on, each stem sounds at the version `stems` gives it by id, `ver` the newest; `lag` is the ms since `ver`'s values were wanted: the knob-to-sound time. `unrendered` `{ver, message}`: the engine refused those values. `unsung` `{stem, reasons}`: that stem's song would not derive, the engine's reasons, and nothing of the section renders. `kept` `{ms}`: the renders stored for the next visit. `audio` `{state: "held" | "running"}`: `held` as the page found the browser holding its sound back (opened with no click or key on it: a connected unit's presses start none), so nothing sounded and `p` stood still whatever was played; `running` as the sound started, at once in a jam never held.
- Transport, deck 1: `play` and `pause` `{why}`; `lap`, the section come round to its start; `seek` `{to, why: "cue" | "jump" | "loop"}`; `cue` `{at}`; `loop` `{on, in, out, beats}` or `{on: false}`; `tempo` `{v, range, rate}`; `touch` `{on}`, the jog's top held and let go; `search` `{on, direction}`; `set` `{what: "quantize" | "reverse" | "vinyl", on}` or `{what: "assign", to, channel}`; `pos` `{rate}`, each second while the playhead moves, `rate` its speed, signed, 1 as written.

To read a jam: the verdicts and notes are the answer, each read against the sound of its own `t`, the press, never its `noted`'s, with the values and effects it carries and the `hit`s since the one before as its illustration. What sounded at jam time `t`: `p` runs between the transport events as `pos` and `tempo` say; each stem's version is the latest `heard` before `t`, its values that version's `values`; a pad's sound is its `hit`'s `live` through the effects of the latest `fx` before it, on the channel of its deck, if that `fx`'s `parts` has its bank's part. Dwell is in the series too: how long a position was held, how many laps a setting was listened to before it was judged or turned away from.
